Я пытаюсь связать нестатический член класса со стандартом WNDPROC
функция. Я знаю, что могу просто сделать это, сделав член класса статичным. Но, как ученик C ++ 11 STL, я очень заинтересован в том, чтобы сделать это с помощью инструментов под <functional>
заголовок.
Мой код выглядит следующим образом.
class MainWindow
{
public:
void Create()
{
WNDCLASSEXW WindowClass;
WindowClass.cbSize = sizeof(WNDCLASSEX);
WindowClass.style = m_ClassStyles;
WindowClass.lpfnWndProc = std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)>
( std::bind(&MainWindow::WindowProc,
*this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4));
WindowClass.cbClsExtra = 0;
WindowClass.cbWndExtra = 0;
WindowClass.hInstance = m_hInstance;
WindowClass.hIcon = LoadIconW(m_hInstance, MAKEINTRESOURCEW(IDI_WINDOW));
WindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WindowClass.hbrBackground = (HBRUSH) COLOR_WINDOW;
WindowClass.lpszMenuName = MAKEINTRESOURCEW(IDR_MENU);
WindowClass.lpszClassName = m_ClassName.c_str();
WindowClass.hIconSm = LoadIconW(m_hInstance, MAKEINTRESOURCEW(IDI_WINDOW_SMALL));
RegisterClassExW(&WindowClass);
m_hWnd = CreateWindowEx(/*_In_ DWORD*/ ExtendedStyles,
/*_In_opt_ LPCTSTR*/ m_ClassName.c_str(),
/*_In_opt_ LPCTSTR*/ m_WindowTitle.c_str(),
/*_In_ DWORD*/ m_Styles,
/*_In_ int*/ m_x,
/*_In_ int*/ m_y,
/*_In_ int*/ m_Width,
/*_In_ int*/ m_Height,
/*_In_opt_ HWND*/ HWND_DESKTOP,
/*_In_opt_ HMENU*/ NULL,
/*_In_opt_ HINSTANCE*/ WindowClass.hInstance,
/*_In_opt_ LPVOID*/ NULL);
}
private:
LRESULT CALLBACK WindowProc(_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
};
Когда я запускаю его как есть, выдается сообщение об ошибке:
Error: no suitable conversion function from "std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)>" to "WNDPROC".
Хотя JohnB уже объяснил детали, почему это невозможно, вот общее решение проблемы, которую вы пытаетесь решить: Предоставление экземпляру класса доступа к статическому члену класса.
Руководящий принцип решения заключается в том, что указатель экземпляра должен храниться так, чтобы он был доступен статическому члену класса. При работе с окнами дополнительная память окна является хорошим местом для хранения этой информации. Запрашиваемое пространство дополнительной памяти окна указывается через WNDCLASSEXW::cbWndExtra
в то время как доступ к данным обеспечивается через SetWindowLongPtr
а также GetWindowLongPtr
.
Сохраните указатель экземпляра в области дополнительных данных окна после построения:
void Create()
{
WNDCLASSEXW WindowClass;
// ...
// Assign the static WindowProc
WindowClass.lpfnWndProc = &MainWindow::StaticWindowProc;
// Reserve space to store the instance pointer
WindowClass.cbWndExtra = sizeof(MainWindow*);
// ...
RegisterClassExW(&WindowClass);
m_hWnd = CreateWindowEx( /* ... */ );
// Store instance pointer
SetWindowLongPtrW(m_hWnd, 0, reinterpret_cast<LONG_PTR>(this));
}
Получите указатель экземпляра из статической оконной процедуры и вызовите функцию-член оконной процедуры:
static LRESULT CALLBACK StaticWindowProc( _In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam )
{
// Retrieve instance pointer
MainWindow* pWnd = reinterpret_cast<MainWindow*>(GetWindowLongPtrW(hwnd, 0));
if ( pWnd != NULL ) // See Note 1 below
// Call member function if instance is available
return pWnd->WindowProc(hwnd, uMsg, wParam, lParam);
else
// Otherwise perform default message handling
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Подпись ученика WindowProc
такой же, как в коде, который вы предоставили.
Это один из способов реализации желаемого поведения. Реми Лебо предложил вариант к этому, который имеет преимущество в том, что все сообщения маршрутизируются через ученика. WindowProc
:
Выделите место в окне дополнительных данных (так же, как указано выше):
void Create()
{
WNDCLASSEXW WindowClass;
// ...
// Assign the static WindowProc
WindowClass.lpfnWndProc = &MainWindow::StaticWindowProc;
// Reserve space to store the instance pointer
WindowClass.cbWndExtra = sizeof(MainWindow*);
// ...
Передать указатель экземпляра на CreateWindowExW
:
m_hWnd = CreateWindowEx( /* ... */,
static_cast<LPVOID>(this) );
// SetWindowLongPtrW is called from the message handler
}
Извлечь указатель экземпляра и сохранить его в области дополнительных данных окна при появлении первого сообщения (WM_NCCREATE
) отправляется в окно:
static LRESULT CALLBACK StaticWindowProc( _In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam )
{
// Store instance pointer while handling the first message
if ( uMsg == WM_NCCREATE )
{
CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam);
LPVOID pThis = pCS->lpCreateParams;
SetWindowLongPtrW(hwnd, 0, reinterpret_cast<LONG_PTR>(pThis));
}
// At this point the instance pointer will always be available
MainWindow* pWnd = reinterpret_cast<MainWindow*>(GetWindowLongPtrW(hwnd, 0));
// see Note 1a below
return pWnd->WindowProc(hwnd, uMsg, wParam, lParam);
}
Примечание 1: указатель экземпляра сохраняется в области дополнительных данных окна после того, как окно было создано, пока lpfnWndProc
устанавливается до создания. Это означает, что StaticWindowProc
будет вызываться, пока указатель экземпляра еще не доступен. Как следствие if
заявление внутри StaticWindowProc
требуется, чтобы сообщения при создании (например, WM_CREATE
) получить должным образом обработаны.
Примечание 1а: ограничения, указанные в примечании 1, не применяются к альтернативному варианту реализации. Указатель экземпляра будет доступен, начиная с первого сообщения и члена класса WindowProc
следовательно, будет вызываться для всех сообщений.
Примечание 2: если вы хотите уничтожить экземпляр класса C ++, когда основной HWND
уничтожен, WM_NCDESTROY
это место для этого; это последнее сообщение, отправленное в любое окно.
Думаю, вы не можете этого сделать, поскольку WNDPROC обозначает указатель на функцию. Каждый указатель на функцию может быть преобразован в std :: function, но не каждый std :: function представляет указатель на функцию.
Доказательство невозможности вашего плана: Технически WNDPROC представляет только адрес функции в памяти, которая должна быть вызвана. Следовательно, переменная типа WNDPROC не содержит «пробела» для хранения информации о связанных параметрах.
Это та же проблема, что и в следующем примере:
typedef void (* callbackFn) ();
struct CallingObject {
callbackFn _callback;
CallingObject (callbackFn theCallback) : _callback (theCallback) {
}
void Do () {
_callback ();
}
};
void f () { std::cout << "f called"; }
void g () { std::cout << "g called"; }
void h (int i) { std::cout << "h called with parameter " << i; }
int main () {
CallingObject objF (f); objF.Do (); // ok
CallingObject objG (g); objG.Do (); // ok
}
Еще для того чтобы позвонить h
из CallingObject
с некоторым значением параметра, определенным во время выполнения, вы должны сохранить значение параметра в статической переменной, а затем написать функцию-оболочку, вызывающую h
с этим значением в качестве аргумента.
Вот почему функции обратного вызова обычно принимают аргумент типа void *
где вы можете передать произвольные данные, необходимые для расчета.