如何合并一个 pImpl 接口,同时还允许 WndProc 与其交互?

How to incorporate a pImpl Interface while also allowing WndProc to interact with it?

目前正在 Win32(C/C++ 语言)中使用 Wrapper/GameEngine class 组合开发 2-D 游戏开发环境。就目前而言,我使用 Wrapper 设置和初始化所有带有 Window 的项目,并在进入消息循环之前初始化 GameEngine class。

为此,我将发送到 WndProc(...) 的 Windows 消息重定向到 Wrapper 和 GameEngine classes 中的 HandleEvent(...) 方法。这是通过包装器 class 中的静态私有 shared_ptrs 完成的。一个这样的指针指向包含的 GameEngine,另一个指向 Wrapper 本身。

WndProc 函数示例可能如下所示:

LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // Initialize via wrapper, else route to engine
    switch (msg)
    {
        case WM_CREATE:
            return GEN::Wrapper::GetWrapper()->HandleEvent(hWindow, msg, wParam, lParam);
            break;
        default:
            return GEN::Wrapper::GetEngine()->HandleEvent(hWindow, msg, wParam, lParam) // DefWndProc called from this HandleEvent
    }
}

其中 GetEngine()GetWrapper() 是静态访问器方法 return 它们各自的 shared_ptr。

我想做的是在这个设计中加入 pImpl 习惯用法。也就是说,我想创建一个包装器接口 class,它从正在使用的特定包装器中删除实现细节。困扰我的问题之一是我需要(或者至少认为我需要)用于所讨论的 Wrapper 的静态访问器方法。这是因为正如我所知道的,每个派生的 Wrapper 都以特定于游戏的方式初始化 window,并且 WndProc 需要知道将初始消息转发到哪里,如上面的代码所示。当然,静态方法绑定到包装器 class,因此 GetWrapper() 不可能放入该接口。

基本上,我想像这样声明 WrapperInterface:

class WrapperInterface
{
public:
    static std::tr1::shared_ptr<WrapperInterface> // factory function
        create_Wrapper(...); // returns pImpl

    // ... Pure virtuals here                                                               
};

从 WrapperInterface 公开派生 Wrapper,然后或多或少地实现 create_Wrapper 的原始版本:

std::tr1::shared_ptr<WrapperInterface> WrapperInterface::create_Wrapper(...)
{
    return std::tr1::shared_ptr<WrapperInterface>(new Wrapper(...));
}

所以我可以将这一行放在 WinMain 中:

std::tr1::shared_ptr<WrapperInterface>
      Wrapper(WrapperInterface::create(...));

还有 WndProc 能够将消息转发到接口方法吗?

更新:

我想到存储一个指向 WrapperInterface 本身的静态指针,并 create_wrapper 将该成员变量设置为接口正在使用的任何包装器。然后,我完全消除了 Wrapper 的静态指针,并使 Engine 指针成为非静态指针。不过,这在某种程度上感觉像是在作弊,因为现在我正在向接口中引入一个私有成员变量,尽管它是一个静态变量。任何关于不存储静态的重新设计方法的想法或技巧都会很棒!

无论如何,感谢大家的阅读和任何建议。

您可以将指向实际 Wrapper 对象的指针与它为自己创建的 window 相关联。为此,您可以:

  1. 使用 RegisterClass/Ex() with the cbWndExtra field set to sizeof(Wrapper*) to reserve extra memory inside the HWND, then use SetWindowLong/Ptr() 并将 nIndex 参数设置为 0 以将 Wrapper* 指针值存储在分配的内存中:

    WNDCLASS wc;
    wc.lpszClassName = TEXT("MyWrapperWindow");
    wc.cbWndExtra = sizeof(Wrapper*);
    ...
    RegisterClass(&wc);
    

    hwnd = CreateWindow(TEXT("MyWrapperWindow"), ...);
    SetWindowLongPtr(hwnd, 0, (LONG_PTR) this);
    
  2. 使用 SetWindowsLong/Ptr() 并将 nIndex 参数设置为 GWLP_USERDATA:

    hwnd = CreateWindow(...);
    SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) this);
    
  3. lpString 参数中使用带有自定义名称的 SetProp()

    static const LPCTSTR szMyProp = TEXT("MyProp");
    
    hwnd = CreateWindow(...);
    SetProp(hwnd, szMyProp, (HANDLE) this);
    
  4. 使用 SetWindowSubclass() 和在其 dwRefData 参数中传递的 Wrapper* 指针:

    hwnd = CreateWindow(...);
    SetWindowSubclass(hwnd, &MySubClassProc, 1, (DWORD_PTR) this);
    

至少在情况1-3中(不确定情况4),您可以选择将Wrapper*指针传递给CreateWindow/Ex()lpParam参数,然后调用一个window 过程的 WM_NCCREATEWM_CREATE 处理程序中提到的函数:

hwnd = CreateWindow(..., this);

case WM_CREATE:
{
    Wrapper *pThis = (Wrapper*) ((LPCREATESTRUCT)lParam)->lpCreateParams;
    // SetWindowLongPtr(hwnd, 0, (LONG_PTR) pThis);
    // SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) pThis);
    // SetProp(hwnd, szMyProp, (HANDLE) pThis);
    break;
}

对于所有其他消息,您的 window/subclass 过程可以在需要时提取 Wrapper* 指针。这样,它就不需要使用任何全局静态来寻找对象:

  1. GetWindowLong/Ptr() nIndex 参数设置为 0:

    Wrapper *pThis = (Wrapper*) GetWindowLongPtr(hwnd, 0);
    
  2. GetWindowsLong/Ptr() nIndex 参数设置为 GWLP_USERDATA:

    Wrapper *pThis = (Wrapper*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
    
  3. GetProp(),传递传递给 SetProp() 相同的 lpString 指针(重要!):

    Wrapper *pThis = (Wrapper*) GetProp(hwnd, szMyProp);
    
  4. subclass proceduredwRefData参数:

    Wrapper *pThis = (Wrapper*) dwRefData;