cpp WinAPI WndProc封装在类中

cpp WinAPI WndProc wrapped in the class

本文关键字:封装 WinAPI WndProc cpp      更新时间:2023-10-16

我想创建一个类来动态实例化窗口,但由于几周后无法正常工作,将非常感谢您的帮助

由于我无法使用GetObjectFromHandle函数检索正确的数据,我尝试使用std::map来存储类实例,在构造函数中,我可以按预期访问映射中的数据,但在HWND-HWND正确的情况下,从消息循环中,我只能访问垃圾。

这是代码

.h

#ifndef BASE_WINDOW_H
#define BASE_WINDOW_H
#include "GlobalApp.h"
#include <string>
#include <map>
namespace G{
    class cWin;
    static void SetObjectToHandle( HWND hWnd, LPARAM lParam );
    static cWin *GetObjectFromHandle( HWND hWnd );
    class cWin{
        static LRESULT CALLBACK internal_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    public:
        static std::map<HWND, cWin*> hwndMap;
        LRESULT WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        cWin();
        int     registerCls();
        HWND    createWnd();
        HWND    hWnd;
    };
}
#endif

和.cpp

#include "stdafx.h"
#include "BaseWindow.h"
HWND G::cWin::createWnd(){
    HWND hWnd;
    hWnd = ::CreateWindowEx(WS_EX_WINDOWEDGE, L"div", NULL,
        WS_CHILD | WS_VISIBLE,
        50, 50, 50, 50,
        G::hWnd, NULL, G::hInst, this );
    ::UpdateWindow( hWnd );
    return hWnd;
}
int G::cWin::registerCls(){
    WNDCLASSEX wcex;
    if ( !::GetClassInfoEx(G::hInst, L"div", &wcex) ){
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style          = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = (WNDPROC)this->internal_WndProc;
        wcex.cbClsExtra     = 0;
        wcex.cbWndExtra     = DLGWINDOWEXTRA;
        wcex.hInstance      = G::hInst;
        wcex.hIcon          = LoadIcon(G::hInst, MAKEINTRESOURCE(IDI_WINAPITWO));
        wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+3);
        wcex.lpszMenuName   = NULL;
        wcex.lpszClassName  = L"div";
        wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
        if ( ::RegisterClassEx(&wcex) == 0 ){
            G::Console( L"wcex_ERR" );
            return -1;
        }
    }
    return 0;
}
G::cWin::cWin(){
    this->registerCls();
    this->hWnd = this->createWnd();
    G::Console( L"wndCreated", this->hWnd );
    this->hwndMap.insert( std::pair< HWND, G::cWin*>( this->hWnd, this ) );
    G::cWin *pWnd = this->hwndMap[ this->hWnd ];
    G::Console( L"map:", pWnd->hWnd ); //point to correct data
}
LRESULT G::cWin::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
    if ( !this->hwndMap.count( hWnd ) ){
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    G::cWin *pWnd = this->hwndMap[ hWnd ];
    switch (message){
        case WM_LBUTTONDOWN:
            G::Console( L"ButtonDown", pWnd->hWnd ); // not correct, why?
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
std::map<HWND, G::cWin*> G::cWin::hwndMap;

LRESULT CALLBACK G::cWin::internal_WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){
    if( uMsg == WM_NCCREATE ){
        G::SetObjectToHandle( hWnd, lParam );
    }
    G::cWin *pWnd = G::GetObjectFromHandle( hWnd );
    if( pWnd ){
        return pWnd->WindowProc( hWnd, uMsg, wParam, lParam );
    } else
        return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
void G::SetObjectToHandle( HWND hWnd, LPARAM lParam ){
    LPCREATESTRUCT cs = reinterpret_cast<LPCREATESTRUCT>( lParam );
    G::cWin *pWnd = reinterpret_cast<G::cWin*>( cs->lpCreateParams );
    SetLastError( 0 );
    if( !SetWindowLongPtr( hWnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>( pWnd ) ) && GetLastError() ){
        G::Console( L"Error" );
    }
}
G::cWin *G::GetObjectFromHandle( HWND hWnd ){
    return reinterpret_cast<G::cWin*>( GetWindowLongPtr( hWnd, GWL_USERDATA ) );
}

我使用visual studio 2005

您缺少一个析构函数来销毁HWND并清除对它的任何引用。HWND在销毁后可以重用。如果您不从std::map中删除已销毁的HWND,那么您最终将得到过时的cWin*指针。

就这一点而言,您的std::map是不必要的。在访问std::map之前,您依赖于GetObjectFromHandle()返回一个有效的cWin*指针,但您认为GetObjectFromHandle()一开始工作不正常。所以只要去掉std::map,你就不需要它了

试试类似的东西:

.h

#ifndef BASE_WINDOW_H
#define BASE_WINDOW_H
#include "GlobalApp.h"
#include <string>
namespace G
{
    class cWin
    {
    private:
        HWND hWnd;
        LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
        static LRESULT CALLBACK internal_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    public:
        cWin();
        ~cWin();
        HWND    getWnd();
        int     registerCls();
        int     createWnd();
    };
}
#endif

.cpp

#include "stdafx.h"
#include "BaseWindow.h"
G::cWin::cWin()
{
    registerCls();
    createWnd();
    G::Console( L"wndCreated", hWnd );
}
G::cWin::~cWin()
{
    if ( hWnd )
    {
        DestroyWindow( hWnd );
    }
}
HWND G::cWin::getWnd()
{
    return hWnd;
}
int G::cWin::createWnd()
{
    hWnd = ::CreateWindowEx(WS_EX_WINDOWEDGE, L"div", NULL,
        WS_CHILD | WS_VISIBLE,
        50, 50, 50, 50,
        G::hWnd, NULL, G::hInst, this );
    if ( !hWnd )
    {
        G::Console( L"hwnd_ERR" );
        return -1;
    }
    ::UpdateWindow( hWnd );
    return 0;
}
int G::cWin::registerCls()
{
    WNDCLASSEX wcex;
    if ( !::GetClassInfoEx(G::hInst, L"div", &wcex) )
    {
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style          = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = &internal_WndProc;
        wcex.cbClsExtra     = 0;
        wcex.cbWndExtra     = DLGWINDOWEXTRA;
        wcex.hInstance      = G::hInst;
        wcex.hIcon          = LoadIcon(G::hInst, MAKEINTRESOURCE(IDI_WINAPITWO));
        wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+3);
        wcex.lpszMenuName   = NULL;
        wcex.lpszClassName  = L"div";
        wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
        if ( !::RegisterClassEx(&wcex) )
        {
            G::Console( L"wcex_ERR" );
            return -1;
        }
    }
    return 0;
}
LRESULT G::cWin::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_LBUTTONDOWN:
            G::Console( L"ButtonDown", hWnd );
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        case WM_NCDESTROY:
            this->hWnd = NULL;
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
LRESULT CALLBACK G::cWin::internal_WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    G::cWin *pWnd;
    if( uMsg == WM_NCCREATE )
    {
        LPCREATESTRUCT cs = reinterpret_cast<LPCREATESTRUCT>( lParam );
        pWnd = reinterpret_cast<G::cWin*>( cs->lpCreateParams );
        SetLastError( 0 );
        if( !SetWindowLongPtr( hWnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>( pWnd ) ) )
        {
            if( GetLastError() != 0 )
                G::Console( L"Error" );
        }
    }
    else
    {
        pWnd = reinterpret_cast<G::cWin*>( GetWindowLongPtr( hWnd, GWL_USERDATA ) );
    }
    if( pWnd )
    {
        return pWnd->WindowProc( uMsg, wParam, lParam );
    }
    else
    {
        return DefWindowProc( hWnd, uMsg, wParam, lParam );
    }
}

类的成员函数不是类实例的一部分。您应该看看类成员函数的工作方式:

class Foo {
};
namespace Foo {
    void Bar(Foo *this, ...);
};

当然,命名空间不可能与具有相同名称的类在同一范围内。这只是为了举例说明。定义类成员函数时,编译器将其翻译为"一个放置在类定义的命名空间中的函数,它有一个第一个隐式参数this,该参数设置为用.->运算符调用该函数的类实例的指针。static类成员函数只放置在类命名空间中,但没有隐式this参数……这就是全部区别。

所以在这条线上

    wcex.lpfnWndProc    = (WNDPROC)this->internal_WndProc;

问题是,没有实例成员"函数"变量internal_WandProc。您只需要使用类名称空间中的函数:

    wcex.lpfnWndProc    = G::cWin::internal_WndProc;