游戏开发论坛

 找回密码
 立即注册
搜索
查看: 1012|回复: 0

ATL布幔之下的秘密1wxh zt

[复制链接]

1367

主题

1993

帖子

2118

积分

金牌会员

Rank: 6Rank: 6

积分
2118
发表于 2004-12-31 22:22:00 | 显示全部楼层 |阅读模式
   很多人认为ATL只是用来编写COM组件的,其实你也可以使用ATL中的窗口类来创建基于窗口的应用程序。虽然你可以将基于MFC的程序转换为ATL,但是ATL中对于UI(译注:用户界面)组件的支持太少了。所以,这就要求你需要自己编写很多代码。例如,在ATL中没有文档/视图,所以在你想使用它的时候就需要自己实现了。在本篇中,我们将要探究一些关于窗口类的秘密,以及ATL技术实现的秘密。WTL(Window Template Library,窗口模板库),虽然到现在(译注:本文于2002年10月27日发表在CodeProject)还不为Microsoft所支持,但是它在制作图形应用程序方面跨出了一大步。WTL就是基于ATL的窗口类的。
   在开始讨论基于ATL的程序之前,让我们从一个经典的Hello world程序开始吧。这个程序完全用SDK编写,并且我们中几乎所有人都已经熟悉它了。

程序66.

#include <windows.h>

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放在了一个类里,并且它可以和其它的API相关联,例如字体、文件、菜单等等。所以我编写了一个很小的类,并将所有第一个参数为HWND的API放在了这个类中。也就是说,这个类只是简单地对窗口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。对于这个类来说的唯一优点,就是你不用像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 :efWindowProc(hWnd, uMsg, wParam, lParam);
    }
};
你需要为WNDCLASS或WNDCLASSEX的一个域提供这个回调函数的地址。并且,你需要在创建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++中,如果我们将成员函数声明为static的话,那么编译器就不会传递this指针了。这就是static和非static成员函数实质上的不同。
   所以,我们可以把ZWindow类中的WndProc声明为static成员函数。这一技术也可以用在多线程的情况下,比如当你想要使用成员函数作为一个线程函数的时候,你就可以将一个static成员函数作为线程函数。
   下面就是使用了ZWindow类的更新程序。

程序67.
#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); }

    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参数了,但是你必须得在调用成员函数的时候输入对象的名称。
   对于以前,你这样调用函数:
ShowWindow(hWnd, nCmdShow);
现在,你可以这么做:
zwnd.ShowWindow(nCmdShow);
到现在为止,这并不是一个明显的优点。
   我们来看看在WndProc中如何处理窗口消息。在前一个程序中,我们只处理了一个函数,也就是WM_DESTROY。如果你想要处理更多的消息,那么可以在switch语句中加入更多的case。让我们来修改一下WndProc,处理一下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:
    :ostQuitMessage(0);
    break;
}
这个代码很正确,它会在窗口的正中显示Hello world。但是,为什么要用BeginPaint、GetClientRect和EndPaint这些API呢?根据我们的标准,这些API都应该作为ZWindow的成员函数来使用的——它们的第一个参数都是HWND。
   因为所有这些函数都是非static函数。并且,你不能在static成员函数中调用非static成员函数。为什么呢?因为它们的区别就是this指针,非static成员函数拥有this指针,而static函数没有。如果我们通过某种手段将this指针传递给了static成员函数,那么我们就可以在static成员函数中调用非static成员函数了。让我们看看下面的程序。

程序68.
#include <iostream>
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对象的地址存入一个全局变量,然后利用这个指针调用非static成员函数。下面是前一个程序的更新版本,在其中我们没有直接调用窗口的API。

程序69.
#include <windows.h>

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的默认行为。现在,WndProc是类似这个样子:
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);
}
在这里,OnCreate和OnPaint是虚函数。并且,当我们从ZWindow继承一个类的时候,我们就可以重写所有我们想自定义的这些函数。下面是一个完整的程序,它示范了在派生类中WM_PAINT消息的使用。

程序70.
#include <windows.h>

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);
    }
};
窗口过程就可以被调用了。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-12-23 22:35

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表