为什么我不能绑定到wndproc?

时间:2013-06-29 16:13:30

标签: winapi visual-studio-2012 c++11 bind

我正在尝试使用C ++ 11来解决我最喜欢的指针问题

LRESULT CALLBACK renderMan::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
//some code
WNDPROC crazy = bind(&renderMan::WindowProc,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3,std::placeholders::_4);

错误

1>renderman.cpp(50): error C2440: 'initializing' : cannot convert from 'std::_Bind<_Forced,_Ret,_Fun,_V0_t,_V1_t,_V2_t,_V3_t,_V4_t,_V5_t,<unnamed-symbol>>' to 'WNDPROC'
1>          with
1>          [
1>              _Forced=true,
1>              _Ret=LRESULT,
1>              _Fun=std::_Pmf_wrap<LRESULT (__cdecl glSurface::* )(HWND,UINT,WPARAM,LPARAM),LRESULT,glSurface,HWND,UINT,WPARAM,LPARAM,std::_Nil,std::_Nil,std::_Nil>,
1>              _V0_t=glSurface *const ,
1>              _V1_t=std::_Ph<1> &,
1>              _V2_t=std::_Ph<2> &,
1>              _V3_t=std::_Ph<3> &,
1>              _V4_t=std::_Ph<4> &,
1>              _V5_t=std::_Nil,
1>              <unnamed-symbol>=std::_Nil
1>          ]
1>          No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

4 个答案:

答案 0 :(得分:3)

问题

WNDPROC是使用__stdcall调用约定指向具有特定签名的函数的指针。

std :: bind返回函数 object 而不是函数指针。虽然在源代码级别,函数对象几乎可以与函数指针一样使用,但事实仍然是它是一种完全不同的类型。没有多少铸件可以解决这个问题。

由于你正在使用renderMan :: WindowProc方法绑定this指针,很明显renderMan :: WindowProc不是静态成员函数,因此它使用thiscall调用约定。因此,即使您可以获得指向它的指针并将其传递给Windows,Windows也不会使用正确的调用约定来调用它。

解决方案

处理此问题的最常用方法是将非成员函数(或静态成员函数)注册为WNDPROC。该函数查找与窗口关联的this指针,并将其转发给您的成员函数。

LRESULT CALLBACK WndProcHelper(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
  renderMan *that = LookUpPointer(hwnd);
  assert(that != nullptr);
  return that->WndProc(hwnd, msg, wp, lp);
}

LookUpPointer的详细信息将根据您的方法而有所不同。听起来你只是通过拥有一个全局变量来解决它,因此只需要一个窗口实例。

如果需要从此窗口类实例化多个窗口,则可以维护一个全局(或类静态)表,将HWND映射到renderMan指针。创建新实例时,将其添加到表中,然后为LookUpPointer提供一个简单的实现:

std::map<HWND, renderMan*> g_windows;

renderMan *LookUpPointer(HWND hwnd) {
  // If there isn't an entry for hwnd in the map, then this will
  // will create one, associating it with nullptr.
  return g_windows[hwnd];
}

这可能会有一些鸡和蛋的问题,因为你会在CreateWindow调用期间得到一些消息,然后才能恢复HWND并有机会添加它和指向地图的指针。例如:

// ... inside a renderMan constructor ...
m_hwnd = CreateWindow(L"renderMan", /* ... */);
g_windows[m_hwnd] = this;  // already too late for some messages

解决鸡和蛋问题的另一种常用方法是将renderMan指针存储在创建参数中,并在WndProcHelper中使用特殊逻辑将其创建时添加到地图中。

// ... inside the renderMan constructor ...
m_hwnd = CreateWindow(L"renderMan", /* ... */, reinterpret_cast<LPVOID>(this));

// ... and then ...
LRESULT CALLBACK WndProcHelper(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
  static std::map<HWND, renderMan*> windows;  // still static, but not global :-)
  LRESULT result = 0;
  renderMan *that = nullptr;
  if (msg == WM_NCCREATE) {
    auto pCreateStruct = reinterpret_cast<const CREATESTRUCT*>(lp);
    that = reinterpret_cast<renderMan*>(pCreateStruct->lpCreateParams);
    windows[hwnd] = that;
  } else {
    that = windows[hwnd];
  }
  if (that != nullptr) {
    result = that->WndProc(hwnd, msg, wp, lp);
  }
  if (msg == WM_NCCDESTROY) {
    windows.erase(hwnd);
  }
  return result;
}

警告:这是一个简单的代码,没有足够的错误检查来说明这个想法。一个干净而完整的实现可以更优雅地处理错误,记录意外的事情(比如缺少条目)等等。

答案 1 :(得分:0)

WNDPROC是一个函数指针,而bind的结果是一个函数对象。正如编译器所说,它不能转换为WNDPROC。

你可以这样做:

auto crazy = bind(.....)
std::function<LRESULT CALLBACK(HWND, UINT, WPARAM, LPARAM)> crazy = bind(...)

但我猜这不能解决你的问题。我认为没有免费功能就没办法做到这一点。也许是这样的:

std::function<LRESULT CALLBACK(HWND, UINT, WPARAM, LPARAM)> crazy;

LRESULT CALLBACK myWindowProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{
    if(!crazy)
        return (LRESULT)nullptr;

    return crazy(hwnd,Msg,wParam,lParam)
}

//and then somewhere in your renderMan:
crazy = bind(&renderMan::WindowProc,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3,std::placeholders::_4);

// wherever you want:
SetWindowLongA( hwnd, GWL_WNDPROC, ( LONG )myWindowProc );

答案 2 :(得分:0)

解决方案是使用全局变量。这样,构造函数将this变量绑定到另一个变量,如that。这适用于单身人士模式,虽然我觉得我的初步问题仍然没有答案。

答案 3 :(得分:0)

带有回调功能的C API几乎总能提供用户数据&#34;为此目的的字段 - 将您自己的上下文结构或对象(即&#39; this&#39;指针)传递给回调。 在这种情况下,userdata确实存在,这意味着不需要任何std :: map shenanigans,但它有点隐藏。您正在寻找的API是:

SetWindowLongPtr( hwnd, GWLP_USERDATA, your_user_data )
your_user_data = GetWindowLongPtr( hwnd, GWLP_USERDATA )