【问题标题】:Win32: More "object oriented" window message handling systemWin32:更“面向对象”的窗口消息处理系统
【发布时间】:2012-01-06 14:42:21
【问题描述】:

在 Win32 API 中,窗口具有指向处理其消息的用户定义版本的 WndProc 函数的指针。

有一些方法可以使用 MFC 消息映射等解决方案来覆盖这种低级机制。

在我非常小的应用程序中,我正在寻找一种方法来用面向对象的解决方案封装这个低级的东西。

我尝试使用 HWND 键和“MyWindowClass”项创建 C++ 映射,当我创建 MyClass 的对象时,我在映射中添加了一对,然后通过 HWN 查找 MyWindowClass 对象。但问题是在调用 Win32 的 CreateWindowEx 内部向刚刚创建的窗口发送 WM_CREATE 消息后,我无法在此消息之前在地图中添加对,也无法通过将 WM_CREATE 传递给实例化的 WndProc 来控制 WM_CREATE。

代码是:

#ifndef NOTIFYWINDOW_H
#define NOTIFYWINDOW_H

#include "Bacn.h"

class NotifyWindow
{
private:

    HWND m_hWnd;

    Gdiplus::Graphics* m_graphics;

protected:

    static std::map<HWND, NotifyWindow*> s_NotifyWindows;

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

    static void s_WndMessageLoop();

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

    void Initialize();

    void OnPaint();

    void OnCreate();

public: 

    NotifyWindow();

    ~NotifyWindow();
};

#endif //NOTIFYWINDOW_H

及其实现:

#include "NotifyWindow.h"

using namespace Gdiplus;
using namespace std;

map<HWND*, NotifyWindow> NotifyWindow::s_NotifyWindows;

LRESULT CALLBACK NotifyWindow::s_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    map<HWND, NotifyWindow*>::iterator search = s_NotifyWindows.find(hWnd);
    if (search != s_NotifyWindows.end())
    {
        search->second->WndProc(uMsg, wParam, lParam);
    }

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

void NotifyWindow::s_WndMessageLoop()
{

}

LRESULT NotifyWindow::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        OnCreate();
        break;
    case WM_PAINT:
        OnPaint();
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_SIZE:
        break;
    case WM_SETFOCUS:
        break;
    case WM_KILLFOCUS:
        break;
    case WM_MOUSEMOVE:
        break;
    default:
        return DefWindowProc(m_hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

void NotifyWindow::Initialize()
{
    WNDCLASSEX wc;

    const wchar_t *className = L"BacnNotifyWindowClass";
    const wchar_t *windowName = L"BacnNotifyWindow";

    HINSTANCE hInstance = GetModuleHandle(NULL);

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpszClassName = className;
    wc.lpfnWndProc = NotifyWindow::s_WndProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = NULL;
    wc.hIconSm = NULL;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;

    RegisterClassEx(&wc);

    DWORD dwExtStyle = WS_EX_TOPMOST;
    DWORD dwStyle = WS_POPUP | WS_SYSMENU;  

    m_hWnd = CreateWindowEx(
        dwExtStyle,
        className,
        windowName,
        dwStyle,
        300,
        300,
        100,
        100,
        NULL,
        NULL,
        hInstance,
        NULL);  

    s_NotifyWindows.insert(pair<HWND, NotifyWindow*>(m_hWnd, this));

    ShowWindow(m_hWnd, SW_SHOW);
}

NotifyWindow::NotifyWindow()
{
    Initialize();   
}

NotifyWindow::~NotifyWindow()
{

}

void NotifyWindow::OnPaint()
{

}

void NotifyWindow::OnCreate()
{

}

【问题讨论】:

标签: c++ oop winapi window


【解决方案1】:

建议:在您的窗口基类中设置WndProc 虚拟。请求 Windows 为每个足够大的窗口实例分配额外的内存以存储指针(使用cbWndExtra)。创建窗口时,使用SetWindowLongPtr 将指向您的窗口类对象的指针放入与每个窗口实例关联的额外内存中。在您的static sWndProc 中,使用GetWindowLongPtr 检索此指针并调用您的窗口基类函数virtual WndProc。在我看来,这比拥有一个专门用于调度 WndProc 调用的整个额外地图对象更简洁。

编辑:另外,您确实意识到,在您的代码中,您每次创建窗口类的对象时都试图注册 Windows 窗口类?如果你只创建一个窗口,我想这在技术上是可以的,但即便如此,它也是容易出错的设计。 Windows 窗口类应该只注册一次,而不是每次使用此 Windows 窗口类创建一个窗口时。

编辑:另外,在您的代码中,如果您没有处理 WndProc 中的 Windows 消息,您的代码将为此消息调用 DefWindowProc 两次:第一次在 switch 子句中的成员函数中,第二次在static sWndProcDefWindowProc 不应在同一条消息上调用两次。

编辑:对不起,我之前不知何故错过了你的实际问题,我认为你的帖子是关于设计的,而不是关于 WM_CREATE。为了以统一的方式处理WM_NCCREATEWM_NCCALCSIZEWM_CREATE,您可以在对CreateWindowEx 的调用中将lpParam 再次设置为指向您的窗口类对象的指针。此参数将作为CREATESTRUCT 的成员与WM_NCCREATEWM_CREATE 一起传递给您的static sWndProc。你可以在static sWndProc中处理,比如说WM_NCCREATE(要发送的第一条消息),得到这个指向你的对象的指针,使用SetWindowLongPtr把它放到窗口实例额外的内存中,然后用它来调用成员函数@ 987654346@(如果您从其构造函数调用CreateWindowEx,请注意调用未完全创建的windows类对象)。这样,您就不必担心程序中其他任何地方的“低级”Windows 消息调度。当然,您仍然需要您的成员函数 WndProc 将消息发送给实际的函数调用。

【讨论】:

  • Borland 的 VCL 框架通过动态分配一块可执行内存作为 WndProc 代理函数来解决这个问题。目标对象指针存储在此代理中。当操作系统调用代理时,代理会检索对象指针并在其上调用虚拟方法来处理消息。这样,即使在接收到第一条消息之前,对象指针就已经准备就绪(对象指针存储在 HWND 中用于其他目的,但不用于消息分派)。消息从操作系统直接发送到对象,而不涉及 HWND。
  • @RemyLebeau-TeamB 总的来说,这是一种处理回调的好方法,但在这种情况下是不是有点太重量级了?它需要了解调用约定、关于“this”指针以及可能的虚拟表的编译器约定的知识、依赖于平台的汇编程序的使用、为每个对象分配更多内存VirtualAlloc()(至少几个指针和汇编程序指令) ) 并且可能执行较慢。
  • 另外,对于这种特殊情况 - 窗口创建,我对您将如何从这种技术中受益感到有些困惑。如果不对您创建的每个窗口实例进行子类化,我就无法找到一种方法来使用您的方法 - 您需要向 Windows 提供驻留在动态分配的内存中的回调地址,每个对象都不同。你需要HWND 子类化一个窗口实例,这意味着你最早可以在WM_NCCREATE 中做这件事。或者你的意思是,有一种方法可以不用子类化每个窗口实例?怎么样?!
  • 本次讨论的主题是面向对象的消息分发。由于 OS WndProc 回调没有对象的概念,因此需要某种代理将接收到的消息传递给对象方法。无论该代理是由RegisterClass()SetWindowLong(GWL_WNDPROC) 分配的普通WndProc 函数,并且对象指针由SetWindowLong(GWL_USERDATA) 存储在HWND 内,还是代理是动态的并将对象指针存储在自身内部,结果是相同。操作系统直接调用代理,然后代理调用对象。
  • 我所描述的可能在代码中实现起来比较繁重,但它并不慢(如果有的话,它非常快),并且有一些技术可以有效地管理虚拟内存(Borland 使用可重用池分配的代理以减少所需的分配数量)。是的,如果你想要一个跨编译器工作的通用实现,它可能有点复杂(我知道,我编写了一个使用动态代理方法的跨编译器对象到回调库)。
猜你喜欢
  • 2022-01-21
  • 1970-01-01
  • 2013-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多