3

我正在尝试使用 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

4 回答 4

4

问题

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

std::bind 返回一个函数对象而不是函数指针。尽管在源代码级别,函数对象几乎可以与函数指针相同地使用,但事实仍然是它是完全不同的类型。再多的铸造也无法解决这个问题。

由于您将 this 指针与 renderMan::WindowProc 方法绑定,很明显 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];
}

但是,这可能会带来一些先有后有的问题,因为在您获取 HWND 并有机会添加它和指向地图的指针之前,您将在 CreateWindow 调用期间收到一些消息。例如:

// ... 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;
}

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

于 2013-07-13T23:02:27.173 回答
1

带有回调的 C API 几乎总是为此提供一个“用户数据”字段——将您自己的上下文结构或对象(即“this”指针)传递给回调。在这种情况下,用户数据确实存在,这意味着不需要任何 std::map 恶作剧,但是它有点隐藏起来。您正在寻找的 API 是:

SetWindowLongPtr( hwnd, GWLP_USERDATA, your_user_data )
your_user_data = GetWindowLongPtr( hwnd, GWLP_USERDATA )
于 2017-05-30T11:31:08.940 回答
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 );
于 2013-06-29T17:55:24.103 回答
0

解决方案是使用全局变量。这样,构造函数将this变量绑定到另一个变量,例如that. 这适用于单例模式,尽管我觉得我最初的问题仍未得到解答。

于 2013-07-13T20:49:18.830 回答