Why can't I bind to winproc?

拈花ヽ惹草 提交于 2019-12-12 17:29:35

问题


I am trying to use C++11 to solve my favorite pointer problem

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);

The error

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

回答1:


The Problem

A WNDPROC is a pointer to a function with a certain signature using the __stdcall calling convention.

std::bind returns a function object not a function pointer. Although, at the source code level, the function object can be used almost identically to a function pointer, the fact remains that it is a completely different type. No amount of casting will solve this.

Since you're binding a this pointer with the renderMan::WindowProc method, it's clear that renderMan::WindowProc is not a static member function, and thus it uses the thiscall calling convention. So even if you could get a pointer to it and hand it to Windows, Windows wouldn't call it with the right calling convention.

Solutions

The most common way to handle this is to have a nonmember function (or static member function) registered as the WNDPROC. That function looks up the this pointer associated with the window, and forwards it to your member function.

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);
}

The details of LookUpPointer will vary depending on your approach. It sounds like you solved it just by having a global variable and thus just one window instance.

If you need multiple windows instantiated from this window class, you could maintain a global (or class-static) table mapping the HWNDs to the renderMan pointers. When you create a new instance, you add it to the table, and then you have a simple implementation for 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];
}

This can have some chicken and egg problems, though, since you will get some messages during the CreateWindow call, before you've gotten the HWND back and had a chance to add it and the pointer to the map. For example:

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

Another common approach, which solves the chicken and egg problem, is to stash the renderMan pointer in the creation parameters, and have special logic in your WndProcHelper to add it to the map during creation.

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

Caution: This is simplistic code without enough error checking for the purposes of illustrating the idea. A clean and complete implementation would handle errors more gracefully, log unexpected things (like missing entries), etc.




回答2:


WNDPROC is a function pointer while the result of bind is a function object. and as the compiler says, it can´t be converted to WNDPROC.

you could do:

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

but i guess that doesn´t solve your problem. i think there´s no way to do this without a free function. maybe like this:

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 );



回答3:


C API's with callbacks almost always provide a "user data" field for this purpose -- passing your own context structure or object (i.e. the 'this' pointer) through to the callback. In this case, the userdata does exist, which means there's no need for any std::map shenanigans, however it is kind of hidden away. The APIs that you're looking for are:

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



回答4:


The solution was to use a global variable. That way the constructor binds the this variable to another variable like that. This works for a singleton pattern, although I feel my initial question remains unanswered.



来源:https://stackoverflow.com/questions/17382174/why-cant-i-bind-to-winproc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!