ATL技术内幕 第五部分 (完结篇)

前言:这一系列博客翻译自 The Code Project 上的文章,作者是Zeeshan amjad。

题目:ATL Under the Hood - Part 4

原文链接:http://www.codeproject.com/Articles/3102/ATL-Under-the-Hood-Part-5

介绍

很多人认为ATL仅仅是用来创造COM组件。实际上你可以利用ATL的窗口类,来创建流畅的基于窗口的应用程序。虽然我们可以将你基于MFC的工程转化为ATL,但ATL中包含的UI组件非常少,所以,你需要写大量的代码。比如,ATL中没有文档/试图结构,所以,你想在ATL中用的话,就必须自己实现它了。这部分中,我们来探讨窗口类。同时也探讨ATL窗口类技术。WTLwindow template library)到现在还没有被微软支持,实际上是向守卫制作图形应用方向走了一步。WTL就是基于ATL的窗口类构建的。

在讨论任何基于ATL的程序之前,我们首先看看经典的 “hello world”程序。这个程序完全利用windows SDK编写。大家应该都比较熟悉:

程序 66

#include 

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   LPSTR lpCmdLine,  int nCmdShow)
{
    char szAppName[] = "Hello world";
    HWND hWnd;
    MSG msg;
    WNDCLASS wnd;
    
    wnd.cbClsExtra    = NULL;
    wnd.cbWndExtra    = NULL;
    wnd.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wnd.hCursor    = LoadCursor(NULL, IDC_ARROW);
    wnd.hIcon        = LoadIcon(NULL, IDI_APPLICATION);
    wnd.hInstance    = hInstance;
    wnd.lpfnWndProc    = WndProc;
    wnd.lpszClassName    = szAppName;
    wnd.lpszMenuName    = NULL;
    wnd.style        = CS_HREDRAW | CS_VREDRAW;
    
    if (!RegisterClass(&wnd))
    {
        MessageBox(NULL, "Can not register window class", "Error", 
                   MB_OK | MB_ICONINFORMATION);
        return -1;
    }
    
    hWnd = CreateWindow(szAppName, "Hello world", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    
    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }
    
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hDC;
    PAINTSTRUCT ps;
    RECT rect;

    switch (uMsg)
    {
    case WM_PAINT:
        hDC = BeginPaint(hWnd, &ps);
        GetClientRect(hWnd, &rect);
        DrawText(hDC, "Hello world", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        EndPaint(hWnd, &ps);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }
    
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

程序没有什么特别的。只是在其窗口中心显示了hello world字样。

ATL是面向物件的库,意味着你的程序用类来工作。我们来尝试自己实现一些小的类,来简化我们的工作。创造类的标准是什么?换句话说,我们应该创造几个类,他们之间有什么关系,他们分别有什么属性和方法。我这里不讨论面向对象理论和创建高效库的过程。我只是对API进行分类,将相关的API放在一个类中。我 将处理窗口的API放入在一个类中,以便在其他窗口中可以重复使用,比如字体,文件,菜单等。所以我做了一个小的类,将所有第一个参数为HWNDAPI放入其中。这个类除了对windows API进行一层包装外,什么也没有做。我取名为ZWindow,你可以改为其他名字:

class ZWindow
{
public:
    HWND m_hWnd;

    ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }

    inline void Attach(HWND hWnd)
    { m_hWnd = hWnd; }

    inline BOOL ShowWindow(int nCmdShow)
    { return ::ShowWindow(m_hWnd, nCmdShow); }

    inline BOOL UpdateWindow()
    {  return ::UpdateWindow(m_hWnd); }

};

我只放入了目前我们会用到的API。你可以添加任何想添加的API。这个类唯一的有点是,你不再需要为window API传递HWND这个参数了。这个类将自动传递。

目前为止,没什么特别。但我们的回调函数该如何处理呢?别忘了,这个函数的第一个参数也是HWND,所以,按照规则,也应该是这个类的一个成员。现在,我为该类加入回调函数:

class ZWindow
{
public:
    HWND m_hWnd;

    ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }

    inline void Attach(HWND hWnd)
    { m_hWnd = hWnd; }

    inline BOOL ShowWindow(int nCmdShow)
    { return ::ShowWindow(m_hWnd, nCmdShow); }

    inline BOOL UpdateWindow()
    {  return ::UpdateWindow(m_hWnd); }

    LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        }

        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
};

初始化WNDCLASS结果时,你需要将回调函数的地址传递给它,在我们创建了ZWindow类的实例后,你可以这样传递:

    ZWindow zwnd;
    WNDCLASS wnd;

    wnd.lpfnWndProc = wnd.WndProc;

当程序编译时,得到如下错误:

cannot convert from 'long (__stdcall ZWindow::*)(struct HWND__ *,
   unsigned int,unsigned int,long)' to 'long (__stdcall *)(struct HWND__ *,
   unsigned int, unsigned int,long)

这是因为,你不可以将一个成员函数作为回调函数。因为编译器会为每一个成员函数自动传递一个参数,这个参数就是this指针,换句话说,就是这个类的实例自己。所以,当你为成员函数传递n个参数时,编译器将传递n+1个,附加的那个就是this指针。编译器显示的错误信息还表明,编译器无法将成员函数转化为全局函数。

如果我们想用成员函数作为回调函数,我们该怎么办呢?如果我们能通过某种方式告诉编译器不要传递this指针,那就可以了。 c++中,如果我们将成员函数申明为静态函数,编译器就不会传递this指针了。这也是静态成员函数和非静态成员函数的区别。

所以,我们将WndProc作为ZWindowd的静态成员函数。这个技术在成员函数做线程函数时也用到了。

看下面的例子:

程序 67

#include 

class ZWindow
{
public:
    HWND m_hWnd;

    ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }

    inline void Attach(HWND hWnd)
    { m_hWnd = hWnd; }

    inline BOOL ShowWindow(int nCmdShow)
    { return ::ShowWindow(m_hWnd, nCmdShow); }

    inline BOOL UpdateWindow()
    {  return ::UpdateWindow(m_hWnd); }

    static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        }

        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,  
                   int nCmdShow)
{
    char szAppName[] = "Hello world";
    HWND hWnd;
    MSG msg;
    WNDCLASS wnd;
    ZWindow zwnd;
    
    wnd.cbClsExtra    = NULL;
    wnd.cbWndExtra    = NULL;
    wnd.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wnd.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wnd.hIcon        = LoadIcon(NULL, IDI_APPLICATION);
    wnd.hInstance        = hInstance;
    wnd.lpfnWndProc    = ZWindow::WndProc;
    wnd.lpszClassName    = szAppName;
    wnd.lpszMenuName    = NULL;
    wnd.style        = CS_HREDRAW | CS_VREDRAW;
    
    if (!RegisterClass(&wnd))
    {
        MessageBox(NULL, "Can not register window class", "Error", 
                   MB_OK | MB_ICONINFORMATION);
        return -1;
    }
    
    hWnd = CreateWindow(szAppName, "Hello world", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

    zwnd.Attach(hWnd);
    
    zwnd.ShowWindow(nCmdShow);
    zwnd.UpdateWindow();

    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }
    
    return msg.wParam;
}

这个程序显示了ZWindow的用法。实际上,这个类没什么特别。只是包装了windows API。唯一的优点依然是不用传递HWND参数个API了。但当你调用函数时,你还得键入类名。

比如,之前你这样调用:

    ShowWindow(hWnd, nCmdShow);

现在你这样掉:

    zwnd.ShowWindow(nCmdShow);

目前为止没什么到不了的,接下来看看WndProc中处理消息的方式。之前我们只响应一个消息:WM_DESTROY。如果我们想处理更多的消息,我们就需要在消息循环函数中添加更多的case 语句。下面的函数添加了WM_PAINT消息响应。

switch (uMsg)
{
case WM_PAINT:
    hDC = ::BeginPaint(hWnd, &ps);
    ::GetClientRect(hWnd, &rect);
    ::DrawText(hDC, "Hello world", -1, &rect, DT_CENTER | DT_VCENTER  DT_SINGLELINE);
    ::EndPaint(hWnd, &ps);
    break;

case WM_DESTROY:
    ::PostQuitMessage(0);
    break;
}

这段代码在窗口中央打印“hello world”,注意观察,为什么要用BeginPaintGetClientRectEndPaint这些API呢?他们第一个参数都是HWND,应该是包含在zwindow中成员变量才对呀?

由于所有这些函数都不是静态函数,而你有不可以在静态的成员函数中调用非静态的成员函数。为什么呢?他们的不同依然是this指针,非静态成员函数含有this指针,而静态成员函数没有。如果我们有办法将这个this指针传递给类的静态成员函数,那我们就可以在其中调用非静态成员函数了。看看下面的例子:

Program 68

#include 
using namespace std;

class C 
{
public:
    void NonStaticFunc() 
    {    
        cout << "NonStaticFun" << endl;
    }

    static void StaticFun(C* pC) 
    {
        cout << "StaticFun" << endl;
        pC->NonStaticFunc();
    }
};

int main()
{
    C objC;
    C::StaticFun(&objC);
    return 0;
}

程序输出为:

StaticFun
NonStaticFun

我们可以用类似的技术,用一个全局变量来存储zwindow对象的地址,在静态成员函数中,就可以利用这个变量来调用非静态成员函数了。下面程序是对前面程序的改进,其中没有直接调用windowAPI

程序 69

#include 

class ZWindow;

ZWindow* g_pWnd = NULL;

class ZWindow
{
public:
    HWND m_hWnd;

    ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }

    inline void Attach(HWND hWnd)
    { m_hWnd = hWnd; }

    inline BOOL ShowWindow(int nCmdShow)
    { return ::ShowWindow(m_hWnd, nCmdShow); }

    inline BOOL UpdateWindow()
    {  return ::UpdateWindow(m_hWnd); }

    inline HDC BeginPaint(LPPAINTSTRUCT ps)
    {  return ::BeginPaint(m_hWnd, ps); }

    inline BOOL EndPaint(LPPAINTSTRUCT ps)
    {  return ::EndPaint(m_hWnd, ps); }

    inline BOOL GetClientRect(LPRECT rect)
    {  return ::GetClientRect(m_hWnd, rect); }

    BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance, 
                HWND hWndParent = 0,    DWORD dwStyle = WS_OVERLAPPEDWINDOW, 
                DWORD dwExStyle = 0, HMENU hMenu = 0)
    {
        m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle, 
                                  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
                                  CW_USEDEFAULT, hWndParent, hMenu, hInstance, NULL);

        return m_hWnd != NULL;
    }

    static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        ZWindow* pThis = g_pWnd;
        HDC hDC;
        PAINTSTRUCT ps;
        RECT rect;

        switch (uMsg)
        {
        case WM_PAINT:
            hDC = pThis->BeginPaint(&ps);
            pThis->GetClientRect(&rect);
            ::DrawText(hDC, "Hello world", -1, &rect, 
                       DT_CENTER | DT_VCENTER | DT_SINGLELINE);
            pThis->EndPaint(&ps);
            break;

        case WM_DESTROY:
            ::PostQuitMessage(0);
            break;
        }

        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   LPSTR lpCmdLine,   int nCmdShow)
{
    char szAppName[] = "Hello world";
    MSG msg;
    WNDCLASS wnd;
    ZWindow zwnd;
    
    wnd.cbClsExtra    = NULL;
    wnd.cbWndExtra    = NULL;
    wnd.hbrBackground    = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wnd.hCursor    = LoadCursor(NULL, IDC_ARROW);
    wnd.hIcon        = LoadIcon(NULL, IDI_APPLICATION);
    wnd.hInstance    = hInstance;
    wnd.lpfnWndProc    = zwnd.WndProc;
    wnd.lpszClassName    = szAppName;
    wnd.lpszMenuName    = NULL;
    wnd.style        = CS_HREDRAW | CS_VREDRAW;
    
    if (!RegisterClass(&wnd))
    {
        MessageBox(NULL, "Can not register window class", "Error", 
                   MB_OK | MB_ICONINFORMATION);
        return -1;
    }

    g_pWnd = &zwnd;
    zwnd.Create(szAppName, "Hell world", hInstance);
    zwnd.ShowWindow(nCmdShow);
    zwnd.UpdateWindow();

    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }
    
    return msg.wParam;
}

终于,我们有了可以运行的版本。现在让我们利用一下面向对象编程的优势。 如果我们在每个消息中都调用这个函数,我们可以将其写为虚函数,通过继承ZWindow类,我们可以调用者些函数。于是,我们就可以特化zwindow的默认行为了。看看下面的例子:

static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, 
                                LPARAM lParam)
{
    ZWindow* pThis = g_pWnd;

    switch (uMsg)
    {
    case WM_CREATE:
        pThis->OnCreate(wParam, lParam);
        break;

    case WM_PAINT:
        pThis->OnPaint(wParam, lParam);
        break;

    case WM_DESTROY:
        ::PostQuitMessage(0);
        break;
    }

    return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}

这里的OnCreateOnPaint都是虚函数。如果我们继承ZWindow类,我们可以重写那些想要特化的函数,下面的程序显示了如何在子类中调用OnPaint函数。

程序 70

#include 

class ZWindow;

ZWindow* g_pWnd = NULL;

class ZWindow
{
public:
    HWND m_hWnd;

    ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }

    inline void Attach(HWND hWnd)
    { m_hWnd = hWnd; }

    inline BOOL ShowWindow(int nCmdShow)
    { return ::ShowWindow(m_hWnd, nCmdShow); }

    inline BOOL UpdateWindow()
    {  return ::UpdateWindow(m_hWnd); }

    inline HDC BeginPaint(LPPAINTSTRUCT ps)
    {  return ::BeginPaint(m_hWnd, ps); }

    inline BOOL EndPaint(LPPAINTSTRUCT ps)
    {  return ::EndPaint(m_hWnd, ps); }

    inline BOOL GetClientRect(LPRECT rect)
    {  return ::GetClientRect(m_hWnd, rect); }

    BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance, 
                HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW, 
                DWORD dwExStyle = 0, HMENU hMenu = 0)
    {
        m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle, 
                                  CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, 
                                  CW_USEDEFAULT, hWndParent, hMenu, hInstance, NULL);
        return m_hWnd != NULL;
    }

    virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
    {
        HDC hDC;
        PAINTSTRUCT ps;
        RECT rect;

        hDC = BeginPaint(&ps);
        GetClientRect(&rect);
        ::DrawText(hDC, "Hello world", -1, &rect, 
                   DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        EndPaint(&ps);
        return 0;
    }

    virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam)
    {
        return 0;
    }

    static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, 
                                    LPARAM lParam)
    {
        ZWindow* pThis = g_pWnd;

        switch (uMsg)
        {
        case WM_CREATE:
            pThis->OnCreate(wParam, lParam);
            break;

        case WM_PAINT:
            pThis->OnPaint(wParam, lParam);
            break;

        case WM_DESTROY:
            ::PostQuitMessage(0);
            break;
        }

        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
};

class ZDriveWindow : public ZWindow
{
public:
    LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
    {
        HDC hDC;
        PAINTSTRUCT ps;
        RECT rect;

        hDC = BeginPaint(&ps);
        GetClientRect(&rect);
        SetBkMode(hDC, TRANSPARENT);
        DrawText(hDC, "Hello world From Drive", -1, &rect, 
                 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        EndPaint(&ps);

        return 0;
    }
};

这个程序的输出是 "Hello world from Drive"。在一个派生类上操作,一切都工作的很好,当我们从ZWindow继承不止一个累时,问题就出现了。所有的消息都跑到了最后一个派生类的消息循环里了。我们看下面的例子:

程序 71

#include 

class ZWindow;

ZWindow* g_pWnd = NULL;

class ZWindow
{
public:
    HWND m_hWnd;

    ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }

    inline void Attach(HWND hWnd)
    { m_hWnd = hWnd; }

    inline BOOL ShowWindow(int nCmdShow)
    { return ::ShowWindow(m_hWnd, nCmdShow); }

    inline BOOL UpdateWindow()
    {  return ::UpdateWindow(m_hWnd); }

    inline HDC BeginPaint(LPPAINTSTRUCT ps)
    {  return ::BeginPaint(m_hWnd, ps); }

    inline BOOL EndPaint(LPPAINTSTRUCT ps)
    {  return ::EndPaint(m_hWnd, ps); }

    inline BOOL GetClientRect(LPRECT rect)
    {  return ::GetClientRect(m_hWnd, rect); }

    BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance, 
                HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW, 
                DWORD dwExStyle = 0, HMENU hMenu = 0, int x = CW_USEDEFAULT, 
                int y = CW_USEDEFAULT, int nWidth = CW_USEDEFAULT, 
                int nHeight = CW_USEDEFAULT)
    {
        m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle, 
                                   x, y, nWidth, nHeight, hWndParent, hMenu, 
                                   hInstance, NULL);
        return m_hWnd != NULL;
    }

    virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
    {
        HDC hDC;
        PAINTSTRUCT ps;
        RECT rect;

        hDC = BeginPaint(&ps);
        GetClientRect(&rect);
        ::DrawText(hDC, "Hello world", -1, &rect, 
                   DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        EndPaint(&ps);
        return 0;
    }

    virtual LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)
    {
        return 0;
    }

    virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam)
    {
        return 0;
    }

    virtual LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam)
    {
        return 0;
    }

    static LRESULT CALLBACK StartWndProc(HWND hWnd, UINT uMsg, 
                                          WPARAM wParam, LPARAM lParam)
    {
        ZWindow* pThis = g_pWnd;

        if (uMsg == WM_NCDESTROY)
            ::PostQuitMessage(0);

        switch (uMsg)
        {
        case WM_CREATE:
            pThis->OnCreate(wParam, lParam);
            break;

        case WM_PAINT:
            pThis->OnPaint(wParam, lParam);
            break;

        case WM_LBUTTONDOWN:
            pThis->OnLButtonDown(wParam, lParam);
            break;

        case WM_KEYDOWN:
            pThis->OnKeyDown(wParam, lParam);
            break;

        case WM_DESTROY:
            ::PostQuitMessage(0);
            break;
        }

        return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
};

class ZDriveWindow1 : public ZWindow
{
public:
    LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
    {
        HDC hDC;
        PAINTSTRUCT ps;
        RECT rect;

        hDC = BeginPaint(&ps);
        GetClientRect(&rect);
        ::SetBkMode(hDC, TRANSPARENT);
        ::DrawText(hDC, "ZDriveWindow1", -1, &rect, 
                   DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        EndPaint(&ps);

        return 0;
    }

    LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)
    {
        ::MessageBox(NULL, "ZDriveWindow1::OnLButtonDown", "Msg", MB_OK);
        return 0;
    }

};

class ZDriveWindow2 : public ZWindow
{
public:
    LRESULT OnPaint(WPARAM wParam, LPARAM lParam)
    {
        HDC hDC;
        PAINTSTRUCT ps;
        RECT rect;

        hDC = BeginPaint(&ps);
        GetClientRect(&rect);
        ::SetBkMode(hDC, TRANSPARENT);
        ::Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);
        ::DrawText(hDC, "ZDriveWindow2", -1, &rect,
                   DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        EndPaint(&ps);

        return 0;
    }

    LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)
    {
        ::MessageBox(NULL, "ZDriveWindow2::OnLButtonDown", "Msg", MB_OK);
        return 0;
    }

};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                    LPSTR lpCmdLine,   int nCmdShow)
{
    char szAppName[] = "Hello world";
    MSG msg;
    WNDCLASS wnd;
    ZDriveWindow1 zwnd1;
    ZDriveWindow2 zwnd2;
    
    wnd.cbClsExtra        = NULL;
    wnd.cbWndExtra        = NULL;
    wnd.hbrBackground        = (HBRUSH)GetStockObject(GRAY_BRUSH);
    wnd.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wnd.hIcon            = LoadIcon(NULL, IDI_APPLICATION);
    wnd.hInstance        = hInstance;
    wnd.lpfnWndProc        = ZWindow::StartWndProc;
    wnd.lpszClassName        = szAppName;
    wnd.lpszMenuName        = NULL;
    wnd.style            = CS_HREDRAW | CS_VREDRAW;
    
    if (!RegisterClass(&wnd))
    {
        MessageBox(NULL, "Can not register window class", "Error", 
                     MB_OK | MB_ICONINFORMATION);
        return -1;
    }

    g_pWnd = &zwnd1;
    zwnd1.Create(szAppName, "Hell world", hInstance);

    zwnd1.ShowWindow(nCmdShow);
    zwnd1.UpdateWindow();

    g_pWnd = &zwnd2;

    zwnd2.Create(szAppName, "Hello world", hInstance, zwnd1.m_hWnd, 
        WS_VISIBLE | WS_CHILD | ES_MULTILINE, NULL, NULL, 0, 0, 150, 150);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }
    
    return msg.wParam;
}

无论你点击哪个窗口,这个程序输出同样的消息框。这就是说,消息没有被合适的传送给相应的窗体。实际上,每个窗口都有自己的消息过程,来处理相应窗口的消息。但这里的第一个窗体也用了第二个派生类的消息循环函数,从而导致第一个窗口的消息无法执行。

这里的关键问题是:我们必须将特定的消息循环与特定的窗口相联系。也就意味着,HWND必须与特定的派生类相关联,从而使消息走到特定的窗口。为实现这个功能,有很多方法可以选择,我们下面分别给予阐述:

第一种简单的思路是:构造一个全局的结构,该结构存储窗口句柄和对应的派生类地址。但有两个重要问题,首先是当程序中窗口越来越多时,这个结构将越来越大;其次是这种方法涉及到全局搜索的问题,而在该结构非常大的情况下,时间开销是个问题。(我补充了下面的例子,已经在vs2005里编译通过,请看下面的程序:)

#include


class ZWindow;


typedef struct _Node{

ZWindow * g_pWnd;

HWND m_hwnd;

_Node * next;

}Node;


Node * pheader = NULL;


Node * pcurrent = NULL;


ZWindow * current_wnd = NULL;


class ZWindow

{

public:

HWND m_hWnd;


ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }


inline void Attach(HWND hWnd)

{ m_hWnd = hWnd; }


inline BOOL ShowWindow(int nCmdShow)

{ return ::ShowWindow(m_hWnd, nCmdShow); }


inline BOOL UpdateWindow()

{ return ::UpdateWindow(m_hWnd); }


inline HDC BeginPaint(LPPAINTSTRUCT ps)

{ return ::BeginPaint(m_hWnd, ps); }


inline BOOL EndPaint(LPPAINTSTRUCT ps)

{ return ::EndPaint(m_hWnd, ps); }


inline BOOL GetClientRect(LPRECT rect)

{ return ::GetClientRect(m_hWnd, rect); }


BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance,

HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW,

DWORD dwExStyle = 0, HMENU hMenu = 0, int x = CW_USEDEFAULT,

int y = CW_USEDEFAULT, int nWidth = CW_USEDEFAULT,

int nHeight = CW_USEDEFAULT)

{

m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle,

x, y, nWidth, nHeight, hWndParent, hMenu,

hInstance, NULL);


Node* pnode = new Node();

pnode->g_pWnd = this;

pnode->m_hwnd = this->m_hWnd;

pnode->next = NULL;


Node * pNode = pheader;


if(pheader == NULL){

pheader = pnode;

pcurrent = pheader;

}

else{

pcurrent->next = pnode;

pcurrent = pcurrent->next;

}


return m_hWnd != NULL;

}


virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

RECT rect;


hDC = BeginPaint(&ps);

GetClientRect(&rect);

::DrawText(hDC, "Hello world", -1, &rect,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

EndPaint(&ps);

return 0;

}


virtual LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)

{

return 0;

}


virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam)

{

return 0;

}


virtual LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam)

{

return 0;

}


static LRESULT CALLBACK StartWndProc(HWND hWnd, UINT uMsg,

WPARAM wParam, LPARAM lParam)

{

ZWindow* pThis = NULL;


Node * pthisnode = pheader;


while(pthisnode != NULL && pthisnode->m_hwnd != hWnd)

pthisnode = pthisnode->next;


if(pthisnode != NULL)

pThis = pthisnode->g_pWnd;


if(pThis == NULL)

pThis = current_wnd;



if (uMsg == WM_NCDESTROY)

::PostQuitMessage(0);


switch (uMsg)

{

case WM_CREATE:

pThis->OnCreate(wParam, lParam);

break;


case WM_PAINT:

pThis->OnPaint(wParam, lParam);

break;


case WM_LBUTTONDOWN:

pThis->OnLButtonDown(wParam, lParam);

break;


case WM_KEYDOWN:

pThis->OnKeyDown(wParam, lParam);

break;


case WM_DESTROY:

::PostQuitMessage(0);

break;

}


return ::DefWindowProc(hWnd, uMsg, wParam, lParam);

}

};


class ZDriveWindow1 : public ZWindow

{

public:

LRESULT OnPaint(WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

RECT rect;


hDC = BeginPaint(&ps);

GetClientRect(&rect);

::SetBkMode(hDC, TRANSPARENT);

::DrawText(hDC, "ZDriveWindow1", -1, &rect,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

EndPaint(&ps);


return 0;

}


LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)

{

::MessageBox(NULL, "ZDriveWindow1::OnLButtonDown", "Msg", MB_OK);

return 0;

}


};


class ZDriveWindow2 : public ZWindow

{

public:

LRESULT OnPaint(WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

RECT rect;


hDC = BeginPaint(&ps);

GetClientRect(&rect);

::SetBkMode(hDC, TRANSPARENT);

::Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);

::DrawText(hDC, "ZDriveWindow2", -1, &rect,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

EndPaint(&ps);


return 0;

}


LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)

{

::MessageBox(NULL, "ZDriveWindow2::OnLButtonDown", "Msg", MB_OK);

return 0;

}


};


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

char szAppName[] = "Hello world";

MSG msg;

WNDCLASS wnd;

ZDriveWindow1 zwnd1;

ZDriveWindow2 zwnd2;

wnd.cbClsExtra = NULL;

wnd.cbWndExtra = NULL;

wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);

wnd.hCursor = LoadCursor(NULL, IDC_ARROW);

wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wnd.hInstance = hInstance;

wnd.lpfnWndProc = ZWindow::StartWndProc;

wnd.lpszClassName = szAppName;

wnd.lpszMenuName = NULL;

wnd.style = CS_HREDRAW | CS_VREDRAW;

if (!RegisterClass(&wnd))

{

MessageBox(NULL, "Can not register window class", "Error",

MB_OK | MB_ICONINFORMATION);

return -1;

}


current_wnd = &zwnd1;

zwnd1.Create(szAppName, "Hell world", hInstance);


zwnd1.ShowWindow(nCmdShow);

zwnd1.UpdateWindow();



current_wnd = &zwnd2;

zwnd2.Create(szAppName, "Hello world", hInstance, zwnd1.m_hWnd,

WS_VISIBLE | WS_CHILD | ES_MULTILINE, NULL, NULL, 0, 0, 150, 150);


while (GetMessage(&msg, NULL, 0, 0))

{

DispatchMessage(&msg);

}

return msg.wParam;

}


该程序与前一个程序相比较,有以下不同:

首先,构造了全局的节点,用来存储派生类和窗口的组合,然后构造一个链表,记录其头部和当前位置。同时,还需要一个全局的派生类指针来引导窗口过程进入相应的类,来初始化全局节点。

ypedef struct _Node{

ZWindow * g_pWnd;

HWND m_hwnd;

_Node * next;

}Node;


Node * pheader = NULL;


Node * pcurrent = NULL;


ZWindow * current_wnd = NULL;


(由上面程序可以看出,我们在刚刚创建窗口时,是利用全局的派生类指针作为标识,进行窗口过程和派生类的关联;而在基类的Create函数中(注意该函数不是虚函数,所以子类不可以重写,但子类将继承该函数)进行全局链表的初始化。之后,所有的窗口消息将有这个链表中类和窗口的关联性来连接。)

ATL的主要目标是让代码尽可能少,执行速度尽可能快。但上述的方法却连其中任何一点都做不到。上述的方法不仅很慢,而且在窗口很多的情况下会消耗大量内存。

另一种可能的方案是,利用WNDCLASS 或者 WNDCLASSEX结构中的cbWndExtra字段。同时,我们要注意一个细节,为何没有用cbClsExtra而用cbWndExtra 答案很简单,cbClsExtra为每个类存储额外字节,而cbWndExtra为类中每个窗口存储附加字节。所以,你无法用cbClsExtra区分统一各类的多个窗口。我们只需要在cbWndExtra中存储适当的窗口类信息。 (我补充了下面的例子,已经在vs2005里编译通过,请看下面的程序:)

#include“windows.h”

class ZWindow

{

public:

HWND m_hWnd;


ZWindow(HWND hWnd = 0) : m_hWnd(hWnd) { }


inline void Attach(HWND hWnd)

{ m_hWnd = hWnd; }


inline BOOL ShowWindow(int nCmdShow)

{ return ::ShowWindow(m_hWnd, nCmdShow); }


inline BOOL UpdateWindow()

{ return ::UpdateWindow(m_hWnd); }


inline HDC BeginPaint(LPPAINTSTRUCT ps)

{ return ::BeginPaint(m_hWnd, ps); }


inline BOOL EndPaint(LPPAINTSTRUCT ps)

{ return ::EndPaint(m_hWnd, ps); }


inline BOOL GetClientRect(LPRECT rect)

{ return ::GetClientRect(m_hWnd, rect); }


BOOL Create(LPCTSTR szClassName, LPCTSTR szTitle, HINSTANCE hInstance,

HWND hWndParent = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW,

DWORD dwExStyle = 0, HMENU hMenu = 0, int x = CW_USEDEFAULT,

int y = CW_USEDEFAULT, int nWidth = CW_USEDEFAULT,

int nHeight = CW_USEDEFAULT)

{

m_hWnd = ::CreateWindowEx(dwExStyle, szClassName, szTitle, dwStyle,

x, y, nWidth, nHeight, hWndParent, hMenu,

hInstance, NULL);


::SetWindowLong(m_hWnd,GWL_USERDATA,(LONG)this);


return m_hWnd != NULL;

}


virtual LRESULT OnPaint(WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

RECT rect;


hDC = BeginPaint(&ps);

GetClientRect(&rect);

::DrawText(hDC, "Hello world", -1, &rect,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

EndPaint(&ps);

return 0;

}


virtual LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)

{

return 0;

}


virtual LRESULT OnCreate(WPARAM wParam, LPARAM lParam)

{

return 0;

}


virtual LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam)

{

return 0;

}


static LRESULT CALLBACK StartWndProc(HWND hWnd, UINT uMsg,

WPARAM wParam, LPARAM lParam)

{

ZWindow* pThis = NULL;


pThis = (ZWindow*)::GetWindowLong(hWnd,GWL_USERDATA);


if (uMsg == WM_NCDESTROY)

::PostQuitMessage(0);


switch (uMsg)

{

case WM_CREATE:

pThis->OnCreate(wParam, lParam);

break;


case WM_PAINT:

pThis->OnPaint(wParam, lParam);

break;


case WM_LBUTTONDOWN:

pThis->OnLButtonDown(wParam, lParam);

break;


case WM_KEYDOWN:

pThis->OnKeyDown(wParam, lParam);

break;


case WM_DESTROY:

::PostQuitMessage(0);

break;

}


return ::DefWindowProc(hWnd, uMsg, wParam, lParam);

}

};


class ZDriveWindow1 : public ZWindow

{

public:

LRESULT OnPaint(WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

RECT rect;


hDC = BeginPaint(&ps);

GetClientRect(&rect);

::SetBkMode(hDC, TRANSPARENT);

::DrawText(hDC, "ZDriveWindow1", -1, &rect,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

EndPaint(&ps);


return 0;

}


LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)

{

::MessageBox(NULL, "ZDriveWindow1::OnLButtonDown", "Msg", MB_OK);

return 0;

}


};


class ZDriveWindow2 : public ZWindow

{

public:

LRESULT OnPaint(WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

RECT rect;


hDC = BeginPaint(&ps);

GetClientRect(&rect);

::SetBkMode(hDC, TRANSPARENT);

::Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);

::DrawText(hDC, "ZDriveWindow2", -1, &rect,

DT_CENTER | DT_VCENTER | DT_SINGLELINE);

EndPaint(&ps);


return 0;

}


LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam)

{

::MessageBox(NULL, "ZDriveWindow2::OnLButtonDown", "Msg", MB_OK);

return 0;

}


};


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

char szAppName[] = "Hello world";

MSG msg;

WNDCLASS wnd;

ZDriveWindow1 zwnd1;

ZDriveWindow2 zwnd2;

wnd.cbClsExtra = NULL;

wnd.cbWndExtra = sizeof(int);

wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);

wnd.hCursor = LoadCursor(NULL, IDC_ARROW);

wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wnd.hInstance = hInstance;

wnd.lpfnWndProc = ZWindow::StartWndProc;

wnd.lpszClassName = szAppName;

wnd.lpszMenuName = NULL;

wnd.style = CS_HREDRAW | CS_VREDRAW;

if (!RegisterClass(&wnd))

{

MessageBox(NULL, "Can not register window class", "Error",

MB_OK | MB_ICONINFORMATION);

return -1;

}


zwnd1.Create(szAppName, "Hell world", hInstance);


zwnd1.ShowWindow(nCmdShow);

zwnd1.UpdateWindow();



zwnd2.Create(szAppName, "Hello world", hInstance, zwnd1.m_hWnd,

WS_VISIBLE | WS_CHILD | ES_MULTILINE, NULL, NULL, 0, 0, 150, 150);


while (GetMessage(&msg, NULL, 0, 0))

{

DispatchMessage(&msg);

}

return msg.wParam;

}



该程序与程序71相比,仅仅是在基类的Create函数中,添加了下面语句:

::SetWindowLong(m_hWnd,GWL_USERDATA,(LONG)this);

同时,在消息循环中通过下面语句获取当前派生类:

pThis = (Zwindow*)::GetWindowLong(hWnd,GWL_USERDATA);


这种方案比第一种要好一些,但任然有两个问题。第一,如果用户想利用cbWndExtra字段,很可能将导致消息循环的混乱,所以用这种技术的人必须小心谨慎,别丢到这个字段的内容。就算你决定这么做,并且写好文档告诉别人你的库不支持修改cbWndExtra字段,但还有第二个问题,与ATL使用的方法相比,这种方法不够快。

ATL既没有用第一种方法,也没有用第二种方法,而使用了一种叫做“Thunk”的方法。Thunk是执行特定工作的一小段代码,这个术语在一些不同的情况下,你可能听过两种Thunk技术

通用ThunkUniversal Thunking

通用ThunkUniversal Thunking)可以实现16位代码调用32位函数。在Win9xWinNT/2000/XP均可用。这种Thunk又被较为泛型ThunkGeneric Thunking.

一般性ThunkGeneral Thunking

一般性Thunk可以使16位函数调用32位的代码,仅仅在 Win 9x上使用,因为Win NT/2000 / XP是真正的32位操作系统,所以没有理由用16位代码调用32为程序,这种Thunk也被叫做平面ThunkFlat Thunking)。

ATL没有使用这两种里面的任意一种,因为我们在ATL中不涉及16位和32位的混合编程,而只是插入一小段代码来调用合理的窗口过程。

在看ATLthunking技术之前,我们先看看基本概念。看看下面简单例子:

程序 72

#include 
using namespace std;

struct S
{
    char ch;
    int i;
};

int main()
{
    cout << "Size of character = " << sizeof(char) << endl;
    cout << "Size of integer = " << sizeof(int) << endl;
    cout << "Size of structure = " << sizeof(S) << endl;
    return 0;
}

程序输出如下:

Size of character = 1
Size of integer = 4
Size of structure = 8

成员变量的大小之和应该是5而不是8,下面我们再添加一个成员变量,看看如下程序:

程序 73

#include 
using namespace std;

struct S
{
    char ch1;
    char ch2;
    int i;
};

int main()
{
    cout << "Size of character = " << sizeof(char) << endl;
    cout << "Size of integer = " << sizeof(int) << endl;
    cout << "Size of structure = " << sizeof(S) << endl;
    return 0;
}

程序输出结果与前面的一样,那到底是怎么回事呢?我们对前述程序稍作改变,看看内部发生了什么。

程序 74

#include 
using namespace std;

struct S
{
    char ch1;
    char ch2;
    int i;
}s;

int main()
{
    cout << "Address of ch1 = " << (int)&s.ch1 << endl;
    cout << "Address of ch2 = " << (int)&s.ch2 << endl;
    cout << "Address of int = " << (int)&s.i << endl;
    return 0;
}

程序输出结果为:

Address of ch1 = 4683576
Address of ch2 = 4683577
Address of int = 4683580

这是由于结构和联合的成员按字对齐导致的。如果你仔细观察,你会发现在结构外面的变量都存储在可以被4整出的地址处。这是为了提高CPU的性能。所以,这里的结构体存在了4的倍数对应的一个地址 4683576 处,成员变量ch1 当然也是同样的地址. 成员变量 ch2紧挨着这个存储单元存储,而int型的成员变量 I存放在地址为 4683580,为什么不紧挨着放在 4683578处呢,因为这个地址是不可以被4整除的。 一个自然的问题:在4683578 and 4683579中放置的内容是什么呢? 如果变量是局部的,那存放的就是垃圾数据,如果是全局的或者静态的,就是0。看下面程序有助于理解这里所讲的内容:

程序 75

#include 
using namespace std;

struct S
{
    char ch1;
    char ch2;
    int i;
};

int main()
{
    S s = { 'A', 'B', 10};

    void* pVoid = (void*)&s;
    char* pChar = (char*)pVoid;

    cout << (char)*(pChar + 0) << endl;
    cout << (char)*(pChar + 1) << endl;
    cout << (char)*(pChar + 2) << endl;
    cout << (char)*(pChar + 3) << endl;
    cout << (int)*(pChar + 4) << endl;
    return 0;
}

程序输出为:

A
B
¦
¦
10

程序结果显示,在其中存放了垃圾数据。

我们如何才可以避免这种空间浪费呢。有两种方法,一是使用编译器开关/Zp,二是在申明结构体的前面添加语句#pragma

程序 76

#include 
using namespace std;

#pragma pack(push, 1)
struct S
{
    char ch;
    int i;
};
#pragma pack(pop)

int main()
{
    cout << "Size of structure = " << sizeof(S) << endl;
    return 0;
}

程序输出结果为:

Size of structure = 5

这意味着,没有了因字对齐导致的空闲空间存在。实际上,ATL用这种技术来制作thunkATL用一个不进行字节对齐的结构体来存储微处理器的直接机器码。

#pragma pack(push,1)
// structure to store the machine code
struct Thunk
{
    BYTE    m_jmp;          // op code of jmp instruction
    DWORD   m_relproc;      // relative jmp
};
#pragma pack(pop)

这种结构可以存储thunk代码,这些代码执行的速度非常快。我们看看用thunk执行函数的例子。

Program 77

#include 
#include 
using namespace std;

class C;

C* g_pC = NULL;

typedef void(*pFUN)();

#pragma pack(push,1)
// structure to store the machine code
struct Thunk
{
    BYTE    m_jmp;          // op code of jmp instruction
    DWORD   m_relproc;      // relative jmp
};
#pragma pack(pop)

class C
{
public:
    Thunk    m_thunk;

    void Init(pFUN pFun, void* pThis)
    {
        // op code of jump instruction
        m_thunk.m_jmp = 0xe9;
        // address of the appripriate function
        m_thunk.m_relproc = (int)pFun - ((int)this+sizeof(Thunk));

        FlushInstructionCache(GetCurrentProcess(), 
                                &m_thunk, sizeof(m_thunk));
    }

    // this is cour call back function
    static void CallBackFun()
    {
        C* pC = g_pC;

        // initilize the thunk
        pC->Init(StaticFun, pC);

        // get the address of thunk code
        pFUN pFun = (pFUN)&(pC->m_thunk);

        // start executing thunk code which will call StaticFun
        pFun();

        cout << "C::CallBackFun" << endl;
    }

    static void StaticFun()
    {
        cout << "C::StaticFun" << endl;
    }
};

int main()
{
    C objC;
    g_pC = &objC;
    C::CallBackFun();
    return 0;
}

程序输出结果为:

C::StaticFun
C::CallBackFun

StaticFun通过在thunk中初始化,并且通过其调用。程序执行的思路大致是:

  • CallBackFun

  • Init (to initialize the thunk)

  • Get the address of thunk

  • Execute thunk

  • Thunk code call StaticFun



ATL用了同样的技术实现回调函数的调用,但在调之前还做了一件事。ZWindow又加了一个虚函数: ProcessWindowMessage ,这个函数在基类中不做任何事情。但每个派生类都要通过重写这个函数来处理其消息。过程是一样的:我们将ZWindow派生类的地址保存在一个指针中,用这个指针来调用派生类的虚函数,但窗口函数由WindowProc变为StartWndProcATL用该技术将HWND参数用this指针来替代,这样,this指针里包含了相应的HWND,所以,thisHWND信息都将得到保存。

为了实现这个,ATL用了比之前稍微大的结构:

#pragma pack(push,1)
struct _WndProcThunk
{
    DWORD   m_mov;    // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
    DWORD   m_this;
    BYTE    m_jmp;    // jmp WndProc
    DWORD   m_relproc;    // relative jmp
};
#pragma pack(pop)

在初始化时,写入汇编代码: "mov dword ptr [esp +4], pThis"。大概是这样的:

void Init(WNDPROC proc, void* pThis)
{
    thunk.m_mov = 0x042444C7;  //C7 44 24 04
    thunk.m_this = (DWORD)pThis;
    thunk.m_jmp = 0xe9;
    thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));

    FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));
}

初始化thunk代码后,获取thunk地址,并且将新的窗口过程地址设为thunk地址,之后,thunk代码将调用callWindowProc函数,而此时的第一个参数由HWND变成了this指针。所以,我们可以安全地将其转化为ZWindow*类型,然后调用 ProcessWindowMessage 函数。

static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, 
                                   WPARAM wParam, LPARAM lParam)
{
    ZWindow* pThis = (ZWindow*)hWnd;

    if (uMsg == WM_NCDESTROY)
        PostQuitMessage(0);

    if (!pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam))
        return ::DefWindowProc(pThis->m_hWnd, uMsg, wParam, lParam);
    else
        return 0;
}
现在,每个窗口都会别适当的窗口过程处理。

-------------------------------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------华丽的分割线--本系列结束----------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------

你可能感兴趣的:(windows)