分类目录归档:MFC

Capturing the Desktop Screen with the Mouse Cursor Image

下面的文章介绍了两种截屏方式及如何“截取”光标的方法。

通常情况下是无法截取光标的,因为光标不属于窗口中的windows元素,而是独立与窗口系统的另一层东西,叫Hot Spot。

申明:本文以下内容归原作者所有!

By 27 Jan 2006

DesktopCaptureWithMouse

Introduction

This article shows how you can capture screen images including the mouse cursor.

Background

Screen capturing is a very useful way of resource sharing as used in applications like Remote Desktop, Virtual Network Computing (VNC), where a user can access, view, and interact with a remote desktop as his own desktop. Also, it is used in non ethical applications like hacking applications, where a hacker can hack a computer using some malicious server application, and the server then frequently takes screenshots of the prey machine and sends them to the clients. You will see a lot of source code resources over the internet discussing how to take screenshots of the desktop or an area of the desktop but none of them discuss how to capture the mouse cursor bitmap with the screenshot. Sometimes it becomes necessary to capture the mouse to see the whole activity of the hacked machine. First, we will discuss here what the actual problem is.

Problem

Most of us think that the mouse cursor image is a part of the desktop display but actually it works on an upper layer over the desktop. Windows always tracks mouse with a “Hot Spot”, the actual position seen by the Windows.

Currently, there are two common ways to capture and manipulate the desktop image:

  1. Copy the desktop bitmap data from Video Memory to the System Memory. Do processing and then again blit it back to the Video Memory. This can be easily done using the BitBlt() or the StretchBlt() APIs provided by Win32.
  2. Another way is to directly manipulate the desktop bitmap in the Video Memory if enough memory is available as provided by DirectDraw.

Both of these don’t provide us the facility to capture the mouse cursor image with the desktop image.

Solution

The solution to the problem of capturing the mouse cursor image with the desktop image is quite simple.

  1. First, get the bitmap of the screen using BitBlt(). I have provided a simple function namedCaptureDesktop() in the CaptureScreen.cs file that captures the screen bitmap as almost all the codes available over the internet do.
  2. Then capture the mouse cursor bitmap as:

    Get Cursor Icon:

    First, get the cursor information using the Win32 GetCursorInfo(). The function fills the CURSORINFOstructure provided as a parameter. Don’t forget to initialize the cbSize member of the structure before passing it as an argument. Then we check whether the cursor is visible or not, by checking for the equality of the flagsmember of the filled structure with the CURSOR_SHOWING constant. If they are equal then we get the handle to the cursor icon using the CopyIcon() function that takes the hCursor member of the above filled structure.

    Get Cursor Position:

    Now, we have to get the Icon information so that we can get the hotspot position. This information is easily retrieved using the GetIconInfo() function. Here is the C# implementation of the mouse capturing function:

    static Bitmap CaptureCursor(ref int x, ref int y)
    {
       Bitmap bmp;
       IntPtr hicon;
       Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO(); 
       Win32Stuff.ICONINFO icInfo;
       ci.cbSize = Marshal.SizeOf(ci);
       if(Win32Stuff.GetCursorInfo(out ci))
       {
           if (ci.flags == Win32Stuff.CURSOR_SHOWING)
           { 
               hicon = Win32Stuff.CopyIcon(ci.hCursor);
               if(Win32Stuff.GetIconInfo(hicon, out icInfo))
               {
                   x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
                   y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);
                   Icon ic = Icon.FromHandle(hicon);
                   bmp = ic.ToBitmap(); 
    
                   return bmp;
               }
           }
       }
       return null;
    }
  3. We now have both the bitmaps, i.e., the desktop bitmap and the mouse cursor bitmap with its position on the screen. Now, it’s time to place the mouse cursor bitmap on the desktop bitmap. I have provided the following function that places the mouse cursor image over the desktop bitmap at the proper position:
    public static Bitmap CaptureDesktopWithCursor()
    {     
       int cursorX = 0;
       int cursorY = 0;
       Bitmap desktopBMP;
       Bitmap cursorBMP;
       Bitmap finalBMP;
       Graphics g;
       Rectangle r;
       desktopBMP = CaptureDesktop();
       cursorBMP = CaptureCursor(ref cursorX, ref cursorY);
       if(desktopBMP != null)
       {
           if (cursorBMP != null)
           {
               r = new Rectangle(cursorX, cursorY, 
                       cursorBMP.Width, cursorBMP.Height);
               g = Graphics.FromImage(desktopBMP);
               g.DrawImage(cursorBMP, r);
               g.Flush();
               return desktopBMP;
           }
           else
               return desktopBMP;
       }
       return null;
    }
  4. The bitmap with the cursor is now ready to be rendered over a viewer surface (a PictureBox used here). Since the viewer’s visible area is smaller than the desktop bitmap area, scaling has been used in the function that displays the cooked desktop image.
    // ssWithMouseViewer is the PictureBox control
    private void Display(Bitmap desktop)
    {
        Graphics g;
        Rectangle r;
        if(desktop != null)
        {
            r = new Rectangle(0,0,ssWithMouseViewer.Width, 
                                ssWithMouseViewer.Height);
            g = ssWithMouseViewer.CreateGraphics();
            g.DrawImage(desktop,r);
            g.Flush();
        }
    }

Note

The binary image provided is compiled with Visual Studio .NET 2005 so you have to install .NET Framework 2.0.

Known problems

I have tested the application on my machine with no known problems, so I expect the same behavior on your machine.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

本文转自:http://www.codeproject.com/Articles/12850/Capturing-the-Desktop-Screen-with-the-Mouse-Cursor

三种截取屏幕的方式

Various methods for capturing the screen

By 19 Sep 2006

Contents

Introduction

Some times, we want to capture the contents of the entire screen programmatically. The following explains how it can be done. Typically, the immediate options we have, among others, are using GDI and/or DirectX. Another option that is worth considering is Windows Media API. Here, we would consider each of them and see how they can be used for our purpose. In each of these approaches, once we get the screenshot into our application defined memory or bitmap, we can use it in generating a movie. Refer to the article Create Movie From HBitmap for more details about creating movies from bitmap sequences programmatically.

Capture it the GDI way

When performance is not an issue and when all that we want is just a snapshot of the desktop, we can consider the GDI option. This mechanism is based on the simple principle that the desktop is also a window – that is it has a window Handle (HWND) and a device context (DC). If we can get the device context of the desktop to be captured, we can just blit those contents to our application defined device context in the normal way. And getting the device context of the desktop is pretty straightforward if we know its window handle – which can be achieved through the function GetDesktopWindow(). Thus, the steps involved are:

  1. Acquire the Desktop window handle using the function GetDesktopWindow();
  2. Get the DC of the desktop window using the function GetDC();
  3. Create a compatible DC for the Desktop DC and a compatible bitmap to select into that compatible DC. These can be done using CreateCompatibleDC() and CreateCompatibleBitmap(); selecting the bitmap into our DC can be done with SelectObject();
  4. Whenever you are ready to capture the screen, just blit the contents of the Desktop DC into the created compatible DC – that’s all – you are done. The compatible bitmap we created now contains the contents of the screen at the moment of the capture.
  5. Do not forget to release the objects when you are done. Memory is precious (for the other applications).

Example

Void CaptureScreen()
{
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    HWND hDesktopWnd = GetDesktopWindow();
    HDC hDesktopDC = GetDC(hDesktopWnd);
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 
                            nScreenWidth, nScreenHeight);
    SelectObject(hCaptureDC,hCaptureBitmap); 
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); 
    SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
                                //here to save the captured image to disk
    ReleaseDC(hDesktopWnd,hDesktopDC);
    DeleteDC(hCaptureDC);
    DeleteObject(hCaptureBitmap);
}

In the above code snippet, the function GetSystemMetrics() returns the screen width when used withSM_CXSCREEN, and returns the screen height when called with SM_CYSCREEN. Refer to the accompanying source code for details of how to save the captured bitmap to the disk and how to send it to the clipboard. Its pretty straightforward. The source code implements the above technique for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

And the DirectX way of doing it

Capturing the screenshot with DirectX is a pretty easy task. DirectX offers a neat way of doing this.

Every DirectX application contains what we call a buffer, or a surface to hold the contents of the video memory related to that application. This is called the back buffer of the application. Some applications might have more than one back buffer. And there is another buffer that every application can access by default – the front buffer. This one, the front buffer, holds the video memory related to the desktop contents, and so essentially is the screen image.

By accessing the front buffer from our DirectX application, we can capture the contents of the screen at that moment.

Accessing the front buffer from the DirectX application is pretty easy and straightforward. The interfaceIDirect3DDevice9 provides the GetFrontBufferData() method that takes a IDirect3DSurface9 object pointer and copies the contents of the front buffer onto that surface. The IDirect3DSurfce9 object can be generated by using the method IDirect3DDevice8::CreateOffscreenPlainSurface(). Once the screen is captured onto the surface, we can use the function D3DXSaveSurfaceToFile() to save the surface directly to the disk in bitmap format. Thus, the code to capture the screen looks as follows:

extern IDirect3DDevice9* g_pd3dDevice;
Void CaptureScreen()
{
    IDirect3DSurface9* pSurface;
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
    pSurface->Release(); 
}

In the above, g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized. This code snippet saves the captured image onto the disk directly. However, instead of saving to disk, if we just want to operate on the image bits directly – we can do so by using the method IDirect3DSurface9::LockRect(). This gives a pointer to the surface memory – which is essentially a pointer to the bits of the captured image. We can copy the bits to our application defined memory and can operate on them. The following code snippet presents how the surface contents can be copied into our application defined memory:

extern void* pBits;
extern IDirect3DDevice9* g_pd3dDevice;
IDirect3DSurface9* pSurface;
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 
                                          &pSurface, NULL);
g_pd3dDevice->GetFrontBufferData(0, pSurface);
D3DLOCKED_RECT lockedRect;
pSurface->LockRect(&lockedRect,NULL,
                   D3DLOCK_NO_DIRTY_UPDATE|
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();

In the above, pBits is a void*. Make sure that we have allocated enough memory before copying into pBits. A typical value for BITSPERPIXEL is 32 bits per pixel. However, it may vary depending on your current monitor settings. The important point to note here is that the width of the surface is not same as the captured screen image width. Because of the issues involved in the memory alignment (memory aligned to word boundaries are assumed to be accessed faster compared to non aligned memory), the surface might have added additional stuff at the end of each row to make them perfectly aligned to the word boundaries. The lockedRect.Pitch gives us the number of bytes between the starting points of two successive rows. That is, to advance to the correct point on the next row, we should advance by Pitch, not by Width. You can copy the surface bits in reverse, using the following:

for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 
        ScreenWidth * BITSPERPIXEL/8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth* BITSPERPIXEL/8);
}

This may come handy when you are converting between top-down and bottom-up bitmaps.

While the above technique of LockRect() is one way of accessing the captured image content onIDirect3DSurface9, we have another more sophisticated method defined for IDirect3DSurface9, the GetDC()method. We can use the IDirect3DSurface9::GetDC() method to get a GDI compatible device context for the DirectX image surface, which makes it possible to directly blit the surface contents to our application defined DC. Interested readers can explore this alternative.

The sample source code provided with this article implements the technique of copying the contents of an off-screen plain surface onto a user created bitmap for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

However, a point worth noting when using this technique for screen capture is the caution mentioned in the documentation: The GetFrontBufferData() is a slow operation by design, and should not be considered for use in performance-critical applications. Thus, the GDI approach is preferable over the DirectX approach in such cases.

Windows Media API for capturing the screen

Windows Media 9.0 supports screen captures using the Windows Media Encoder 9 API. It includes a codec namedWindows Media Video 9 Screen codec that has been specially optimized to operate on the content produced through screen captures. The Windows Media Encoder API provides the interface IWMEncoder2 which can be used to capture the screen content efficiently.

Working with the Windows Media Encoder API for screen captures is pretty straightforward. First, we need to start with the creation of an IWMEncoder2 object by using the CoCreateInstance() function. This can be done as:

IWMEncoder2* g_pEncoder=NULL; 
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncoder2,(void**)&g_pEncoder);

The Encoder object thus created contains all the operations for working with the captured screen data. However, in order to perform its operations properly, the encoder object depends on the settings defined in what is called a profile. A profile is nothing but a file containing all the settings that control the encoding operations. We can also create custom profiles at runtime with various customized options, such as codec options etc., depending on the nature of the captured data. To use a profile with our screen capture application, we create a custom profile based on the Windows Media Video 9 Screen codec. Custom profile objects have been supported with the interfaceIWMEncProfile2. We can create a custom profile object by using the CoCreateInstance() function as:

IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncProfile2,(void**)&g_pProfile);

We need to specify the target audience for the encoder in the profile. Each profile can hold multiple number of audience configurations, which are objects of the interface IWMEncAudienceObj. Here, we use one audience object for our profile. We create the audience object for our profile by using the methodIWMEncProfile::AddAudience(), which would return a pointer to IWMEncAudienceObj which can then be used for configurations such as video codec settings (IWMEncAudienceObj::put_VideoCodec()), video frame size settings (IWMEncAudienceObj::put_VideoHeight() and IWMEncAudienceObj::put_VideoWidth()) etc. For example, we set the video codec to be Windows Media Video 9 Screen codec as:

extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2') 
    //MSS2 is the fourcc for the screen codec

long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
    &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);

The fourcc is a kind of unique identifier for each codec in the world. The fourcc for the Windows Media Video 9 Screen codec is MSS2. The IWMEncAudienceObj::put_VideoCodec() accepts the profile index as the input to recognize a particular profile – which can be obtained by using the method IWMEncProfile::GetCodecIndexFromFourCC().

Once we have completed configuring the profile object, we can choose that profile into our encoder by using the method IWMEncSourceGroup :: put_Profile() which is defined on the source group objects of the encoder. A source group is a collection of sources where each source might be a video stream or audio stream or HTML stream etc. Each encoder object can work with many source groups from which it get the input data. Since our screen capture application uses only a video stream, our encoder object need to have one source group with a single source, the video source, in it. This single video source needs to configured to use the Screen Device as the input source, which can be done by using the method IWMEncVideoSource2::SetInput(BSTR) as:

extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");

The destination output can be configured to save into a video file (wmv movie) by using the methodIWMEncFile::put_LocalFileName() which requires an IWMEncFile object. This IWMEncFile object can be obtained by using the method IWMEncoder::get_File() as:

IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);

Now, once all the necessary configurations have been done on the encoder object, we can use the methodIWMEncoder::Start() to start capturing the screen. The methods IWMEncoder::Stop() andIWMEncoder::Pause might be used for stopping and pausing the capture.

While this deals with full screen capture, we can alternately select the regions of capture by adjusting the properties of input video source stream. For this, we need to use the IPropertyBag interface of the IWmEnVideoSource2 object as:

#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );

The accompanied source code implements this technique for capturing the screen. One point that might be interesting, apart from the nice quality of the produced output movie, is that in this, the mouse cursor is also captured. (By default, GDI and DirectX are unlikely to capture the mouse cursor).

Note that your system needs to be installed with Windows Media 9.0 SDK components to create applications using the Window Media 9.0 API.

To run your applications, end users must install the Windows Media Encoder 9 Series. When you distribute applications based on the Windows Media Encoder SDK, you must also include the Windows Media Encoder software, either by redistributing Windows Media Encoder in your setup, or by requiring your users to install Windows Media Encoder themselves.

The Windows Media Encoder 9.0 can be downloaded from:

Conclusion

All the variety of techniques discussed above are aimed at a single goal – capturing the contents of the screen. However, as can be guessed easily, the results vary depending upon the particular technique that is being employed in the program. If all that we want is just a random snapshot occasionally, the GDI approach is a good choice, given its simplicity. However, using Windows Media would be a better option if we want more professional results. One point worth noting is, the quality of the content captured through these mechanisms might depend on the settings of the system. For example, disabling hardware acceleration (Desktop properties | Settings | Advanced | Troubleshoot) might drastically improve the overall quality and performance of the capture application.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

How can I return a string from a C dll to C#?

// c code:
#include <ole2.h> /* needed for CoTaskMemAlloc */
EXPORT char* getUrlFromBaseURLInList(char *ListFilePath, char *SrchBaseURL, char* strBuffer)
{
    static const char[] sTest =  "Test My String";
    /* you must use CoTaskMemAlloc to allocate the memory, not malloc, new, or anything else */
    char* returnedString = CoTaskMemAlloc(sizeof(sTest));
    strcpy(returnedString, sTest);
    return returnedString;
}

// c# code:
[DllImport("SLSSeoUrlFunc.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
public static extern string getUrlFromBaseURLInList(byte[] ListFilePath, byte[] sSrchUrl);

string myTestString = SLSSeoUrlFuncDllWrap.getUrlFromBaseURLInList(null, null);
Console.WriteLine("Eureka - {0}", myTestString);

解决 error C1083: 无法打开预编译头文件xxx.pch: No such file or directory

问题产生:

通常情况下不会出现这种错误,我出现这样的错误提示是因为,我要发布的是一个静态库,于是把要发布的头文件放到了include目录下,把其他文件(包括stdAfx.h及stdAfx.cpp)放到了src目录下,于是编译时提示:

错误 1 error C1083: 无法打开预编译头文件:“Debug\PlotData.pch”: No such file or directory e:\mcp prj\tmdataplotting\plotdata\src\stdafx.cpp 5 1 PlotData

 

解决办法:

在解决方案资源管理器中:

1、在stdafx.cpp上右键–>属性

2、弹出的stdafx.cpp属性框中,选择配置属性–>C/C++–>预编译头

3、在预编译头中选择“创建”,而不是原来默认的“使用”

4、其他cpp文件默认“使用”就OK了

 

 PS:

预编译头.pch文件是咋回事?

编译器一般都是以文件为单位进行编译,如果修改了工程中的一个文件,那么将导致所有文件都要从新编译,这样的编译将耗费很长时间。
为了提高编译速度,将那些不常被修改,比较稳定,文件单独包含到一个指定的头文件中, 然后生成一个预编译头文件 *.pch 。

VC中默认的头文件为 stdAfx.h, 但光有头文件无法编译,所以还要用到 StdAfx.cpp里只包含一句有效代码,这样编译时,通过编译 stdAfx.cpp就把大部分系统头文件编译进来, Debug目录下便会产生一个 存储了预编译信息的 *.pch 文件。
如果 *.pch文件损坏或者不存在,就会出现,题目所示的无法打开预编译头文件的错。

 

可见,你也可以在项目属性中选择“不使用预编译头”,但是编译大项目时可能会很慢

 

 

临界区,互斥量,信号量,事件的区别

四种进程或线程同步互斥的控制方法

1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

临界区(Critical Section)

保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线 程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操 作共享资源的目的。
临界区包含两个操作原语:
EnterCriticalSection() 进入临界区
LeaveCriticalSection() 离开临界区
EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的 LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本 进程内的线程,而不可用来同步多个进程中的线程。
MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是 非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代 码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。

互斥量(Mutex)

互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程 所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同 一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。

互斥量包含的几个操作原语:
CreateMutex() 创建一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象

同样MFC为互斥量提供有一个CMutex类。使用CMutex类实现互斥量操作非常简单,但是要特别注意对CMutex的构造函数的调用
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)
不用的参数不能乱填,乱填会出现一些意想不到的运行结果。

信号量(Semaphores)

信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程 最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量 时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数 就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目, 不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可 用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。
P操作 申请资源:
(1)S减1;
(2)若S减1后仍大于等于零,则进程继续执行;
(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
V操作 释放资源:
(1)S加1;
(2)若相加结果大于零,则进程继续执行;
(3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。

信号量包含的几个操作原语:
CreateSemaphore() 创建一个信号量
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量

事件(Event)

事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
信号量包含的几个操作原语:
CreateEvent() 创建一个信号量
OpenEvent() 打开一个事件
SetEvent() 回置事件
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects()         等待多个事件
WaitForMultipleObjects 函数原型:
WaitForMultipleObjects(
IN DWORD nCount, // 等待句柄数
IN CONST HANDLE *lpHandles, //指向句柄数组
IN BOOL bWaitAll, //是否完全等待标志
IN DWORD dwMilliseconds //等待时间

参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核 对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。 dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT。

总结
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使 用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但 对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和 线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根 据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数 器。

转自:http://space.itpub.net/10697500/viewspace-612045

 

【转】Windows上调用Cygwin编译的函数库

2012-04-05 19:22
转载自 kaien_space

本篇介绍的是静态库函数。静态库扩展名是.lib(windows上)或.a(linux上),他和动态库(dll)是有区别的。

调用静态库编译后会写入执行程序中。然后就可以独立运行了。

动态库旨在动态调用,调用的时候需要加载dll才能正常工作。

(所以动态库往往可以提供补丁,或功能升级的时候使用,但是运行的速度有待商协)

另外,两个库的编译器也不一样,例如mingw用g++.exe生成动态库.dll, 用ar 生成静态库.a

而VC则一律用link.exe生成生成动态和静态库,用options来区别生成哪种。

 

本文只讲静态库.dll和.a 的例子。动态库其实很类似。

我们的目的是,把若干的C++和C文件在Cygwin上编译成一个静态库函数文件。

然后在Windows上调用这个库函数。

举个简单的例子:

我们有两个库函数文件.cpp或.c

1. myf1.cpp

#include <stdio.h>

void f1_Fonction1(int a, double b, char *c)
{
printf(“调用文件myf1.cpp的f1_Fonction1成功\n”);
}
int f1_Fonction2(int c)
{
printf(“调用文件myf1.cpp的f1_Fonction2成功\n”);
return c+1;
}

2. myf2.c

#include <stdio.h>

void f2_Fonction1(void)
{
printf(“调用文件myf2.c的f2_Fonction1成功\n”);
}
int f2_Fonction2(int c)
{
printf(“调用文件myf2.c的f2_Fonction2成功\n”);
return c+2;
}

上面两个文件一个是C++的,另一个是C的。

两个文件各提供了两个函数。

现在我们在Cygwin上编译他们为库函数mylib.a 或mylib.lib (扩展名不重要)

> gcc -c myf1.cpp myf2.c

由于我们这里的函数都是C标准格式,所以我们还使用g++, c++或cc等编译。选用任意一种都可以编译通过。

(通常情况下,选哪个编译器和你的C++代码的编写用语格式有关,每个编译器都有其特殊的支持格式)

上面的命令编译生成两个文件myf1.o和myf2.o

接着,我们使用ar生成静态库

> ar r mylib.lib myf1.o myf2.o

这样,我们就在cygwin上生成了一个静态库mylib.lib

现在,我们尝试在windows上调用这个库中的一个函数试试看

写一个简单的调用的C++程序main.cpp

#include <iostream>
using namespace std;

void f1_Fonction1(int a, double b, char *c);

int main()
{
char c;

f1_Fonction1(1,2.0,&c); //调用了myf1里面的函数f1_Fonction

cout << “Hello world!” << endl;
return 0;
}

现在,在window上用MinGW编译。

首先生成main.o文件

> gcc -c main.cpp

接着把main.o和函数库mylib.lib连接起来生成main.exe文件

> g++ -o main.exe main.o mylib.lib

注意link的顺序。函数库必须在主函数后面。另外,因为main.cpp是C++格式,所以link的时候要用g++编译器。gcc编译器会出现没有定义std::cout 这类的错误信息。

另外一个值得注意的是,虽然VC的.lib和Linux的.a格式是相同,都是一种归档文件格式。但是由于VC编译的程序中函数列表需要符号索引 (如果你用 reimp -s mylib.lib 察看VC的lib文件就会发现,函数名都被加了冗长的前后缀),所以vc的lib似乎无法直接在gcc上连接通过,同样的gcc的.a似乎也无法在vc上使用。虽然也有些文章讨论过lib和a的转换问题,配合使用reimp和dlltool命令转换。但是我的测试中发现转换后的库文件还是无法调用。如果有人成功的实现vc和gcc的静态库互换,请务必留言告诉我具体方法。

UTF8与GBK字符编码之间的相互转换

[code lang=”c”]
string GBKToUTF8(const std::string& strGBK)
{
string strOutUTF8 = "";
WCHAR * str1;
int n = MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, NULL, 0);
str1 = new WCHAR[n];
MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, str1, n);
n = WideCharToMultiByte(CP_UTF8, 0, str1, -1, NULL, 0, NULL, NULL);
char * str2 = new char[n];
WideCharToMultiByte(CP_UTF8, 0, str1, -1, str2, n, NULL, NULL);
strOutUTF8 = str2;
delete[]str1;
str1 = NULL;
delete[]str2;
str2 = NULL;
return strOutUTF8;
}
[/code]

2、将UTF8转换成GBK

[code lang=”c”]

string UTF8ToGBK(const std::string& strUTF8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, NULL, 0);
unsigned short * wszGBK = new unsigned short[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUTF8.c_str(), -1, wszGBK, len);

len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char *szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP,0, wszGBK, -1, szGBK, len, NULL, NULL);
//strUTF8 = szGBK;
std::string strTemp(szGBK);
delete[]szGBK;
delete[]wszGBK;
return strTemp;
}

[/code]

 

转自:中国勇士-X

http://www.cppblog.com/zgysx/archive/2006/09/28/13085.aspx

关于#pragma pack与sizeof()

本文来自:http://www.cnblogs.com/whu-gy/archive/2008/05/04/1182365.html
这是讲的最清楚明了的了,必须要收藏 :)
—khler

对于结构体,在使用sizeof的时候会进行字节的对齐,对齐的规则如下:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding)
备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。
备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
按照上面的规则,对如下几个例子进行分析:
例1:

[code lang=”c”]
struct DATA1
{
char c1; //偏移量0,累积size = 1
char c2; //偏移量1,累积size = 1 + 1 = 2
short si; //偏移量2,累积size = 2 + 2
};
[/code]

这个例子中,首先找到结构体变量的首地址,按照规则1,该首地址需要能被最宽基本类型成员整除,即能被short(字节数2)整除。接下来,为c1分配内存,由于其占一个字节,此时内存状态为*,然后,为c2分配内存,由于结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,而c2是char型成员,占用一个字节,刚好能够对齐,此时内存状态为**,最后,为short型成员分配内存,由于前两个成员占2个字节,si相对于结构体首地址的偏移量刚好是2的整数倍,所以,此时内存状态为****

例2:

[code lang=”c”]
struct DATA2
{
char c1; //偏移量0,累积size = 1
short si; //偏移量1 + (1),累积size = 1 + (1) + 2 = 4
char c2; //偏移量4,累积size = 4 + 1 = 5,但按最大长度sizeof(short) = 2对齐,故最后取6
};
[/code]

这个例子中,首先找到结构体变量的首地址,与上例同。接下来,为c1分配内存,由于其占一个字节,此时内存状态为*,然后,为si分配内存,由于结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,而si是short型成员,占用2个字节,而前一个成员占用一个字节,所以需要在c1与si之间补一个字节进行对齐,此时内存状态为*×**,最后,为char型成员分配内存,由于前两个成员占4个字节,c2相对于结构体首地址的偏移量是1的整数倍,所以,此时内存状态为*×***,接下来,按照规则3,结构体的总大小为结构体最宽基本类型成员大小的整数倍,所以总大小应该是short(2字节)型变量大小的整数倍,需要再补一个字节,即*×***×,总共占6个字节

例3:

[code lang=”c”]
struct DATA3
{
char c1; //偏移量0,累积size = 1
double d; //偏移量1 + (7),累积size = 1 + (7) + 8 = 16
char c2; //偏移量16,累积size = 16 + 1 = 17,但按最大长度sizeof(double) = 8对齐,故最后取24
};
[/code]

关于#pragma pack(n)

VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。

n字节对齐就是说变量存放的起始地址的偏移量有两种情况:

第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,

第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

结构的总大小也有个约束条件,分下面两种情况:

如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

否则必须为n的倍数。

例4:

[code lang=”c”]
#pragma pack(push) //保存对齐状态
#pragma pack(2)//设定为2字节对齐
struct DATA4
{
char c1;
double d1;
int i1;
short s1;
};
#pragma pack(pop)//恢复对齐状态
[/code]

以上结构的大小为16,下面分析其存储情况,首先为c1分配空间,其偏移量为0,c1占用1个字节,由于1<2,所以按照char类型变量默认的对其方式。接着开始为d分配空间,这时其偏移量为1,需要补足1个字节,这样使偏移量满足为n=2的倍数(因为sizeof(double)大于n),d占用8个字节。接着为i1分配空间,这时其偏移量为10,满足为2的倍数,i1占用4个字节。这时已经为所有成员变量分配了空间,共分配了14个字节。 接下来再为s1分配空间,由于min(sizeof(s1),2)=2,所以s1按照2字节对齐,此时已经是对其的,所以分配2字节内存,这时,总共占用16字节

例5:

[code lang=”c”]
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct DATA4
{
char c1;
double d;
int i1;
short s1;
};
#pragma pack(pop)//恢复对齐状态
[/code]

以上结构的大小为20,下面分析其存储情况,首先为c1分配空间,其偏移量为0,c1占用1个字节,由于1<2,所以按照char类型变量默认的对其方式。接着开始为d分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),d占用8个字节。接着为i1分配空间,这时其偏移量为12,满足为4的倍数,i1占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节。接下来再为s1分配空间,由于min(sizeof(s1),4)=2,所以s1按照2字节对齐,此时已经是对齐的,所以分配2字节内存,这时,总共占用16字节,由于占用内存总数需要为4的整数倍,所以结构大小为20

【参考文献】
1.《对几组sizeof信息的分析》 http://blog.vckbase.com/billdavid/archive/2004/06/23/509.html
2.《sizeof(结构体)和内存对齐》http://www.ksarea.com/articles/20071004_sizeof-struct-memory.html

串口通讯简单回顾

最近几个小项目都是与串口有关的,前段时间用C++实现了一个串口通讯类,高效稳定,效果还不错;后来由于一个同事用C#的SerialPort类与设备通讯时效率很低,让我帮他自己包装了一个C#的Comm通讯类。

串口通讯中对串口的设置基本上很简单,大部分时候只需要设置串口号和波特率、数据位、停止位,其他的很少改动,默认就行了。

在使用C#与设备通讯时,发现一个比较贵的USB转串口线通讯速度不稳定,至少前面几分钟速度会非常的慢,之后就恢复正常了。但是用sscom32调试助手看却很快,所以怀疑打开串口的时候是不是对其他几个参数的设置上的区别导致通讯速度的不同?更奇怪的是另一根相当便宜的USB转串口线,在任何情况下速度都很正常,汗……

简单讲解

串口是计算机上一种非常通用的通信设备,大多数计算机包含两个基于RS232的串口,由于其使用简单造价低廉而被普遍应用。

奇怪的是近几年出产的笔记本和PC机基本都没有串口这种设备了,而简单易用、成本低廉的特性,让很多硬件设计工程师还是选择了串口作为设备与其他设备的通讯方式。因此,usb串口转换线使用的非常多,它的RS232-485接口连接串口设备,另一端转换为USB接入PC等上位机,在上位机上模拟出多个串口,以实现PC机通过串口与外围串口设备通讯。

串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。而IEEE488定义规定,对于并行通讯,设备线总常不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。

典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。

串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配。

高深一点,通讯过程中的硬件知识

由于 CPU 与接口之间按并行方式传输,接口与外设之间按串行方式传输,因此,在串行接口中,必须要有“接收移位寄存器” (串→并)和 “发送移位寄存器 ”(并→串)。在数据输入过程中,数据 1 位 1 位地从外设进入接口的 “接收移位寄存器” ,当“接收移位寄存器”中已接收完 1 个字符的各位后,数据就从“接收移位寄存器”进入“数据输入寄存器”。CPU 从“数据输入寄存器”中读取接收到的字符。(并行读取,即 D7~D0 同时被读至累加器中)。“接收移位寄存器”的移位速度由“ 接收时钟”确定。

在数据输出过程中,CPU 把要输出的字符(并行地)送入“数据输出寄存器”,“数据输出寄存器”的内容传输到“发送移位寄存器”,然后由“发送移位寄存器”移位,把数据 1 位 1 位地送到外设。“发送移位寄存器”的移位速度由“发送时钟”确定。

接口中的“控制寄存器”用来容纳 CPU 送给此接口的各种控制信息,这些控制信息决定接口的工作方式。

“状态寄存器”的各位称为“状态位”,每一个状态位都可以用来指示数据传输过程中的状态或某种错误。例如,用状态寄存器的 D5 位为 ‘1’ 表示“数据输出寄存器”空,用 D0 位表示“数据输入寄存器满”,用 D2 位表示“奇偶检验错”等。

实际上,上面所说的”串→并”转换的功能由一块叫UART(Universal Asynchronous Receiver and Transmitter)的芯片完成,典型的芯片有: Intel 8250/8251,16550。

从电气特性上说,串口就使用接收线和发送线传输数据。无论是发送还是接收一个逻辑1,发送或者接收线被置成了低电压,相反,如果是逻辑0,则被置成了高电压。当传输一个字节时,UART首先发送一个‘开始位’,为逻辑0,即,高电压,然后是数据位(通常是8位,当然也可以设置成5、6或者7位,上位机通常以一个字节为单位来处理串口数据的,所以,如果不想给自己找麻烦,或者不想被人看的很高深,还是老老实实用默认的8位吧:-)。PS:前两天用串口通讯,发现接收到的数据总是不大于127,原因就是在用C#定义DCB结构体时把几个flag定义错了,导致覆盖到了数据位的标志位为7,所以肯定完蛋,把第8为当成了下一个字节的起始标示位了),最后接着来的是1位或者两位停止位,是逻辑1,即低电平。这样,一个字节就传输出去了,其它字节依次周而复始。

上个图给你look look,应该就很形象了:

流控制:XON/XOFF

到这里就有点复杂了,一般情况下没必要了解,如果很好奇,那就看看,毕竟了解的越多,动起手来才得心应手!

下面的文字来自网络,讲的很清楚,直接摘了:
XON/XOFF 是一种流控制协议(通信速率匹配协议),用于数据传输速率大于等于1200b/s时进行速率匹配,方法是控制发送方的发速率以匹配双方的速率。
XMODEM是一种低速文件传输协议。KERMIT是异步通信环境中使用的一种文件传输协议。它与XMODEM的主要区别是:KERMIT一次可传送多个文件,而XMODEM一次只能传送一个文件;KERMIT在接收方以完整的信息包应答,而XMODEM以单字节应答;KERMIT提供多种错误校验技术,而XMODEM只提供一种错误校验技术。
在流量控制方面,可以从不同侧面采取不同的控制方案。最简单的方法就是增加接收端的缓冲存储空间,使得接收端可以缓存更多的数据。但这只是一种被动、消极的方法。因为,一方面系统不允许开设过大的缓冲空间;另一方面对于速率显著失配,并且又传送大量数据的场合,即使缓存空间再大也会出现不够的现象。目前普遍采取一种称之为“XON/XOFF”的发送控制字符的方案,通过控制字符来确定发送方是否继续发送数据,相比之下更主动、更积极、更有效。
XON/XOFF(继续/停止)是异步串行连接的计算机和其他元件之间的数据流控制协议。例如,计算机向打印机发送数据的速度通常快于打印机打印的速度,打印机包含一个缓冲器,用来存储数据,使打印机能够赶上计算机。如果在打印机赶上之前缓冲器变满了,打印机的小微处理器便发回一个XOFF信号来停止数据传送,打印完相当多的数据,缓冲存储器变空时,打印机发送XON信号,让计算机继续发送数据。“X”表示“发送器”,X/ON和X/OFF为开启和关闭发送器的信号。X/ON的实际信号为ASCII的Ctrl-Q键盘组合的位组合,X/OFF信号为Ctrl-S字符。在为计算机操作系统定义调制解调器时,可能需要用XON/XOFF或CTS/RTS来指定流控制的使用。在发送二进制数据时,XON/XOFF可能不能识别,因为它被译成了字符。
XON/XOFF是一种异步通信协议,接收设备或计算机使用特殊字符来控制发送设备或计算机传送的数据流。当接收计算机不能继续接收数据时,发送一个XOFF控制字符告诉发送方停止传送;当传输可以恢复时,该计算机发送一个XON字符来通知发送方。其中XON采用ASCII字符集中的控制字符DC1,XOFF采用ASCII字符集中的控制字符DC3。当通信线路上的接收方发生过载时,便向发送方发送一个XOFF字符,发送方接收XOFF字符后便暂停发送数据;等接收方处理完缓冲器中的数据,过载恢复后,再向发送方发送一个XON字符,以通知发送方恢复数据发送。在一次数据传输过程中,XOFF、XON的周期可重复多次,但这些操作对用户来说是透明的,也就是说用户不用管它。
许多异步数据通信软件包均支持XON/XOFF协议。这种方案也可用于计算机向打印机或其他终端设备(如MODEM的串行通信)发送字符,在这种情况下,打印机或终端设备中的控制部件用以控制字符流量。

MFC中位图按钮的实现

用了4天搞定一个智能工厂的短信猫控制数据采集设备的程序,重用一回MFC,感觉还是相当爽的,有种老朋友重逢的感觉:)

由于短信猫自带的SDK写的垃圾了点,所以我直接使用AT指令搞定了;

界面基本就是一些位图,通过贴图模拟现场设备的样子;

界面背景、Label,Edit的背景修改都还好说,就是按钮有些特殊,所以需要特殊处理一下,我用的当然也是现成CBitmapButton按钮类——它其实就是简单的从CButton继承了一下,把一些绘制工作子类化了,使用注意事项和其他知识,网上找找,有很多资料,一下是网上找的一些使用资料:

方法一:
1、在资源编辑时,要设置按钮的Owner draw 属性,不需要选择 Bitmap 属性.在按钮上右键,选择属性,然后–>Styles–>Owner draw2、在程序中定义一个CBitmapButton成员变量。需要注意的是,不能使用ClassWizard为按钮映射一个CButton控件变量,然后改类型为CBitmapButton,这么做并不能将按钮直接映射为CBitmapButton类的对象,反而会出现初始化错误。3、使用CBitmapButton::LoadBitmaps装载各种状态(有四种状态)的图片,
使用CBitmapButton::SubclassDlgItem关联到想要控制的按钮,
使用CBitmapButton::SizeToContent函数使按钮适合图片大小。

【注意:Loadbitmaps一定要在关联到按钮之前进行!】

————————————-例子如下—————————————————————–:
首先,选中按钮的owner Draw属性,然后在资源中添加四副*.bmp格式的位图,
然后在对话框类中加入相关代码:

[code lang=”c”]
CBitmapButton m_btnX1;
//定义变量,必须放在函数外面才能正常实现
BOOL CXXXXXXX::OnInitDialog()
{
CDialog::OnInitDialog();
//装载位图
m_btnX1.LoadBitmaps(IDB_XXXX_UP,IDB_XXXX_DOWN,IDB_XXXX_FOCUS,IDB_XXXX_DISABLED);
//这里是四副位图,鼠标按下和弹起,获得焦点,不可用状态,
//可以加入四副中的任意几副;
m_btnX1.SubclassDlgItem(IDC_DASEN_X1,this);//关联按钮控件
m_btnX1.SizeToContent();
}
[/code]

方法二:使用CBitmapButton::AutoLoad函数关联到想要的按钮
需要注意:
A、使用CBitmapButton::AutoLoad函数之前不能使用CBitmapButton::LoadBitmaps装载各种状态的图片,否则会出错。
B、AutoLoad函数完成关联和改变按钮大小的功能。等价于上面CBitmapButton::SubclassDlgItem和CBitmapButton::SizeToContent函数的功能。
C、CBitmapButton::AutoLoad使用的位图是默认资源ID的,即它会自动装载相关资源位图。位图的资源ID格式为:”按钮Caption+U”、”按钮Caption+D”、”按钮Caption+F”、”按钮Caption+X”,分别代表Up、Down、Focus、Disable状态。如资源编辑时,希望关联的按钮的Caption为Test,那么其默认装载的位图资源的ID:”TestU”/”TestD”/”TestF”/”TestX”,
尤其要注意:分号””也是其资源ID的一部分。
————————————————-例子:——————————————–
首先,选中Caption为:”myBitmapButton”按钮的owner
Draw属性
其次,添加两个位图资源,并修改其ID分别为:”myBitmapButtonU”和”myBitmapButtonD”,这里只添加两个位图
然后在对话框类中加入:
[code lang=”c”]
CBitmapButton m_btnX1;
BOOL CXXXXXXX::OnInitDialog()
{
CDialog::OnInitDialog();m_btnX1.AutoLoad(IDC_BUTTON1,this);//ID为IDC_BUTTON1的按钮的Caption为:myBitmapButton
}

[/code]

 

MFC中跨线程UpdateData(FALSE)报错 ASSERT FAILD问题

MFC程序,在给控件关联的变量赋值后,需要调用UpdateData(FALSE)后才能更新到界面,但是有时候却报错:

断言宏失败,在wincore.cpp的如下位置:
  CObject* p=NULL;
  if(pMap)
  {
   ASSERT( (p = pMap->LookupPermanent(m_hWnd)) != NULL ||
     (p = pMap->LookupTemporary(m_hWnd)) != NULL);
  }
  ASSERT((CWnd*)p == this);   // must be us

这个问题其实是由于跨线程访问UI元素导致的,微软在MSDN中做了如下描述:

In a multi-threaded application written using MFC, you should not pass MFC objects across thread boundaries. As a general rule, a thread should access only those MFC objects that it creates. Failure to do so may cause run-time problems including assertions or unexpected program behavior.

 

也就是说,MFC的UI线程是线程相关的,每个窗口的HandleMap是储存在创建UI那个线程的堆栈里面的(thread-local-storage (TLS) ),所以,你要是在另一个线程里面通过某种方式调用UI的UpdateData(FALSE)函数,他将无法正确执行。

知道了原因就好办事了,下面提供两种解决方法:

1、通过GetDlgItem(IDC_XXX)取得控件后SetWindowText()

    这种方式之所以能得逞,是因为调用SetWindowText 会导致 WM_SETTEXT被发送给目标窗体,由消息机制负责处理
    ctrlDlg->GetDlgItem(IDC_EDIT19)->SetWindowText(A2W(pData));

2、自定义一个消息,将Update消息发送到UI线程,在UI线程的对话框中处理消息,自己执行UpdateData(FALSE)函数的调用。

VC MFC程序,在About对话框中获取并显示程序的版本号

 

=================================================
本文为HeYuanHui原作

转载必须确保本文完整并完整保留原作者信息及本文原始链接!

NN:      khler
E-mail: khler@163.com
QQ:     23381103
MSN:   pragmac@hotmail.com
=================================================

    用VC++写的MFC程序,不管是exe的或者dll,都有个’VERSION’资源,在里面可以指定程序的版本号,这样在程序文件上右键点击,查看属性,就可以看到内嵌的版本信息了。同样,所有程序都愿意在’About’对话框中显示程序的当前版本,但是这里如何显示,跟资源里的’VERSION’信息还是有好几毛钱的关系呢 :)

    如果我们从’VERSION’资源里获取版本信息并在about对话框中显示,那么我们每次发布的时候只要修改’VERSION’里的信息就行了,经过一定处理,就可以在about对话框中显示了。

    其实MSDN中也有说明,但是说实话,要想从’VERSION’中读取信息,还真不是件简单的事情,下面一一实现,代码大部分来自MSDN。

    结果如下图所示:

显示1.1.6 build 718

void CAboutDlg::OnShowWindow(BOOL bShow, UINT nStatus)
{
 CDialog::OnShowWindow(bShow, nStatus);

 // TODO: 在此处添加消息处理程序代码

 CString ver = GetAppVersion(L”TowerWatch.exe”);
 if(ver.IsEmpty()) return

;

 

 int pos = ver.ReverseFind(‘.’);
 CString mainVer = ver.Left(pos);
 CString build = ver.Right(ver.GetLength() – pos -1);
 GetDlgItem(IDC_STATIC_VER)->SetWindowText(mainVer);
 GetDlgItem(IDC_STATIC_BUILD)->

SetWindowText(build);

 

}

void ErrorExit(LPTSTR lpszFunction) 

 // Retrieve the system error message for the last-error code

 LPVOID lpMsgBuf;
 LPVOID lpDisplayBuf;
 DWORD dw =

 GetLastError(); 

 

 FormatMessage(
  FORMAT_MESSAGE_ALLOCATE_BUFFER | 
  FORMAT_MESSAGE_FROM_SYSTEM |
  FORMAT_MESSAGE_IGNORE_INSERTS,
  NULL,
  dw,
  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  (LPTSTR) &lpMsgBuf,
  0

, NULL );

 

 // Display the error message and exit the process

 lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
  (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
 StringCchPrintf((LPTSTR)lpDisplayBuf, 
  LocalSize(lpDisplayBuf),
  TEXT(“%s failed with error %d: %s”), 
  lpszFunction, dw, lpMsgBuf); 
 MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT(“Error”

), MB_OK); 

 

 LocalFree(lpMsgBuf);
 LocalFree(lpDisplayBuf);
}

CString CAboutDlg::GetAppVersion(WCHAR*

 AppName) 
{
 CString   AppVersion; 

 

 DWORD   RessourceVersionInfoSize; 
 DWORD   JustAJunkVariabel; 
 WCHAR*   VersionInfoPtr; 
 struct   LANGANDCODEPAGE
 { 
  WORD   wLanguage; 
  WORD   wCodePage; 
 }   *TranslationPtr; 
 WCHAR*     InformationPtr; 
 UINT      VersionInfoSize; 
 WCHAR     VersionValue[255

]; 

 

 RessourceVersionInfoSize=GetFileVersionInfoSize(AppName,&JustAJunkVariabel); 
 if(0!=RessourceVersionInfoSize) 
 {
  VersionInfoPtr = new WCHAR[RessourceVersionInfoSize];
  if(!GetFileVersionInfo(AppName,0,RessourceVersionInfoSize,VersionInfoPtr)) 
  {
    ErrorExit((LPTSTR)L”GetFileVersionInfo”);
   delete[]   VersionInfoPtr; 
   return

 NULL; 
  } 

 

  if(!VerQueryValue( VersionInfoPtr, L”VarFileInfoTranslation”, (LPVOID*)&TranslationPtr, &VersionInfoSize)) 
  { 
   ErrorExit((LPTSTR)L”VerQueryValue”);
   delete[]   VersionInfoPtr; 
   return

 NULL; 
  } 

 

  // retrieve product version
  wsprintf(VersionValue, L”StringFileInfo%04x%04xProductVersion”, TranslationPtr[0].wLanguage, TranslationPtr[0

].wCodePage); 

 

  if(!VerQueryValue( VersionInfoPtr, VersionValue, (LPVOID*)&InformationPtr, &VersionInfoSize)) 
  { 
   ErrorExit((LPTSTR)L”VerQueryValue”);
   delete[]   VersionInfoPtr; 
   return NULL; 
  } 
  if(wcslen(InformationPtr)> 0)   //Not   Null 
  { 
   AppVersion=

CString(InformationPtr); 
  } 

 

  delete[]   VersionInfoPtr; 
 } 
 return   AppVersion; 

当然,你可以将分解Version和build部分代码也封装到GetAppVersion函数中。

如何学好3D游戏引擎编程

此文为转载,值得一看

此篇文章献给那些为了游戏编程不怕困难的热血青年,它的神秘要我永远不间断的去挑战自我,超越自我,这样才能攀登到游戏技术的最高峰   

        ——阿哲VS自己 QQ79134054多希望大家一起交流与沟通

这篇文章是我一年半前,找工作时候写的,那时是发到学校的BBS上。现在我工作了,想法和以前也有不同,但对游戏引擎编程理念还是基本差不多。在我没遇到U3以前,一直研究WILDMAGIC,可以说是GAMEBRYO的前身吧,他们基本框架都差不多,是完全面向对象,代码容易读,但它发展存在严重的弊端,这个也是它为什么不是牛B引擎的原因。但从渲染和动画方面来看,U3的 MATERIAL TREE 和 ANIM TREEE 是2个飞跃的,当我对我自己引擎渲染材质和光照一筹莫展的时候,MATERIAL TREE带来的希望。对比 zfx (wildmagic)gamebryo 和ogre 还有U3,各有优缺点,而且你可以看到一个引擎发展,从一个以多边形为渲染单元,变成以mesh做为渲染单元(现在很少有引擎场景管理分割多边形来做的 我始终喜欢zfx引擎,它是00-02之间的引擎,完全让你可以理解那时候显卡不是很强的时候,一个引擎应该具备的特征。对于架构来讲,(wildmagic)gamebryo 和ogre 做的很好,u3的特点就是架构乱,没有什么可读性的技术文档,研究起来十分吃力。做为一个商业引擎,对于渲染方面,我认为(wildmagic)gamebryo 输给了ogre,框架设计还有弊端,ogre确实是一个好引擎。而U3对于渲染过程,光照,阴影,就是一帧的渲染过程,处理的十分得当。动态生成shader是一个引擎的发展的必然趋势,而往往动态生成最多的,也就是材质和光照,它们不能分开,分开谈无意义,MATERIAL TREE 我始终认为是个太好的想法了,任何特效不需要程序员过多参与,美术可以搞定一切,什么水,反射,折射,这些效果,编写起来易如反掌,还可以实时光看效果。其他动态生成,也就是根据配置不同,来做处理,动态编译shader弊端太大,不能做到所有,而且不灵活,如果动态生成和动态编译合起来,简直牛B透顶了。现在我的引擎已经有了MATERIAL TREE这个功能,ANIM TREEE在制作中。
这里面我重点谈的是图形方面,略带些动画方面,一个引擎最基本的是内核,包含内存管理,文件读写,资源管理等等吧,也十分重要,我的引擎这些写的都很简单,基本都是wildmagic里面的。
我总结3D引擎编程的4个阶段
第一阶段是了解3D原理和数学基础,大学里面学的线性代数,概率统计和高等数学完全足够,在3D中都有充分体现。最好自己能做一个软引擎,这样整个3D流水线自己完全可以掌控,然后在学DX之类的API很容易。
第二阶段不要看引擎,还是应该以实现各种效果和基本算法为基础,包括各种图形特效,场景管理,剪裁算法,各种动画算法,等等,所有那些单一的算法,如果这些都不是了解很好,那么做引擎时引擎框架的接口也不会设计好!再添加这些算法和效果时 会很困难,效率也不会很高。
第三阶段,就是看大量的引擎,对引擎有很深刻的概念,3D引擎只是引擎的一部分,引擎主要是框架,如果框架搭建好了,那么引擎的效率是很显著的,其他底层细节优化,也会有明显的优势体现。
第四阶段,再好的引擎没有成功的游戏支撑都是白费,你不仅仅是做引擎,而是做的有开发价值的引擎,可以很好的为游戏服务的引擎。
下面这段话写于一年半前~~

前言  
本人不才,姑且以自己经验来谈如何学游戏编程,这个话题我酝酿了很久,它确实很难写,因为我也不是资深高手,才刚刚登堂入室,游戏编程大师功力一般都十几年左右,所以让我这样的小卒写的深入浅出更何谈容易,错误出现并不奇怪,高手见笑也在所难免,请大家指正,我会欣然笑纳。但这几年走过的路,让我知道这么学是正确的,大体过程经验告诉我,这样做是对的,高手的指引告诉我,这样做是对的。
这篇文章主要讨论如何学习游戏图形编程和游戏引擎编程,告诉没有接触游戏过编程或者现在关于怎么学好游戏编程还迷惑的PEOPLE所要走的方向,完全抛开技术和代码实现进行谈论。至于游戏逻辑这几年重点没有发到这个上面上,很难总结。
经验的限制,对于如何编写一个好的引擎(至于“好”这个词都是相对而言,能做的满意就是我所期望的),至今为止我还没有这个能力,但我相信经过我的努力,不久的将来,我会实现这个愿望,因为我的一个愿望就是实现一个好的引擎。

我从下面几个方面来说明如果学习,记住只是如何学习,要想学好就要靠你自己。
1。 你真的喜欢游戏编程吗?
2。 要了解游戏编程
3。 学会读书,自学与思考,交流与合作
4。 游戏编程需要的基础知识
5。 2D游戏编程
6。 3D游戏编程
7。 游戏引擎编程
8。浅谈游戏物理和AI

一。游戏编程,你真的爱她吗?
当你听完我的介绍后,你就要决定游戏编程是否适合你。
游戏编程可简单可复杂,简单的2D 《蛇吃豆》,复杂的现在流行的3D游戏。咱们不说简单的,因为你不想学习游戏编程就为了做一个简单的游戏。确实每个人对他所向往的东西都想彻底了解清楚,可是我给你的第一个警告,复杂的3D游戏,复杂到难以想象。

1 你思想需要改变吗?

打开话题,游戏是一个结合体,她是一个音乐,美术,技术,创意相结合的艺术品,你要用艺术的眼光去欣赏的,因为好的游戏是用艺术的眼光来设计,除了某国的网游不算外,很多经典的游戏流芳百世,成为真正游戏玩家佳话。
中国的游戏为什么这么落后,你要联想下,当年中国为什么会被八国联军所侵略。腐朽的思想是根本原因,从而导致了腐朽的社会制度。游戏行业也是这样,看待游戏的中国人,始终带着眼镜,用腐朽思想看待游戏,所以现在出现腐朽的游戏行业,导致中国游戏发展缓慢,有人曾经说中国游戏不久就赶超日本和美国,我认为是扯淡,而且不是一般的扯淡,具体原因后面我会详细说明。
从小的时候,国外的电子游戏产品就跑进了我的空间,废寝忘食的玩个没玩,因为耽误学习,从此电子游戏被老师和家长全部封杀,任何事物都有利有弊,鲁迅的《拿来主义》曾说过“去其糟粕,取其精华”。在中国,电子游戏被视为过街老鼠。确实要承认电子游戏有负面影响,但这些负面影响是谁产生的,本质上是我们自己,最后本来是精华的东西从我们身边擦肩而过,从小霸王以后的国外游戏机没有任何仿造,导致了游戏机技术从此断代,现在却又想起来赶超。
而现在,国人知道努力了,国家看到有钱可赚开始支持了,腐朽的游戏思想却还在蔓延,网络游戏就是这样环境下成长的新生儿,国内网络游戏每年要大批量生产,但存活的基本没几个。不能否定,没有利益的事情没人干,但你还不能否定,有些事情即使没有利益,但还是有人干,哪怕知道自己并不能得到什么。国内的人,很少是第2种人,他们把游戏做为赚钱工具,垃圾游戏隔三差五的纷纷涌出。相比来讲,我只拿日本举例子,虽然小日本和咱们有愁,但我们不得不承认小日本在游戏这方面思想层次完完全全超越我们,他们有严格游戏教育背景,他们做一款游戏,要按照严格的流程,从设计到编程,以至最后的测试,都有着干一行敬一行的精神,否则《生化危机》这样游戏你是永远看不到的。
所以你要改变自己的思想,游戏是一种艺术品,你要有一种艺术境界来去欣赏她。

2 你有毅力爱她爱到底吗?

也许我上面的话,你还能接受,残酷的在下面,以我的经验告诉我,如果你编程和游戏理论起步是零,你要对游戏编程达到略有小成的水平最少要4到5年,我比较蠢,花了6年。这6年来,几乎每天平均学习4-5个小时左右,当然假期都要算上。头3年半基本学习计算机基础知识,包括数学,数据结构,计算机体系结构,人工智能,汇编,编译原理,操作系统,网络,软件工程,离散数学,算法分析。我们大学4年基本就学这东西,这些都是基础,游戏编程都需要,还有一个就高等物理我没有学,导致我现在对于一些物理微积分方程很难理解,当然这里并不一定是问题,只要你肯努力,没有学不会的。完全把这些学好,花4年时间真的不容易。我曾经抱怨自己没保研而痛苦的经历考研,现在我却认为是值得的,我很难有时间在集中精力的复习这些专业课了,尤其是数学。
在游戏编程中,上面的基本知识都可以用到,所以我说游戏编程很复杂一点不假。就拿游戏中最难的部分——一个好的游戏引擎来说(这里说的好引擎),世界上能把她做出来的人很少,中国更是寥寥无几。
上面只是理论基础,要走进游戏编程最基本的。过硬的编程技能更是需要的,其实这些理论学习可以和编程相结合,在编程中学习理论,为了学习理论而增长编程经验。
有了基础,你才能进一步学习游戏编程,2D游戏和3D游戏理论都要学,这又要花费很长时间,这个是我下面要详细说的。
看完我写的,你可能心惊胆寒,所以你要爱她,深深的爱她,否则你承受痛苦的时候享受不到暗含的乐趣。

3 爱她请从游戏开始。

很多人可能不解,游戏编程为什么要从游戏开始,玩游戏时,有很多潜移默化的东西影响着我们编写游戏程序,很多灵感确实从游戏中得来。一般的游戏编程高手,都是从小由于游戏对他们巨大吸引,从而走上游戏道路,虽然不是绝对,但按统计学角度讲可以说是一个事实。一个不爱玩游戏的人,爱上游戏编程的几率是很小的。其实很多人小的时候都爱玩游戏,他们都是从游戏厅张大,只有一少部分,走向了游戏开发这个行业,他们之间有着明显不同,走向游戏开发的人,是真的被游戏背后那种神秘东西所吸引,并想要揭开它,而另一些人,则是完全迎合了游戏开发的需要——玩的高兴。
我想说的是,一个游戏编程高手,必定是一个游戏玩家,他们都是从玩游戏而爱上游戏,从而走上游戏编程道路。这条路充满了荆棘,不是一般人能走到最后的。所以你一定要爱她!
当然任何事情都不绝对的,你可能就是那不爱游戏但爱游戏编程(或者和游戏性无关的那部分编程)小概率的人。

二。游戏编程,你真的了解她吗?

游戏编程,这个概念包含了很多深层次的东西。

1.广义上,我给的定义是,游戏引擎开发和游戏逻辑开发。

游戏逻辑开发是集中力量只开发游戏中剧情部分,你只需要做的是什么时候显示什么,什么时候放出什么声音,什么时候通过网络传输什么数据,什么时候这个物体或者人物做什么动作。至于底层怎么显示,声音如何处理,网络如何传输,物体动作怎么实现的,你不用关心,关心这些的是游戏引擎。所以说游戏引擎负责技术方面,而游戏逻辑负责游戏文化内涵。
成功的游戏是以好的游戏逻辑为基础的,而游戏引擎并不是成功游戏主要条件,但好的游戏配上好的游戏引擎,确实会给人一种震撼,相比同样的游戏逻辑,好的游戏引擎显示效果会给人一种全新视觉和听觉感受。
至于游戏逻辑我不想过多解释,但并不是说游戏逻辑简单,游戏逻辑也可以复杂的要命,这取决与游戏复杂程度和游戏引擎与这个游戏的关联程度。为了解释这句话,我不得不从游戏引擎解释开始。
早期的游戏是没有游戏引擎这个概念,都是一个游戏一套代码,随着游戏越来越多,大家发现开发有些游戏中,有很多可重用的代码,逐渐把这些重用代码封状起来,这就是早期的引擎。对于引擎没有明确的概念,只有好坏和适应程度之分,所谓的好坏,就是引擎支持功能和显示特性等是否strong,而适应程度是指一个引擎是否适合开发这个游戏,开发这个游戏是否要自己再编写一些额外的代码。
我举个例子
如果我用开发混斗罗的引擎(假如它有引擎)来开发RPG,和开发RPG游戏的“RPG游戏开发大师”相比,我们还要做很多工作,才能完成玩法功能一样的游戏。
所以说很难有明确的界限划分这一堆代码是否是游戏引擎,但游戏引擎的任务是不用质疑的,它的任务就是尽量不用负责底层处理,而专心到游戏逻辑上来。

2.从狭义上分,我只分解游戏引擎部分,基本包括(1)游戏图形引擎(2)声音引擎(3)网络引擎(4)脚本引擎(5)GUI(6)人工智能引擎(7)物理引擎。

(6)(7)两个不是必须的,而前5个是一个好的游戏引擎要基本具备的,其实每个游戏引擎中都具备物理引擎,只不过很多游戏对于物理要求不是很高,所以就集成到了图形引擎中或者游戏逻辑中。
图形引擎是引擎中最难的,它基本要处理游戏引擎中70-80%的工作量,它的主要任务是负责图形高效显示,包括速度和精度。后面我会集中并详细解释。至于声音和网络引擎大家通过字面就可以知道它干什么。本人对声音处理和网络传输知道甚少,说出来只能误人子弟。

至于脚本我还略有研究,我想解释下什么是脚本,游戏中为什么用脚本。脚本有时很难给出一个定义,但大家都知道那些是脚本语言,我给出的解释是用软件 CPU(虚拟CPU)来运行的编程语言就是脚本语言,软件CPU和所说硬件CPU是对立的,也就是说脚本运行与否先是关联到这个虚拟CPU,在虚拟CPU上运行,然后再又映射到真正CPU,来真正运行。这个过程很抽象,我觉2个例子,第一个就是JAVA语言,它有一个虚拟机,这个虚拟机就是软件CPU。再举一个例子,我现在要编写这样一个指令、
PRINT S  这个指令表示打印S
S代表一个万能类型。
为了实现这个指令,你首先要有一个函数来负责处理打印
比如是这样的函数
void print(S)
{
    cout<<S;
}
这里只是伪代码,只是表示这个意思。
你首先要找出PRINT这个单词,读入到一个buffer1,然后把参数读入buffer2
if(buffer1 == PRINT)
  print(buffer2);
这里段代码其实就是一个软件CPU,它负责虚拟运行PRINT S  这个指令。

那么游戏中为什么要用脚本呢!最简单解释就是灵活性好。拿一个RPG游戏例子来说,游戏中有大量物品,每个物品有大量的属性,如果我把这些属性值直接写到游戏中,每一个小小的改变就要编译整个游戏,花费很多时间,灵活性很差。
如果我把这些写到一个文件中,用脚本语言来编辑,只需要编译脚本文件就可以了,当然如果你脚本是解释型的语言,不用编译直接可以运行。
当然脚本还有很多好处,主要目的还是方便性和灵活性。
其实GUI(图形用户接口)是一个重要部分。3D游戏中,出现的是大量的3D模型,这些模型是先用文件的形式存放好的,而文件里面包括的是3D数据和这些数据的组织方式。
至于数据怎么组织是你引擎要规定好的,这样也方便读取,最后把3D数据加载到内存。现在问题是3D数据上,我们怎么能有效的得到这些3D模型的数据,手工写进文本里?我靠,那是奴隶社会,SB的做法。我想你不会这么做。人类很聪明,这就是为什么人把其他动物关进笼子里,而不是其他动物把人关进笼子里的原因。既然我们很聪明,就要想一个办法快速的定义3D数据,然后保存成文件。这样一个3D编辑器就产生了。3D编辑器就是类似于3DMAX那种,但引擎中的编辑器是有特殊目的的,使用者要按照引擎指定要求编辑3D数据,这样引擎高效的显示和处理数据比较容易,至于3D数据是怎么组织的,是一个设计上的问题,由你引擎来决定,设计好坏,决定你引擎加载数据是否容易,关系到最后渲染。所以一般好的游戏引擎都有自己GUI,但你也可以用3DMAX这样的软件,然后读3DMAX文件变成自己可以利用的数据形式。
说到这里,不知道上面的概念大家是否理解,我想如果你没接触过引擎,可能真的不理解。其实我很难用简短的语言把这么复杂的问题说清楚,如果有一天你把3D学的融会贯通,你发现我说的是对的。我不知道大家是否发现一个共性的问题,有时你读一本书,无论你怎么认真把它看完一遍,你都很难掌握里面的知识,当你再认真看第二遍的时候,你突然觉得这本书没有第一次看那么晦涩难懂。有些知识都是前后呼应的,它们之间相互联系,要想完整的理解它们缺一不可。游戏编程的知识也是这样。我前面介绍的如果你看懂了,那你perfect,如果你没有看懂,也不要灰心,这里介绍的只是一个大纲,让你对游戏编程有一个了解,最基础层面的了解。

三。游戏编程,你准备好了吗?

这个部分讲解如果你想开始游戏编程应该具备那些基础知识,这样你才能接下来游戏编程学习,当然下面的有些知识,我开始学习游戏编程时,也不是都具备,但这些知识都是很容易学的知识,如果你基础好,在游戏编程中随时学就可以,不耽误你学习游戏编程的进度。我在这段最后会指出那些是必须掌握的,因为这些知识如果你在游戏编程时学习再学,时间上真的不允许。

1。书,永远是你的好朋友!  
书,是你了解她最直接途径,当然我说的书还包括所有游戏编程资料。书,有好有坏,想要深刻了解她,你必须挑选精妙的好书,而且还有循序渐进的,合理次序的去读它们。游戏编程书籍大量风起云涌的出现是在2000年以后,当然2000以前也有,只是很少一部分,从2000或者以前开始就研究3D游戏的中国大哥们,现在可以说是现在中国游戏行业的鼎梁支柱。现在出版有关游戏方面的书籍大约有200左右本,中国人自己原著的书几乎没有,大多数都是翻译国外的,就有几本翻译的质量很好(翻译大哥大姐确实很辛苦,我承认这点,但拜托你们翻译前要看懂再翻译,否则望文生义,不如让我们自己看英文原版)要么自己写的也是东挪西凑的,写出来估计是想骗钱,谁叫中国人这么老实,这么好欺负了。其中大部分书籍是3D游戏的,3D游戏确实要比2D游戏难一个数量级别,如果你足够聪明,其实这些书看个几十本,写出一个在中国可以立足的3D引擎足够了。但好象这样聪明的人真的很少,有黄金的这个地方大家都知道,可是前往这块宝地的道路太荆棘,很少有人走下来。
书,大家从出生时就接触,学会读书,这四个字,很少有人能领会,你看一本书,要么否定,要么肯定,如果你还是很迷糊,要么是书写的不好,要么就是你知识水平层次有限,要么就是你根本没学会读书。我在这里只说一句话“要读好书,思而不学则罔,学而不思则怠,温故而知新”,我想高手很容易体会这句话。
想要学好游戏编程,学会读书是你要掌握的,也是你获取知识最快的途径,开始时,建议还是读一些中文的游戏编程书,因为游戏编程中充斥大量术语和名词,没有任何游戏编程概念的人,直接看英文,即使你是老外,我估计你看一会就会爬在书上,眼皮一关,口水把它淹没了。
看书后,代码一定要仔细读,而且对于初学者一定要亲自去实现,这样你才能加深理解,有时看书看不懂,一看代码就全明白了。“纸上得来终觉浅,觉知此事必躬行”,我始终坚信这句话,只有你经历了,你才能感受,就象你没有失去过,就不会知道珍惜是什么东西。
我相信既然你都看到这里了,就应该知道我上面为什么给你打好的警钟。看完书,就要写代码,最好不要一味的效仿,要有你自己的独道之处,这样才说明你认真考虑了这个问题,你也真正了解了,这个问题是什么,应该怎么解决,怎么解决最有效果。
E文书,以后就要是你老婆(同性恋除外),天天都要见面,除非你离婚了,也就是游戏编程这个爱情你不想要了,你提前离开了这个努力走到最后才能见到美好果实的幸福家庭。中文翻译一般都很慢,中文翻译出来,国外这个技术基本过时了,而且有能力翻译的人本来就少,有时间干这事的说他有病,也不足为奇,因为翻译一本太浪费时间了。所以要适应去看E文,现在没钱,就看电子的,很多国外新书,没有电子的,但电子的够我们初学的人玩很长时间,等有钱了,就直接去买国外正中的洋版,然后弄到家中,天天啃,工作时也啃,人家都会说你很有激情的。看电子的如果闲麻烦,就打印下来,打印下来还是花不了几个钢币的,和买一本中文差不多(大家不要哭穷,一本好书100元我认为很值得,里面技术如果你掌握了,你想想,大把大把的票子就进你口袋里,很多人买来不看,天天还抱怨书贵。)
这几年来,我读过很多游戏编程方面的书,中文的基本都读过了,现在我一直在读E文书,有时到觉得看中文有些不适应,还有好多好书,我还没有读。读E文书时,如果没读懂,自己一定要去网上查查相关资料,然后集中精力把它弄懂,现在很少有看不懂的E文游戏编程书(知道我6级没过的,别用砖仍我),有些还是看不懂,确实自己水平有限,但不是因为英语问题,关于高深而复杂数学问题,即使翻译成中文,我也很难看明白。
我在后面会把所有好的游戏编程书籍都列出来,告诉大家那些可以买到,那些只有电子的。不好的书,我就不列出来了,只会浪费大家以后的时间。
2。游戏编程所需要的基础知识

我这里介绍下要做一个好的,称职的,游戏程序员所需要。学习就和盖楼一样,要想上层建筑牢固,你就要有坚实的基础,我这里介绍的基础是你从事游戏编程最基础,基础的不能再基础的东西。
2。1 数学
数学是游戏中支柱,没有了它,任何都不要谈,在你编写你游戏的时候,你就会知道,世界抽象成一堆数学是多么的神奇,突然你会觉得以前枯燥无味的数学现在是一顿大餐,但这个是霸王餐。国外人写关于引擎方面书的人,都可以说是数学高手,他们理论和实践都很厉害,让我崇拜的不得了。大学里是你学好数学的最好机会,一定要弄明白它的现实意义,任何理论都不是凭空出来的,它一定有现实基础,这个现实基础,就可以帮助你很好理解这个理论。
2。1。1高等数学——微积分理论
在游戏引擎中,很多游戏特效都是通过微积分方程来解得的,高度仿真是现在游戏的追求,真实感越强就需要越多的计算量。极限,一元微积分,多元微积分,级数,微分方程等等在游戏图形图象和物理上都有明显体现。有时一个硕士或者一个博士为了拟真一个效果要花费几个月或者几年的时间,做为一个游戏程序员,微积分到达什么层次我很难给出定义,但起码如果别人给出了原理我们能看明白,并把它实现,我觉得就很可贵了。
2。1。2线性代数和空间解析几何
在大学课本中,空间解析几何是放在高等数学书里面讲的,但游戏中,它和线性代数放到一起我认为更加合适。线性代数所有知识都要掌握,在游戏中它们比微积分体现的更淋漓尽致,理解上我个人认为相对微积分更容易。
空间解析几何主要讲向量,平面,线,体,这些东西并通过线形代数矩阵进行空间变换,最后达到游戏想要的目标。
2。1。3四元数
这个在大学里面不学数学的,估计很少有人接触,它也是来用来变换空间,理解起来不是那么容易,但最常用的就是用四元数来表示旋转。
我这里只是一概而过,上面任何知识在游戏中都有应用,但大学里的数学不是为了游戏而开设的,所以和游戏中用的数学在知识分列和讲解上还是有很大差异,尤其是线性代数和空间解析几何,不用担心,我在后面介绍给大家一些好的游戏书籍,对大家学习游戏编程和有帮助的。
2。2语言
编写游戏c和c++是你首选,相对来讲C++是你更应该掌握的。
2。2。1 其他语言为什么不是当今流行的编写游戏程序语言?
VB,JAVA,C#,等等其他语言,并不是说它们不能做游戏,它们能做,由于语言本身设计机理的限制,只能做一些对游戏速度要求低的游戏,对于运行速度要求高的游戏,它们被判成死刑。当然这个也取决于硬件的发展,以前8bit FC游戏都用C语言和汇编来写,现在由于硬件速度提高,用VB,JAVA,C#来写这些游戏运行也是可以的,例如现在兴起的WebGame(网页游戏)。但如果你真想做游戏的话,你一定想要了解现在最流行游戏编写,C和C++是你最好的选择,也是你走上真正的游戏编程道路的重要武器。
2。2。2 C和C++应该选择那一个?
C和C++,这里面我还要说一说,我个人更看好C++。大家都知道C是面向过程的,而C++是面向对象的,虽然C++的面向对象特性还不是很完善,但出于对大型软件设计上的考虑,因为游戏引擎是大的工程,框架设计远远要比编程重要,一般框架设计用面向对象方式更直接,所以C++比C更有优越性性,而且如果你合理用好C++,并不见得C++比C速度慢。有时一个软件架构和软件运行上问题,很难取舍,对于大应用程序来说,都是一点点牺牲速度来获得好的架构,是利大于弊的事情,这里确实体现软件工程的重要性质。
2。3。3 汇编,很重要吗?
汇编语言也是你要掌握的,你说你语言学的好,但却不会汇编,是一个天大的笑话。现在很少有汇编高手能写出比编译器优化出来的汇编代码运行效率要高,因为语言的不同,思考问题的方式就会有不同,就象最早纸带机上的原语言一样(可计算性与计算复杂性就讲这东西)。如果游戏都用汇编来写,写代码效率很低,所以我们都用高级语言来写,同时还要与底层有密切联系的,C和C++担任大部分工作量。对于游戏速度频颈问题,有时我们用C和C++即使优化很多,代码精简很多,算法也改良很多,但由于语言本身设计上问题,和高效汇编来编写还是有不同的,这时就要是汇编高手来做速度最快,内存和寄存器使用最少汇编程序。所以汇编还是很重要的。
2。3。4 其他CPU硬件指令
这里只是说 INTER 和 AMD CPU 都支持的指令集,也是最常用指令集。
MMX和SSE SSE2是游戏编程中最常用的指令集,这个指令集是 SIMD(single instruction multiple data),也就是单指令多数据流指令集,一个指令可以处理多个数据流运算。汇编CPU指令,一个指令只能执行一个数据流运算。
例如:
   汇编中
  ADD EAX,EBX
  这是一个加法指令,EAX,EBX是寄存器,是32位的。这个指令就是 EAX = EAX + EBX。这个过程只有一个数据流相加。
  而SIMD思想是,如果现在有2个指令
  ADD EAX,EBX  
  ADD ECX,EDX
  能不能让他们一起执行?所以SIMD 指令提供这样的功能,他用很大的寄存器,前一部分装EAX值,后一部分装ECX值,再用一个寄存器,前一部分装EBX,后一部分装EDX.
  SIMD 寄存器分成2部分[1部分][2部分]
  SIMD ADD 指令  积存器1  ,积存器2
  运算过程为 寄存器1 [1部分] = 寄存器1  [1部分] + 寄存器2  [1部分] ;
      寄存器1 [2部分] = 寄存器1  [2部分] + 寄存器2  [2部分] ;
  这2个运行是在一个SIMD积寄存器用硬件来执行ADD 同时运行的。
  这里只是说了SIMD思想,现在简单介绍下 MMX,SSE,SSE2。
  
  MMX寄存器是32位的,所以它可以执行4个8bit数据同时运算,也可以执行2个16bit数据同时运算,具体要看使用哪个指令,在早期没有SSE指令时,就用MMX,但MMX有缺点,它和FPU(浮点运算单元,专门执行浮点数运算)共用同样寄存器,所以当你指令有中断,从浮点运算跳到MMX运算,要告诉CPU,从MMX跳到FPU 也要告诉CPU,这样数据要临时保存,降低了处理速度。早期2D游戏经常用这个。
  现在SSE,SSE2有自己单独的寄存器可以使用,而且它们是128位的,支持4个32位整数或者浮点数同时运算。
  这些指令都没有分支,所以使用时效率要我们掌握,因为执行都是并行的,一个指令完成多个计算,所以即使你编写SIMD代码很差,速度也会提高几倍,游戏中经常用SIMD处理颜色和数学运算问题。在游戏速度瓶颈地方用这样的代码,公德无量。

2。3。5  ASM SHADER语言 和 高级SHADER 语言
开始学游戏编程时,这个知识不一定要具备,因为你不了解3D流水线内部细节学起来还是比较困难,所当你做完“软引擎” 了解3D流水线,在学这个语言很容易,接下来的语言发挥,就靠你自己了。
早期的游戏,所有的图形图象处理基本都由CPU来完成,然后把处理完的数据传到显存,用显卡来显示。现在的设计理念尽量解放CPU,把与图形图象有关的运算用显卡来完成。但早期显卡只支持固定流水线,所谓的固定流水线就是所有3D数据处理过程都是事先用硬件实现好的,这个过程是一个大过程,里面封状了很多小过程细节,用户只需要输入数据,显卡负责输出数据,至于用户无法干预这个大过程,小过程更是接触不到。再说的通俗一点,一个空间3D三角形要想显示到计算机屏幕上,基本要经历2个大过程,顶点处理和象素处理,顶点处理封状了很多空间变换的小过程,象素处理包括象素混合小过程。这个过程是不可以访问的,我们只能通过显卡提供的接口来控制,而且改变方式很单一。(刚接触这些可能不明白,现在你知道的不用太详细,就知道大过程是不可以干预的就可以了,或者是只有很少一部分可以干预)为了让图形图象处理更加完善话,让人们可以参与到整个3D流水线中,实现更多丰富的效果,显卡开始出现GPU单元(图形处理单元),这时的3D流水线从固定流水编成了可编程流水线,有了早期的GPU 指令,大家都称为 SHADER指令,也就是ASM SHADER,和汇编一样,这些指令都是和GPU指令一一对应的,随着硬件更新,GPU越来越强大,支持指令越来越多,ASM SHADER 从1。0 到 1。4 ,到现在3。0版本。由于ASM SHADER 用起来不方便,就象我们用汇编写程序一样,所以又出现了高级SHADER语言,有DirectX3D下支持HLSL(high level shader language)和 OPGL下支持的GLSL(opengl shader language)这些语言都是面向过程。由于硬件设计限制,这些语言不能象C语言那样随意使用,SHADER语言有自己的使用规范,大家学习时一定要弄明白3D流水线内部实现过程,再使用SHADER简直很容易。
还有一个要说的是,GPU现在还不支持分支预测,但GPU编程语言现在已经提供了丰富的条件判断,条件判断还是会影响到速度的。

重点你要掌握的是,数学,c和c++,汇编,数据结构,基本掌握的是计算机体系结构,离散数学,编译原理,计算机网络,操作系统,软件工程,数据库,人工智能。可以说这些是计算机最基础的学科,你只有掌握了它们,无论你专研计算机哪个领域,你的基础都是足够的,游戏编程中这些学科都可以用到,我们不用每个学科都精通的要命,我们要精通的是游戏编程,也就是接触到和游戏相关的,我们有这些基础完全可以看懂,然后我们在用自己时间来去专研这些学科在游戏领域的应用。

四。游戏编程,她的大门已经为你打开。

1。 颜色之迷

计算机怎么显示图形的,这个你必须要弄明白,计算机显示彩色图象是经历了一个什么样的发展过程。
1。1 象素和屏幕显示点
计算机显示一个图象是由象素组成,我们通常说的分辨率就是屏幕上显示多少个象素。如果一个屏幕最大可以支持1024*768那么它也可以支持800*600,当你屏幕分辨率是800*600时,一个象素占据了屏幕多个屏幕显示点,也就是说,只有当你分辨率调节到最大时,屏幕显示点和象素点才是一一对应的,至于一个象素占据多个屏幕显示点后颜色怎么显示这个是硬件的事,我也不是很了解。象素都是2进制存储,然后由硬件根据显存中当前象素值,根据颜色显示模式的设定,来显示指定颜色。
1。2 颜色组成和模式
计算机要显示颜色,每个象素都有自己的颜色,通常颜色有3种模式,一个是调色板模式,一个是16bit模式,一个是32bit模式,我这里说的通常,早期也有4bit模式,我要讲出里面的细节,让看的人真正理解,不是死记硬背,你就会发现实质上只有2种模式,一个是调色版模式和bit显示模式。
我先介绍位显示模式,从4bit开始,早期颜色显示很单调,硬件只支持16个颜色,当然这16个可以是黑白的,也可以是彩色的。总之硬件就支持16个颜色。所以用4bit表示4bit 2进制正好可以代表16个。
随着硬件发展支持的颜色越来越多,发展到16bit,16bit开始有了严格限制,颜色是有三种色彩基元组成,也就是红绿蓝,人送江湖称号RGB,16bit分成1555格式和565格式,1555就是1位给ALPHA(表示透明)分给RGB分别5bit,5bit可以表示色彩基元颜色是32种,RGB组合起来就是15bit,就是2的15次方,可以表示很多颜色了。而565格式没有ALPHA位置,直接分给RGB位数为565,也就是绿色多了一位,传说中,专家说人眼对绿色明感,所以给绿色多分了一位。
而32bit分给 ALPHA 和 RGB 分别是 8888,每个都是8bit。
其实标准的颜色都是每个分量都是8bit,每个色彩基元都是256种,而16bit模式不能显示出真实世界色彩。
调色板模式一般都是针对每个分量是8bit,也就是颜色组成是32位的。调色板模式有一个映射表数组,这个映射表数组每个元素都存放的是32位的颜色,而显存存放的是一个索引,但硬件读出显存当前象素值时,然后用这个值当作索引去映射表数组里面查对应的颜色,然后显示到屏幕上。
相比较而言,调色板模式是最早期的,例如早期FC 8bit机 每个象素只占8bit,只能表示256种颜色,而我们彩电可以表示很多颜色,为了节省显存,增加处理速度,还显示出多种颜色,FC用调色板模式,而我们想换颜色时,其实只换调色板映射表数组某一个值就可以了,索引都不用变,毕竟索引是写在显存中的,一般改写显存还是速度很慢的。16bit模式也是在处理速度上,节省存储空间上得以应用。毕竟这些显示模式都是为了应付以前硬件速度太慢而设计的,现在一般都用32bit模式。而且无论颜色基元的数字越大表示这个基元颜色越浅。
还有一个要说的就是ALPHA,这个分量是表示透明度的,这个分量如果是8bit,那么它可以表示256种透明程度,255表示不透明,0表示完全透明。
现在大家知道你设置屏幕或者游戏时1024*768*32 什么意思了吧!每个象素占32bit,屏幕显示1024*768个象素。

1。3 颜色的运算
计算机支持颜色运算都是无符号颜色运算,颜色没有负数,而且颜色运算有CLAMP和MOD
如果每个颜色占8bit,颜色表示范围为0-255
这里CLAMP指的是
if(x<0) x=0;
if(x>255) x=255;
这里MOD指
if(x>255) x = x%255;
Color1(a1,r1,g1,b1) Color2(a2,r2,g2,b2)
颜色加减都是向量加减,每个分量做加减
1。3。1颜色加法
        Color3(a3,r3,g3,b3) = Color1(a1,r1,g1,b1) + Color2(a2,r2,g2,b2)   
a3 = a1 + a2;
r3 = r1 + r2;
        g3 = g1 + g2;
b3 = b1 + b2;
颜色加法一般都用CLAMP
1。3。2颜色乘法
Color3(a3,r3,g3,b3) = Color1(a1,r1,g1,b1) * Color2(a2,r2,g2,b2)   
a3 = a1 * a2;
r3 = r1 * r2;
        g3 = g1 * g2;
b3 = b1 * b2;
颜色乘法一般都用MOD
颜色加法和乘法在象素混合效果上经常用,有时为了到达一个效果,加法和减法混合用,至于什么时候用加法,什么时候用乘法,没有严格的规定,还是要看效果,一般对于全局光照模型颜色之间要用加法的,但其他光照模型可能不同,纹理混合或者纹理和颜色混合,加法和乘法都有各自用途。
  
2。Directx OpenGL到底用来干什么?

这里只说他们支持图形和图象部分,其他部分不说。
很多人都听过这2个名词,其实他们是一个函数库,这个函数库为我们做了一些最基本的和底层打交道的处理,其他它们还提供了一些常用的3D函数库,算是一个2次开发,其实2次开发一般和底层没有关系,所以在游戏引擎中很多都自己来做,微软做了很多2次开发,可以算是一个引擎。我前面说了,引擎没有明确概念,只有适应程度之分,用它这个提供的来做游戏,还是差很远,做小游戏当然没问题。
我主要说说和底层打交道那部分,DX和OPGL最大功劳在于充分调度和发挥了显卡性能,把显卡的特性用接口的形式提供出来,他们各自都有自己的管理层次,管理方法,管理管线,至于怎么管理,我也不是很清楚,但当你使用和显卡资源相关的API时,你要仔细看这个函数各个参数说明,它会根据你的指定,来管理显卡。但他的管理只是一部分,还有很大一部分要引擎里面自己处理。
如果你不想自己写驱动的话,那么你还想控制显卡,你就要用这些API,D3D(DX中主要处理3D的)和OPGL使用上还是有很大不同,所以学习他们也还要花费一些时间。我当初学习时,由于不了解3D渲染流程,学起来特别困难,很难看懂,我就放弃了他的学习,学习制作“软引擎”(用CPU来实现显卡提供的硬件功能),然后再学D3D,很容易的。有时后放弃是为了选择更好的,并不是真正的放弃。
至于学习D3D还是OPGL,我个人认为无所谓的,但好象OPGL越更新越混乱,没有DX那么清晰,我也是听别人说的,毕竟我不是很了解OPGL。

3。游戏编程中的常用术语

游戏编程充着大量常用术语,我不打算把它们都列出来,实在太多,我想对于初学者是来说,我希望,你在学习游戏编程时,自己多查下资料究竟这些术语都有什么含义,记住一定在学习游戏编程时去查,也就是说,针对问题去查,而不是在没有遇到任何问题时去查。其实我们的教育有个弊端,任何知识我们学了,不知道怎么用。其实很多知识都是我们遇到问题时再去查资料,去找解决方式,而不是在不知道这个解决方式用来解决什么的情况下吓学。
最好大家把这些术语的中文和英文名称看见都能知道是怎么回事,因为你到以后看英文书时,如果你知道这个术语的含义,看起来会很容易。

五 游戏编程中的2D游戏,你首先要做到的。

通往神殿的第一个考验就是这个,不知道你是否能走下来,相信自己会成功,你的信念一定要不可动摇,当你走过这段路的时候,想想你现在所得到的,付出的其实不算什么。
很多人可能会问,我想做3D游戏,2D游戏学它干什么。其实3D游戏处理可以分成2个部分,一个是3D空间数据处理,经过纹理映射把象素写到屏幕缓冲,接下来其他特效处理都是归结到2D问题。
所以你想真正了解游戏图象处理过程,还是要学2D的。2D原理相对3D来说简单很多,学起来也不是很难。我建议去看《WINDOWS游戏编程大师技巧》里面的代码都读明白,它用C语言实现一个小的游戏引擎,我希望你用C++封状成类的形式,重新按照自己规划来实现,最后做出个游戏。用引擎做出的游戏和直接做游戏还是有很大区别的,这样你引擎也就符合引擎的概念,代码重用性更强。这本书用的DirectX中DDraw接口来实现的,你一定要了解你使用的工具,它能用来干什么,它不能用来干什么,这样你才能很好的游戏编程。如果你不了解C语言,怎么用C语言编程,都是一个道理的。我不想在过多详细介绍DDraw为我们提供了什么特性,你自己去看书。
2D游戏中,最常用的概念就是贴图,把一个图象贴到另一个上。2D游戏中一种处理模式是在各个图片上的处理,还有就是在象素上的处理,其实这些都可以归结到象素上的处理,象素处理,就是处理显存数据。
颜色模式,《WINDOWS游戏编程大师技巧》这本书讲的很详细。
2D动画系统,《WINDOWS游戏编程大师技巧》这本书没有详细介绍,但你看它的代码,代码写的很清楚而详细,你就知道的。
GDI 我不知道大家听说过没有,它是WINDOW自带的图形图象接口,我希望大家也能了解下,当时我是GDI和DDRAW一起学的,然后分别做了引擎,2个引擎接口也一样,游戏移植非常容易。

一个最重要的问题就是调试,8BIT模式下的游戏,用编译器带的单步调式是不可能的,我机器死机N回。我现在也没有发现好的办法,最好的办法就是自己做一个LOG日志系统,把所有调式信息都写到文件里,用任务管理器把当前程序关掉,然后看文件写的信息。
2D游戏制作细节我没有详细介绍,毕竟我不是在讲怎么制作2D游戏,我是讲你应该注意什么,怎么学,我想《WINDOWS游戏编程大师技巧》会给你想要的一切。当然我在学这本书前已经有了一些2D游戏基础,以前用VB做过游戏,用GDI也做过,所以学起来还算容易,如果你以前没有任何游戏编程基础就直接用DDraw,那么我希望你多下点工夫,把它制作游戏的流程都搞清楚,记住什么时候用自己的双手做出了自己2D游戏,你才算2D游戏编程过关了。这本书仔细看吧!直到把它所有的精髓都掌握。
还有一个要说的就是,图象处理常用算法,大家可以多看看图象处理的书,游戏中特效经常用到,其实如果你真想一心搞3D,2D上做一个坦克大战这样的游戏就可以了,很多2D处理详细东西,在学3D游戏时,都可以接触的。
六。游戏编程中的3D游戏,考验你耐力和勇气的时候到了!

前面你所遇到的一切都是小小测试,证明你有能力接受这个挑战,在3D游戏这个广阔无垠的天地里,它比2D游戏更有让你想象余地的空间,因为3D游戏不仅仅包括2D游戏所有,而且还包括很多其他东西。努力吧!朋友们,无论遇到什么挫折,都不要放弃,因为她最后会给你想要的。
我不知道其他人学习3D游戏是一个什么样的过程,这里我只介绍我自己的学习过程,因为我是从挫败中走过来的。
开始学3D,脑袋里一片模糊,只有一点点大学里学的计算机图形学相关的知识,我不得不说学校里讲的计算机图形学和游戏还是大相径庭的。
(这里允许我抱怨一下,国外很多大学都开设了3D游戏编程的课程,而且很专业,我看过老外的PPT。相对于国内,也有些,但不专业,我说游戏学院骗钱你不要打我,它确实能让你找到工作,但你做的永远是下手,学不到底层的东西。我看过游戏学院开设的课程,没有一个是有技术含量的,用“外包”形容在贴切不过。道理也很简单,中国现在牛人很少,我说搞引擎的高手全国有1000人都是保守的,真正能搞出名堂的,也不多于200人,这200人里面学习游戏编程经验10年就很多了,中国3D游戏起步很晚,相对国外,中国人爱跳槽的习惯,让任何公司都没有技术积累,发展更是困难,所以人才积累的少,而且这些人都很忙,在加上现在学校教育约束,即使他们想去高校教授游戏课程也是不可能,何况人家都不愿意去,这样中国积累的人才更少)

所以你要系统学习游戏中图形学理论。
这里我经历了游戏学习编程一次比较大的挫败,当时自己是初学者,问过很多人学3D,应该从什么入手。很多人都说从D3D开始入手,于是我做了。我不得不说的是,如果你不了解3D流水线过程,学D3D简直是看天书,当时我以为自己反复的看书,写程序,最后会理解的,浪费了我大量的时间,最后还是无法灵活掌握D3D,如果你无法掌握D3D,想做游戏可能真的很困难。我终于认识到,我选择的路是错误的,那些所谓的高手难道就学D3D学过来的?那么他们花费的时间是可想而知的。因为有些细节你无法掌握,你使用时就觉得匪夷所思。
《3D游戏编程大师技巧》这本书是每个学3D的人都应该看的,这本书把显卡硬件实现的功能都用软件来做,真正让你理解一个3D东西到最后的屏幕显示,是一个什么样的过程。作者实现了一个软引擎,软引擎就是所有的3D功能都是用代码自己写的,没有用什么显卡来做处理的引擎。这本书是一个经典巨著,如果你想学3D,即使死也要把这本书弄明白,这样不辜负作者写这本书留下的汗水,写一本书,尤其这么PERFECT,太不容易了,国内人翻译的也很出色,感谢他们。

《3D游戏编程大师技巧》继承了《WINDOWS游戏编程大师技巧》里面2D函数,然后3D功能以及流水线和引擎都是自己完成,这本书的代码量要有十几万,我当时用C++重新封装了《3D游戏编程大师技巧》代码,自己设计了一个面向对象固定流水线模式,代码量13W左右,这个过程很艰辛,这本书我看了2遍,最后一共零零散散花费了大约9。10个月的时间。你会感觉你立刻升级了,HP,MP,CP都增长了,当然也包括RP,哈哈!(不知道这些术语的,大家多玩玩RPG游戏吧!)
好东西去追求,不要怕浪费时间,浪费精力,你想想,你得到的最后会大于你的付出,这些都不算什么。很多人问我怎么学3D,我建议他们不能着急,要从学《3D游戏编程大师技巧》开始,大约要8,9个月时间,把每个细节都弄明白,闭上眼睛,怎么实现就很清楚,书上每一页写什么都很明确。他们都觉得浪费时间,那么这样是永远学不好的

1 3D数学是你要攻克的
虽然你数学学的好,但国内的教科书还是和游戏中用的有些不同,毕竟没有上过专门讲解游戏中数学知识的课程。这需要你系统学习游戏编程中数学,3*3矩阵为什么有的人用,而有的人用4*4矩阵,为什么有的用矩阵转置有的用矩阵的逆。《3D游戏编程大师技巧》这本书介绍的很详细,还有一本书就是《3D数学基础:图形与游戏开发》很不错(有中文的),先系统学习下,然后在学习游戏编程时,不懂的时候,再回头看看这些书,把所有的东西都彻底弄清。你如果数学基础好,学这些都很容易,以后游戏编程中最深奥的地方之一就是数学。

基本上我总结下,你要知道的是:
向量  4维 和 3维向量有什么不同
矩阵  4*4 和 3*3 矩阵有什么不同
四元数
ELUER角
以后有机会深入研究碰撞中的各种算法,还会遇到很多关于数学的问题,上面只是渲染有关系。
2 3D流水线的实现过程
我无法用言语来表明这个过程的重要性,整个3D流水线是是3D游戏的脊柱,你的基础知识牢固与否,主要看你这里。3D流水线每一个细节我希望大家都要搞清楚,这个过程对初学者是要花费大量的时间专研与琢磨,D3D的固定流水线都是由硬件完成,如果你的硬件不支持,它会用软件模拟,好,关键就在这里,软件模拟是怎么回事,而《3D游戏编程大师技巧》的流水线是自己用C语言写的,也就是用软件完成,这样你能了解整个流水线的细节,同时你在学D3D时,对照着来学,你会发现,D3D学起来很容易。现在D3D和硬件都支持可编程流水线,而且DX10没有固定流水线,所有变换过程都用SHADER语言控制显卡完成,这个过程是3D流水线的缩影,所以3D流水线的重要性是是可想而知的。
我说了这么多,3D流水线对你是一个新鲜的名词,我也没有去解释它,我不想去解释,完全留给你自己去,我始终坚持着,我只告诉你去的路,路上有什么困难,应该怎么走,你自己去体会这条路上的酸甜苦辣。我希望大家多花些时间在这上面,真真正正的自己写代码实现一下,基础对你真的很重要,记住我说的话,很多人学习时,认为自己理解了,没有去实现,其实还是那句话“纸上得来终觉浅,绝知此事必躬行”,相信我,没错的。
        基本上我总结下,你要知道的是:
3D中的顶点结构,每个分量都用来干什么,三角形是基本渲染单元。
模型空间——》世界空间——》相机空间——》投影空间——》屏幕空间——》光栅化 这个过程和运行原理
材质究竟是什么?
        常用的宏光照模型原理,光源类型
光源和材质和法向量关系
光照在哪个空间去执行(其实不是很严格)
纹理寻址,纹理映射方式,纹理混合,纹理过滤。
1D 2D 3D CUBE 纹理
Z 1/Z缓冲
2种相机模型
ALPHA 透明
关于光栅化三角形要特别注意,他最后混合纹理和光照颜色和ALPHA透明,还要判断深度缓冲,最后是插值填写每个象素。
3 D3D究竟要如何学习
《3D游戏编程大师技巧》和D3D流水线过程还是有些差别的,比如在投影矩阵上,其实它们都是大同小异,只不过表现形式不同,原理都一样。我希望大家自己可以找出来,深刻理解里面原理。
还有一个重要的东西,就是哪些是D3D开发游戏必须有的,哪些是D3D自己扩展的。必须有的,就是同硬件打交道的部分,而D3D中自己扩展了很多库函数,例如数学库,这个完全可以没有,我们自己来实现,还有D3D中的EFFECT接口,它自己定义的。X文件所有函数接口。也就是说,你要理解,哪些是游戏开发中必须有的,哪些是游戏开发中自己完全可以不接触底层来实现的。
游戏编程中有3个缓冲区,颜色缓冲,深度缓冲,模板缓冲,前2个《3D游戏编程大师技巧》中,都详细的介绍,而模板对初学者可能很晦涩,大家先弄明白模板的机制,然后多看些例子,我想你会深刻理解它的含义的。
D3D的学习,可以看D3D SDK的例子,也可以找一些书籍,很多的。

基本上我总结下,你要知道的是:
D3D提供基本常用底层接口,那些是是D3D为了编程必须提供的,那些是不用提供的,比如数学库我们完全可以自己写。
D3D顶点 索引BUFFER
颜色缓冲,模版缓冲,深度缓冲 以及对这些缓冲的操作
目标渲染对象、
D3D 中的3D流水线
单通道渲染,多通道渲染。
熟练操作上面的所有,这些都足够了,其他的都是D3D中多余的,如果你要自己做引擎,很多都要自己实现,当然初学者可以就用它扩展的函数库,也很方便的。

4 其他要知道的游戏知识
下面知识都属于很强的游戏技术范畴,他们只属于单单游戏中技术支持,引擎就是用合理的方式,把所有3D相关包括流水线和下面这些都整和到一起,但这种整和是有效的管理。
场景管理算法 :BSP OCTREE QUADTREE PORTAL PVS
动画系统 :骨骼动画,蒙皮骨骼动画,渐变动画,和这些动画的混合
阴影  :阴影体 投影阴影 阴影影射
剔除算法 :包围球 OBB AABB BHV算法
LOD系统         :视点无关LOD 视点相关LOD
广告牌  :跟踪相机方向 和 只围绕y轴旋转 粒子系统
这些是比较常用的,常常碰到的,根据基本的颜色运算,空间运算还能衍生出很多游戏特效,等待你自己去看书,去解决。

七。3D引擎编程,令你的挑战达到了极限    
就象我前面说的,引擎没有严格定义划分,但对于当代的引擎,似乎大家都趋向于越好的东西,越健壮的支持,才称上引擎。现在游戏玩家对画面拟真程度要求很高,这就要求引擎能更好的处理3D世界中大量数据,一个好的引擎,起码要做到以下几点(引用《3D.Game.Engine.Architecture.Engineering.RealTime.Applications.with.Wild.Magic》里面的话)
1。如何把3D世界中大量数据,让它实时快速处理。
2。引擎中的渲染器(用来管理数据,进行渲染处理的)接口应该怎么定义。
3。如何让使用引擎的人,能很容易的使用引擎。
4。当引擎中添加新特性时,如果让引擎改动最小。
我感觉这4句话,概括的很精辟,如果让我来概括,好象我现在还没这个能力。这是一个当代引擎的新的定义,虽然不是全面,但它一语道破天机,如果你的引擎能达到上面4点,我想它已经足够的优秀,同时说明你也很优秀了。下面我就分别解刨这4句话,让大家对引擎有一个深刻的了解。我只是分析,这4句话要求高手才可以完全去达到,我现在还不具备这个能力,所以只能帮大家分析。
1。如何把3D世界中大量数据,让它实时快速处理。
  
3D世界中充满了大量得数据,有效的处理只可以看见的,能感受到的就可以了。
3D引擎中,很多时候都在处理剪裁和剔除的问题,把不可见的或者和碰撞无关的数据用最快的方法弄掉,不让它进入显卡,一切还是停留在3D空间中。剪裁,剔除还有碰撞处理,永远是3D引擎中最常见的话题,现在有很多成型的方法,大家自己通过学习就可以了解的。
2。引擎中的渲染器(用来管理数据,进行渲染处理的)接口应该怎么定义。
即使你把3D数据给了显卡,还是不能达到最快显示,我们要把这些数据分类,重新组织,这里面还涉及到存储管理的问题。你的渲染器的接口要定义的和底层提供的DX或者OPGL相通,因为最后你要调用的是这2个库里面的函数。
  
3。如何让使用引擎的人,能很容易的使用引擎。
这是一个经验问题,一个软件设计给用户使用,怎么让用户很容易上手。但无论什么样的用户,你必须对3D有了解才可以使用,否则神仙也没法完成这个引擎。
4。当引擎中添加新特性时,如果让引擎改动最小。

经常说的一句话就是“计划没有变化快”,怎么能让引擎跟上时代的步伐,当硬件有革新,我们引擎就有革新,出现新的处理方法,引擎也要改进,用户需求改变,引擎接口也要改变,我们要求引擎的所有改动最小,这样才是一个完美的引擎。
引擎是软件设计+算法+编程的结合体,难点在软件设计上,一个好的引擎,代码几百万很正常,没有一个好的设计来规范它们,混乱是不可避免的。

对于一个引擎,从设计到编程都很重要,一个人完成一个好的引擎是不可能的,多人协作来完成引擎,在大家相互配合的同时,每个人对自己的要完成的任务必须精通,还要有人协调这些工作。
好的引擎,要有一个抽象层次,然后是渲染API层次,还要支持多操作系统平台,我在这里没有过多介绍各种技术细节,因为很多细节在书中都可以找到,加上你的聪明,很多技术算法可以自己创造的。
对于游戏中大量算法,引擎中到处都是,碰撞,场景管理,光照,纹理混合,动画系统,粒子系统,内存管理,资源管理等等大家要多实践,多思考,多看书,多看资料,把握每一个细节,把握每一个整体。看书时要带着问题去看,同时也要带出问题,因为没有一个方法是完美的,必定会有或多或少的缺陷。

现在开源的引擎确实不少,其实程序员都有一个共性,看别人代码有时不如自己写,确实这样,有时只看代码去体会别人的思想真的很难,但有时为了去学习也要硬着头皮去看的,跟踪代码。开始时,如果你不了解引擎究竟是什么,这时最好集中看一个开源引擎,一定要把它的工作原理都弄明白,这样你会得到很大的提升,你就会明白引擎是什么,为什么不用D3D直接编写游戏,还有一点就是,有机会一定要去实现,这样你的体会会更深。

八 浅谈物理与人工智能

这2个都不是中国游戏中的优势,它们和图形一样,要求一定能力积累才可以胜任。如果我没有预测的话,未来中国游戏中大量的需求将来自物理和人工智能,传统的网络RPG会慢慢不能满足大家需要,而被时代所抛弃的。

这2方面的编程我只是略知一二,还算一个小白,在此简单谈谈。
10。1 物理
游戏中大部分物理模拟都集中在力学和运动学上,本来它们就是一个整体。因为最后效果是大家看到的,而看到的都是物体在屏幕上的移动,所以所有的计算最终都归结到物体移动上。
因为游戏毕竟是游戏,人也毕竟是人,没有必要模拟的那么精确,同时人也是感觉不出来的。
所以要想做好物理上的编程,必须对力学和运动学有系统的了解,高中学的力学部分还不是很足够,对于旋转部分,也就是处理力矩和转动惯量上,高中物理书都没有讲(我那时是没有讲,而且很多直线线形运动方程推倒其实是用微积分推倒的,高中书上都没有明确推倒)
物理上的模拟也没有逃脱物理计算基本规则,先是受力分析,然后计算合力,计算加速度,计算速度,最后计算位置,也可能涉及到动能和动量等知识,还有些流体力学等等吧。
物理其实处理的问题基本就是检测物体碰撞,碰撞后的反映,物体运动情况。
至于理论,大家弄本大学物理,力学部分就足够,如果数学好的话加上高中的物理知识,应该学起来不难的。
《游戏开发物理学》这本书很好,可惜绝版了,但有电子版的,也有中文版。
10。2 AI
相对物理的编程书籍来说,AI的书很多。
AI是研究生必须学的课程,包括传统的AI和计算智能。游戏中常用的传统AI包括自动机理论,A* 算法,回溯,图搜索,剪枝等等吧。计算智能包括的是:遗传算法,模糊计算,人工神经网络。
我答UBI笔试的时候,里面很多题都是A*算法,可见FPS游戏中大量存在的都是AI的路径搜索问题,让电脑更加智能。
AI的游戏编程书很多,我就看过几本。《WINDOWS游戏编程》中有一部分讲的AI,《游戏开发中人工智能》 《游戏编程中的人工智能技术》这3本都有中文的,而且都有大量的实例,对于那些大量理论的书籍来说,看看它们还是很通俗的。还有很多好书,都是英文的。

【转】浅谈C/C++内存泄漏及其检测工具

浅谈C/C++内存泄漏及其检测工具

2006-04-03 09:00 作者: 出处:温馨小屋

  对于一个c/c++程序员来说,内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题,比如Smart Pointer,Garbage Collection等。Smart Pointer技术比较成熟,STL中已经包含支持Smart Pointer的class,但是它的使用似乎并不广泛,而且它也不能解决所有的问题;Garbage Collection技术在Java中已经比较成熟,但是在c/c++领域的发展并不顺畅,虽然很早就有人思考在C++中也加入GC的支持。现实世界就是这样的,作为一个c/c++程序员,内存泄漏是你心中永远的痛。不过好在现在有许多工具能够帮助我们验证内存泄漏的存在,找出发生问题的代码。

  内存泄漏的定义

  一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形:

void MyFunction(int nSize)
{
 char* p= new char[nSize];
 if( !GetStringFrom( p, nSize ) ){
  MessageBox(“Error”);
  return;
 }
 …//using the string pointed by p;
 delete p;
}
  例一

  当函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是c函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。

  广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。

  GDI Object的泄漏是一种常见的资源泄漏:

void CMyView::OnPaint( CDC* pDC )
{
 CBitmap bmp;
 CBitmap* pOldBmp;
 bmp.LoadBitmap(IDB_MYBMP);
 pOldBmp = pDC->SelectObject( &bmp );
 …
 if( Something() ){
  return;
 }
 pDC->SelectObject( pOldBmp );
 return;
}
  例二

  当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很多。

  内存泄漏的发生方式:

  以发生的方式来分类,内存泄漏可以分为4类:

  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。

  2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

  3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:

char* g_lpszFileName = NULL;

void SetFileName( const char* lpcszFileName )
{
 if( g_lpszFileName ){
  free( g_lpszFileName );
 }
 g_lpszFileName = strdup( lpcszFileName );
}

  例三

  如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。

  4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:

class Connection
{
 public:
  Connection( SOCKET s);
  ~Connection();
  …
 private:
  SOCKET _socket;
  …
};

class ConnectionManager
{
 public:
  ConnectionManager(){}
  ~ConnectionManager(){
   list::iterator it;
   for( it = _connlist.begin(); it != _connlist.end(); ++it ){
    delete (*it);
   }
   _connlist.clear();
  }
  void OnClientConnected( SOCKET s ){
   Connection* p = new Connection(s);
   _connlist.push_back(p);
  }
  void OnClientDisconnected( Connection* pconn ){
   _connlist.remove( pconn );
   delete pconn;
  }
 private:
  list _connlist;
};

  例四

  假设在Client从Server端断开后,Server并没有呼叫OnClientDisconnected()函数,那么代表那次连接的Connection对象就不会被及时的删除(在Server程序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。

  从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

  检测内存泄漏

  检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见Steve Maguire的<<Writing Solid Code>>。

  如果要检测堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete就可以了(其实new/delete最终也是用malloc/free的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测BSTR的泄漏,就需要截获SysAllocString/SysFreeString;要检测HMENU的泄漏,就需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个,释放函数只有一个,比如,SysAllocStringLen也可以用来分配BSTR,这时就需要截获多个分配函数)

  在Windows平台下,检测内存泄漏的工具常用的一般有三种,MS C-Runtime Library内建的检测功能;外挂式的检测工具,诸如,Purify,BoundsChecker等;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工具要弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。

  以下我们详细讨论这三种检测工具:

  VC下内存泄漏的检测方法

  用MFC开发的应用程序,在DEBUG版模式下编译后,都会自动加入内存泄漏的检测代码。在程序结束后,如果发生了内存泄漏,在Debug窗口中会显示出所有发生泄漏的内存块的信息,以下两行显示了一块被泄漏的内存块的信息:

E:\TestMemLeak\TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long.

Data: <abcdefghijklmnop> 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70

  第一行显示该内存块由TestDlg.cpp文件,第70行代码分配,地址在0x00881710,大小为200字节,{59}是指调用内存分配函数的Request Order,关于它的详细信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行显示该内存块前16个字节的内容,尖括号内是以ASCII方式显示,接着的是以16进制方式显示。

  一般大家都误以为这些内存泄漏的检测功能是由MFC提供的,其实不然。MFC只是封装和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free,strdup等函数时已经内建了内存泄漏的检测功能。

  注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文件的头部都有这样一段宏定义:

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
  有了这样的定义,在编译DEBUG版时,出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢?DEBUG_NEW也是一个宏,以下摘自afx.h,1632行

#define DEBUG_NEW new(THIS_FILE, __LINE__)
  所以如果有这样一行代码:

char* p = new char[200];
  经过宏替换就变成了:

char* p = new( THIS_FILE, __LINE__)char[200];
  根据C++的标准,对于以上的new的使用方法,编译器会去找这样定义的operator new:

void* operator new(size_t, LPCSTR, int)
  我们在afxmem.cpp 63行找到了一个这样的operator new 的实现

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
{
 return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
}

void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)
{
 …
 pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);
 if (pResult != NULL)
  return pResult;
 …
}

  第二个operator new函数比较长,为了简单期间,我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的,这个函数属于MS C-Runtime Library 的Debug Function。这个函数不但要求传入内存的大小,另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放,那么这些信息就会输出到Debug窗口里。

  这里顺便提一下THIS_FILE,__FILE和__LINE__。__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时,编译器会把__FILE__替换成一个字符串,这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时,编译器会把__LINE__替换成一个数字,这个数字就是当前这行代码的行号。在DEBUG_NEW的定义中没有直接使用__FILE__,而是用了THIS_FILE,其目的是为了减小目标文件的大小。假设在某个cpp文件中有100处使用了new,如果直接使用__FILE__,那编译器会产生100个常量字符串,这100个字符串都是飧?/SPAN>cpp文件的路径名,显然十分冗余。如果使用THIS_FILE,编译器只会产生一个常量字符串,那100处new的调用使用的都是指向常量字符串的指针。

  再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文件中只对new做了映射,如果你在程序中直接使用malloc函数分配内存,调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏,MS C-Runtime Library仍然能检测到,但是当输出这块内存块的信息,不会包含分配它的的文件名和行号。

  要在非MFC程序中打开内存泄漏的检测功能非常容易,你只要在程序的入口处加入以下几行代码:

int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

_CrtSetDbgFlag( tmpFlag );

  这样,在程序结束的时候,也就是winmain,main或dllmain函数返回之后,如果还有内存块没有释放,它们的信息会被打印到Debug窗口里。

  如果你试着创建了一个非MFC应用程序,而且在程序的入口处加入了以上代码,并且故意在程序中不释放某些内存块,你会在Debug窗口里看到以下的信息:

{47} normal block at 0x00C91C90, 200 bytes long.

Data: < > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

  内存泄漏的确检测到了,但是和上面MFC程序的例子相比,缺少了文件名和行号。对于一个比较大的程序,没有这些信息,解决问题将变得十分困难。

  为了能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映射功能,把new,maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述,你可以参考MFC的源代码。

  由于Debug Function实现在MS C-RuntimeLibrary中,所以它只能检测到堆内存的泄漏,而且只限于malloc,realloc或strdup等分配的内存,而那些系统资源,比如HANDLE,GDI Object,或是不通过C-Runtime Library分配的内存,比如VARIANT,BSTR的泄漏,它是无法检测到的,这是这种检测法的一个重大的局限性。另外,为了能记录内存块是在哪里分配的,源代码必须相应的配合,这在调试一些老的程序非常麻烦,毕竟修改源代码不是一件省心的事,这是这种检测法的另一个局限性。

  对于开发一个大型的程序,MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker,一则因为它的功能比较全面,更重要的是它的稳定性。这类工具如果不稳定,反而会忙里添乱。到底是出自鼎鼎大名的NuMega,我用下来基本上没有什么大问题。

  使用BoundsChecker检测内存泄漏:

  BoundsChecker采用一种被称为 Code Injection的技术,来截获对分配内存和释放内存的函数的调用。简单地说,当你的程序开始运行时,BoundsChecker的DLL被自动载入进程的地址空间(这可以通过system-level的Hook实现),然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。BoundsChecker在做这些动作的时,无须修改被调试程序的源代码或工程配置文件,这使得使用它非常的简便、直接。

  这里我们以malloc函数为例,截获其他的函数方法与此类似。

  需要被截获的函数可能在DLL中,也可能在程序的代码里。比如,如果静态连结C-Runtime Library,那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用,BoundsChecker会动态修改这些函数的指令。

  以下两段汇编代码,一段没有BoundsChecker介入,另一段则有BoundsChecker的介入:

126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {

00403C10 push ebp
00403C11 mov ebp,esp
130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
00403C13 push 0
00403C15 push 0
00403C17 push 1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }

  以下这一段代码有BoundsChecker介入:

126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {

00403C10 jmp 01F41EC8
00403C15 push 0
00403C17 push 1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }

  当BoundsChecker介入后,函数malloc的前三条汇编指令被替换成一条jmp指令,原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8,执行原来的三条指令,然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址(函数的返回地址在stack上,所以很容易修改),然后把返回地址指向属于BoundsChecker的代码,接着跳到malloc函数原来的指令,也就是在00403c15的地方。当malloc函数结束的时候,由于返回地址被修改,它会返回到BoundsChecker的代码中,此时BoundsChecker会记录由malloc分配的内存的指针,然后再跳转到到原来的返回地址去。

  如果内存分配/释放函数在DLL中,BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址,以达到截获的目的。

  截获住这些分配和释放函数,BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关,也就是说当BoundsChecker检测到内存泄漏,它如何报告这块内存块是哪段代码分配的。答案是调试信息(Debug Information)。当我们编译一个Debug版的程序时,编译器会把源代码和二进制代码之间的对应关系记录下来,放到一个单独的文件里(.pdb)或者直接连结进目标程序,通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能记录呼叫分配函数的源代码的位置,而且还能记录分配时的Call Stack,以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用,以下我用一个例子来说明:

void ShowXItemMenu()
{
 …
 CMenu menu;

 menu.CreatePopupMenu();
 //add menu items.
 menu.TrackPropupMenu();
 …
}

void ShowYItemMenu( )
{
 …
 CMenu menu;
 menu.CreatePopupMenu();
 //add menu items.
 menu.TrackPropupMenu();
 menu.Detach();//this will cause HMENU leak
 …
}

BOOL CMenu::CreatePopupMenu()
{
 …
 hMenu = CreatePopupMenu();
 …
}

  当调用ShowYItemMenu()时,我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数,如CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在ShowYItemMenu()中,或者还有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息:

Function
File
Line

CMenu::CreatePopupMenu
E:\8168\vc98\mfc\mfc\include\afxwin1.inl
1009

ShowYItemMenu
E:\testmemleak\mytest.cpp
100

  这里省略了其他的函数调用

  如此,我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时,大部分的API调用都被封装在类库的class里,有了Call Stack信息,我们就可以非常容易的追踪到真正发生泄漏的代码。

  记录Call Stack信息会使程序的运行变得非常慢,因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关:

  1. 打开菜单:BoundsChecker|Setting…

  2. 在Error Detection页中,在Error Detection Scheme的List中选择Custom

  3. 在Category的Combox中选择 Pointer and leak error check

  4. 钩上Report Call Stack复选框

  5. 点击Ok

  基于Code Injection,BoundsChecker还提供了API Parameter的校验功能,memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了。

  尽管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。

  使用Performance Monitor检测内存泄漏

  NT的内核在设计过程中已经加入了系统监视功能,比如CPU的使用率,内存的使用情况,I/O操作的频繁度等都作为一个个Counter,应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。

  为了检测内存泄漏,我们一般可以监视Process对象的Handle Count,Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数,监视这个Counter有助于我们发现程序是否有Handle泄漏;Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小,NT的内存分配采用了两步走的方法,首先,在虚地址空间上保留一段空间,这时操作系统并没有分配物理内存,只是保留了一段地址。然后,再提交这段空间,这时操作系统才会分配物理内存。所以,Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量,这个值和程序申请的内存总量存在密切的关系,如果程序存在内存的泄漏这个值会持续增加,但是Virtual Bytes却是跳跃式增加的。

  监视这些Counter可以让我们了解进程使用内存的情况,如果发生了泄漏,即使是隐式内存泄漏,这些Counter的值也会持续增加。但是,我们知道有问题却不知道哪里有问题,所以一般使用Performance Monitor来验证是否有内存泄漏,而使用BoundsChecker来找到和解决。

  当Performance Monitor显示有内存泄漏,而BoundsChecker却无法检测到,这时有两种可能:第一种,发生了偶发性内存泄漏。这时你要确保使用Performance Monitor和使用BoundsChecker时,程序的运行环境和操作方法是一致的。第二种,发生了隐式的内存泄漏。这时你要重新审查程序的设计,然后仔细研究Performance Monitor记录的Counter的值的变化图,分析其中的变化和程序运行逻辑的关系,找到一些可能的原因。这是一个痛苦的过程,充满了假设、猜想、验证、失败,但这也是一个积累经验的绝好机会。

  总结

  内存泄漏是个大而复杂的问题,即使是Java和.Net这样有Gabarge Collection机制的环境,也存在着泄漏的可能,比如隐式内存泄漏。由于篇幅和能力的限制,本文只能对这个主题做一个粗浅的研究。其他的问题,比如多模块下的泄漏检测,如何在程序运行时对内存使用情况进行分析等等,都是可以深入研究的题目。如果您有什么想法,建议或发现了某些错误,欢迎和我交流。

 

另一篇不错的文章请参考:

http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html

 

 

 

用 Directx 和 GDI 的 两种方法的截屏代码

http://www.cnblogs.com/piccolo/articles/241933.html

 

LPDIRECTDRAW lpDD     = NULL;
LPDIRECTDRAWSURFACE lpDDSPrime  = NULL;
LPDIRECTDRAWSURFACE lpDDSBack  = NULL;
LPDIRECTDRAWSURFACE lpDDSGdi  = NULL;

LPDIRECTDRAWSURFACE lpSurf   = NULL;

DDSURFACEDESC DDSdesc;
BOOL m_b24=TRUE;
//rfbServerInitMsg m_scrinfo;
RECT   m_bmrect;

struct _BMInfo {
 BITMAPINFO  bmi;
 BOOL   truecolour;
 RGBQUAD   cmap[256];
} m_bminfo; // 用来保存位图信息的结构

 

// DirectX初始化。返回当前表面获取一张屏幕位图的存储空间大小
int DX_Init()
{
 HRESULT hr;
 // 初始化directX
 hr = DirectDrawCreate(0, &lpDD, 0);
 if (FAILED(hr))
  return FALSE;
 
 hr = lpDD->SetCooperativeLevel(NULL, DDSCL_NORMAL);
 if (FAILED(hr))
  return FALSE; 
 
 ZeroMemory(&DDSdesc, sizeof(DDSdesc));
 DDSdesc.dwSize = sizeof(DDSdesc);
 DDSdesc.dwFlags = DDSD_CAPS;
 DDSdesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
 hr = lpDD->CreateSurface(&DDSdesc, &lpDDSPrime, 0);
 if (FAILED(hr))
  return FALSE; 
 
 hr = lpDD->GetGDISurface(&lpDDSGdi);
 if (FAILED(hr))
  return FALSE;
 
 ZeroMemory(&DDSdesc, sizeof(DDSdesc));
 DDSdesc.dwSize = sizeof(DDSdesc);
 DDSdesc.dwFlags = DDSD_ALL;
 hr = lpDDSPrime->GetSurfaceDesc(&DDSdesc);
 if (FAILED(hr))
  return FALSE;

 // 初始化位图信息
 if ((DDSdesc.dwFlags & DDSD_WIDTH) && (DDSdesc.dwFlags & DDSD_HEIGHT))
 {
  m_bmrect.left = m_bmrect.top = 0;
  m_bmrect.right = DDSdesc.dwWidth;
  m_bmrect.bottom = DDSdesc.dwHeight;
 }
 else
  return FALSE;

 m_bminfo.bmi.bmiHeader.biCompression = BI_RGB;//BI_BITFIELDS;
 m_bminfo.bmi.bmiHeader.biBitCount = DDSdesc.ddpfPixelFormat.dwRGBBitCount;

 //m_bminfo.truecolour = DDSdesc.ddpfPixelFormat.dwFlags & DDPF_RGB;
 if(m_bminfo.bmi.bmiHeader.biBitCount > 8)
  m_bminfo.truecolour = TRUE;
 else
  m_bminfo.truecolour = FALSE;
 
 ZeroMemory(&DDSdesc, sizeof(DDSdesc));
 DDSdesc.dwSize = sizeof(DDSdesc);
 DDSdesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
 DDSdesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
 DDSdesc.dwHeight = m_bmrect.bottom – m_bmrect.top;
 DDSdesc.dwWidth  = m_bmrect.right – m_bmrect.left;
 hr = lpDD->CreateSurface(&DDSdesc, &lpDDSBack, 0);
 if (FAILED(hr))
  return FALSE;
 //hr = lpDDSPrime->QueryInterface( IID_IDirectDrawSurface3,  (LPVOID *)&lpSurf);
 //if (FAILED(hr))
 // return FALSE;
 
 switch (m_bminfo.bmi.bmiHeader.biBitCount)
 {
 case 32:
 case 24:
  // Update the bitmapinfo header
  m_b24 = TRUE;
  m_bminfo.bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
  m_bminfo.bmi.bmiHeader.biWidth = 1024;
  m_bminfo.bmi.bmiHeader.biHeight = 768;
  m_bminfo.bmi.bmiHeader.biPlanes = 1;
//  m_bminfo.bmi.bmiHeader.biBitCount = 24;
  m_bminfo.bmi.bmiHeader.biCompression = BI_RGB;
  m_bminfo.bmi.bmiHeader.biSizeImage =
   abs((m_bminfo.bmi.bmiHeader.biWidth *
    m_bminfo.bmi.bmiHeader.biHeight *
    m_bminfo.bmi.bmiHeader.biBitCount)/ 8);
  m_bminfo.bmi.bmiHeader.biXPelsPerMeter = (1024*1000)/1024;
  m_bminfo.bmi.bmiHeader.biYPelsPerMeter = (768*1000)/768;
  m_bminfo.bmi.bmiHeader.biClrUsed  = 0;
  m_bminfo.bmi.bmiHeader.biClrImportant = 0;
  break;
 } 
 

 return m_bminfo.bmi.bmiHeader.biSizeImage;
}
// 捕捉屏幕。rect: 区域。scrBuff: 输出缓冲。scrBuffSize:  缓冲区大小
BOOL CaptureScreen(RECT &rect, BYTE *scrBuff, UINT scrBuffSize)

 HRESULT hr=0;
 
 hr = lpDDSBack->BltFast(rect.left,rect.top,lpDDSPrime,&rect,DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
 if (FAILED(hr))
 {  
  return FALSE;
 }
 
 DDSURFACEDESC surfdesc;
 ZeroMemory(&surfdesc, sizeof(surfdesc));
 surfdesc.dwSize = sizeof(surfdesc);

 hr   = lpDDSBack->Lock(&rect, &surfdesc, DDLOCK_READONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR /*|DDLOCK_NOSYSLOCK*/, NULL);
 //hr   = lpDDSPrime->Lock(&rect, &surfdesc, DDLOCK_READONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR /*|DDLOCK_NOSYSLOCK*/, NULL);
 if (FAILED(hr))
  return FALSE;
 
 // copy the data into our buffer
 BYTE * destbuffpos, * srcbuffpos;
// m_scrinfo.format.bitsPerPixel = 24;
 srcbuffpos = (BYTE *) surfdesc.lpSurface;  
 destbuffpos = scrBuff;

 memcpy( destbuffpos, srcbuffpos,m_bminfo.bmi.bmiHeader.biSizeImage);

 // unlock the primary surface
 //lpDDSPrime->Unlock(surfdesc.lpSurface);
 lpDDSBack->Unlock(surfdesc.lpSurface); 
 return TRUE;

int SaveBitmapToFile(BITMAP *bitmap, LPSTR lpFileName,char *lpBuf)
{
   DWORD dwWritten;
   BITMAPFILEHEADER   bmfHdr;
   BITMAPINFOHEADER   bi;           
   HANDLE          fh=NULL;
   bi.biSize = sizeof(BITMAPINFOHEADER);
   bi.biWidth= bitmap->bmWidth;
   bi.biHeight = bitmap->bmHeight;
   bi.biPlanes = 1;
   bi.biBitCount         =bitmap->bmBitsPixel*8;
   bi.biCompression      = BI_RGB;
   bi.biSizeImage        = 0;
   bi.biXPelsPerMeter     = 0;
   bi.biYPelsPerMeter     = 0;
   bi.biClrUsed         = 0;
   bi.biClrImportant      = 0;
   fh = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
   if (fh == INVALID_HANDLE_VALUE)
      return FALSE;
   bmfHdr.bfType = 0x4D42;  // “BM”
   bmfHdr.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+bitmap->bmWidth*bitmap->bmHeight*bitmap->bmBitsPixel;
   bmfHdr.bfReserved1 = 0;
   bmfHdr.bfReserved2 = 0;
   bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
   WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
   WriteFile(fh, (char *)&bi,sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
   WriteFile(fh, (char *)lpBuf,bitmap->bmWidth*bitmap->bmHeight*bitmap->bmBitsPixel, &dwWritten, NULL);
   FlushFileBuffers(fh);
   CloseHandle(fh);
   return true;
}

int GetBitmapFromScreen(char *lpFileName)
{
 char *lpBuf;
 HBITMAP hBitmap,hOld ;
 HDC hDC,hcDC;
 BITMAP bb;BITMAPINFO b;
 HANDLE hp,fh=NULL;
 DWORD dwX,dwY;
 //***************
 dwX=GetSystemMetrics(SM_CXSCREEN);
 dwY=GetSystemMetrics(SM_CYSCREEN);
 hDC=GetDC(NULL);
 hcDC=CreateCompatibleDC(hDC);
 hBitmap=CreateCompatibleBitmap(hDC,dwX,dwY);
 hOld=(HBITMAP)SelectObject(hcDC,hBitmap);
 BitBlt(hcDC,0, 0,dwX,dwY, hDC, 0, 0, SRCCOPY);
 bb.bmWidth=dwX;
 bb.bmHeight =dwY;
 bb.bmPlanes = 1;
 bb.bmWidthBytes=bb.bmWidth*3;
 bb.bmBitsPixel=3;
 bb.bmType=0;
 b.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 b.bmiHeader.biWidth=dwX;
 b.bmiHeader.biHeight =dwY;
 b.bmiHeader.biPlanes = 1;
 b.bmiHeader.biBitCount        =3*8;
 b.bmiHeader.biCompression      = BI_RGB;
 b.bmiHeader.biSizeImage        = 0;
 b.bmiHeader.biXPelsPerMeter     = 0;
 b.bmiHeader.biYPelsPerMeter     = 0;
 b.bmiHeader.biClrUsed         = 0;
 b.bmiHeader.biClrImportant      = 0;
 b.bmiColors[0].rgbBlue=8;
 b.bmiColors[0].rgbGreen=8;
 b.bmiColors[0].rgbRed=8;
 b.bmiColors[0].rgbReserved=0;
 hp=GetProcessHeap();
 lpBuf=(char *)HeapAlloc(hp,HEAP_ZERO_MEMORY,bb.bmHeight*bb.bmWidth*4);
 GetDIBits(hcDC,hBitmap,0,dwY,lpBuf,&b,DIB_RGB_COLORS);
 SaveBitmapToFile(&bb,lpFileName,lpBuf);
 ReleaseDC(NULL,hDC);
 DeleteDC(hcDC); 
 DeleteObject(hBitmap);
 DeleteObject(hOld);
 HeapFree(hp,0,lpBuf);
 return true;
}

GetPixel截屏

作     者:周强 2009.05.02

 

/**************************************************************************
* 函数名称:SaveToBmpFile
* 函数功能:把客户区内由两点所确定的矩形内图像保存为bmp图像
***************************************************************************
* 入口参数:CPoint point1     矩形左上角点
*           CPoint point2     矩形右下角点
*           char *filename    保存后文件名
*           CDC* pDC          设备内容对象指针
* 出口参数:std::vector<CtlKeyPoint> &vec_point       提取的极值点的坐标
* 返 回 值:成功,返回true ; 失败返回false。
***************************************************************************
* 备    注:使用方法举例:
*                         CPoint p1(0,0);
*                           CPoint p2(1200,800);
*                         if(SaveToBmpFile(p1,p2,”图像.bmp”,pDC)==true)
*                           {
*                            AfxMessageBox(“保存成功!”);
*                           }
*作     者:周强 2009.05.02
***************************************************************************/
BOOL CMyView::SaveToBmpFile(CPoint point1, CPoint point2, char *filename,CDC* pDC)// 当宽度不是4的倍数时自动添加成4的倍数
{
 int width, height;//保存数据的宽高
 width = point2.x-point1.x;
 height = point2.y-point1.y;
 
 BYTE *pImg = new BYTE[3*width*height];//从屏幕提取要保存的图像数据
 for (int index1=0;index1<height;index1++)
 {
  for (int index2=0;index2<3*width;index2+=3)
  {
   COLORREF coler = pDC->GetPixel(index2/3,index1);
   pImg[index1*width*3+index2+0]=GetBValue(coler);
   pImg[index1*width*3+index2+1]=GetGValue(coler);
   pImg[index1*width*3+index2+2]=GetRValue(coler);
  }
 }
 
 FILE *BinFile;
    BITMAPFILEHEADER FileHeader;
    BITMAPINFOHEADER BmpHeader;
    bool Suc=true;
    int i,extend;
 BYTE *pCur;
 
    // Open File
    if((BinFile=fopen(filename,”w+b”))==NULL) {  return false; }
 // Fill the FileHeader
 FileHeader.bfType= ((WORD) (‘M’ << 8) | ‘B’);
 FileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
    FileHeader.bfSize=FileHeader.bfOffBits+width*height*3L ;
    FileHeader.bfReserved1=0;
    FileHeader.bfReserved2=0;
 if (fwrite((void *)&FileHeader,1,sizeof(BITMAPFILEHEADER),BinFile)!=sizeof(BITMAPFILEHEADER)) Suc=false;
 // Fill the ImgHeader
 BmpHeader.biSize = 40;
    BmpHeader.biWidth = width;
 BmpHeader.biHeight = height;
 BmpHeader.biPlanes = 1 ;
 BmpHeader.biBitCount = 24 ;
 BmpHeader.biCompression = 0 ;
 BmpHeader.biSizeImage = 0 ;
 BmpHeader.biXPelsPerMeter = 0;
 BmpHeader.biYPelsPerMeter = 0;
 BmpHeader.biClrUsed = 0;
 BmpHeader.biClrImportant = 0;
 if (fwrite((void *)&BmpHeader,1,sizeof(BITMAPINFOHEADER),BinFile)!=sizeof(BITMAPINFOHEADER)) Suc=false;
 // write image data
 extend=(width+3)/4*4-width;
 if (extend==0)
 {  
  for(pCur=pImg+(height-1)*3*width;pCur>=pImg;pCur-=3*width)
  {  
   if (fwrite((void *)pCur,1,width*3,BinFile)!=(unsigned int)(3*width)) Suc=false; // 真实的数据
  }
 }
 else
 {  
  for(pCur=pImg+(height-1)*3*width;pCur>=pImg;pCur-=3*width)
  {  
   if (fwrite((void *)pCur,1,width*3,BinFile)!=(unsigned int)(3*width)) Suc=false; // 真实的数据
   for(i=0;i<extend;i++) // 扩充的数据
   {
    if (fwrite((void *)(pCur+3*(width-1)+0),1,1,BinFile)!=1) Suc=false;
    if (fwrite((void *)(pCur+3*(width-1)+1),1,1,BinFile)!=1) Suc=false;
    if (fwrite((void *)(pCur+3*(width-1)+2),1,1,BinFile)!=1) Suc=false;
   }
  }
 }
 // return;
 fclose(BinFile);
 delete []pImg;
 return Suc;
}

基于MFC的截屏

#include “stdafx.h”
#include <afxwin.h>

void Screen(char filename[])
{
    CDC *pDC;//屏幕DC
    pDC CDC::FromHandle(GetDC(NULL));//获取当前整个屏幕DC
    int BitPerPixel pDC->GetDeviceCaps(BITSPIXEL);//获得颜色模式
    int Width pDC->GetDeviceCaps(HORZRES);
    int Height pDC->GetDeviceCaps(VERTRES);

    printf(“当前屏幕色彩模式为%d位色彩\n”, BitPerPixel);
    printf(“屏幕宽度:%d\n”, Width);
    printf(“屏幕高度:%d\n”, Height);
    
    CDC memDC;//内存DC
    memDC.CreateCompatibleDC(pDC);
    
    CBitmap memBitmap, *oldmemBitmap;//建立和屏幕兼容的bitmap
    memBitmap.CreateCompatibleBitmap(pDC, Width, Height);

    oldmemBitmap memDC.SelectObject(&memBitmap);//将memBitmap选入内存DC
    memDC.BitBlt(0, 0, Width, Height, pDC, 0, 0, SRCCOPY);//复制屏幕图像到内存DC

    //以下代码保存memDC中的位图到文件
    BITMAP bmp;
    memBitmap.GetBitmap(&bmp);//获得位图信息
    
    FILE *fp fopen(filename, “w+b”);

    BITMAPINFOHEADER bih {0};//位图信息头
    bih.biBitCount bmp.bmBitsPixel;//每个像素字节大小
    bih.biCompression BI_RGB;
    bih.biHeight bmp.bmHeight;//高度
    bih.biPlanes 1;
    bih.biSize sizeof(BITMAPINFOHEADER);
    bih.biSizeImage bmp.bmWidthBytes bmp.bmHeight;//图像数据大小
    bih.biWidth bmp.bmWidth;//宽度
    
    BITMAPFILEHEADER bfh {0};//位图文件头
    bfh.bfOffBits sizeof(BITMAPFILEHEADER) sizeof(BITMAPINFOHEADER);//到位图数据的偏移量
    bfh.bfSize bfh.bfOffBits bmp.bmWidthBytes bmp.bmHeight;//文件总的大小
    bfh.bfType (WORD)0x4d42;
    
    fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);//写入位图文件头
    
    fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);//写入位图信息头
    
    byte new byte[bmp.bmWidthBytes bmp.bmHeight];//申请内存保存位图数据

    GetDIBits(memDC.m_hDC, (HBITMAP) memBitmap.m_hObject, 0, Height, p, 
        (LPBITMAPINFO) &bih, DIB_RGB_COLORS);//获取位图数据

    fwrite(p, 1, bmp.bmWidthBytes bmp.bmHeight, fp);//写入位图数据

    delete [] p;

    fclose(fp);

    memDC.SelectObject(oldmemBitmap);
}