Начал разбираться с WinAPI
и столкнулся со следующей проблемой:
При создании класса (WNDCLASS
) ему нужно указать функцию-обработчик событий (вероятно это по другому называется, но вы поняли) LRESULT CALLBACK wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
. Для чистоты кода я решил вынести все, что связано с этим окном в отдельный класс MainWindow
.
Тут-то и возникла проблема. Если сделать данную функцию членом класса, то компилятор начинает ругаться на каст:
WNDCLASS wc;
...
wc.lpfnWndProc = static_cast<WNDPROC> (wndProc);
Ошибка C2440 static_cast: невозможно преобразовать "overloaded-function" в "WNDPROC" MyProjectName c:\...\mainwindow.cpp 22
Перебрал несколько очевидных вариантов решения данной проблемы: сделать функцию статической, дружественной, или использовать лямбду, но дело в том, что в данном окне должны рисоваться объекты(например круг). Соответственно, их экземпляры должны храниться в классе окна, чтобы при WM_PAINT
просто вежливо попросить их заново нарисоваться и никаких проблем не было. Тогда это отметает все 3 моих варианта т.к. ни один из них не может напрямую взаимодействовать с нестатическими свойствами класса.
Есть еще 2 варианта, но они ужасные: сделать поля, нужные для перерисовки, статическими или заново создавать их при перерисовки. Даже комментировать это не буду.
Как можно решить эту проблему?
P.S. Вот код реализации класса, если кому-то нужно
Никак нельзя сделать обратный вызов на метод. Можно запоминать для каждого окна Windows указатель на соответствующий ему объект MainWindow
с помощью функции SetWindowLongPtr
с параметром GWLP_USERDATA
и сделать общую функцию WNDPROC
для всех окон, внутри которой получать с помощью функции GetWindowLongPtr
этот указатель, преобразовывать к типу MainWindow*
и запускать на нем какой-то метод.
То есть, вызов метода класса будет осуществляться примерно так:
LRESULT MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
MainWindow *mainwin = static_cast<MainWindow*>(
GetWindowLongPtr(hwnd, GWLP_USERDATA));
return mainwin->wndProc(hwnd, uMsg, wParam, lParam);
}
Тем не менее, я сталкивался с одним показательным случаем, когда это не работает. Моя программа (VST-плагин) получала готовое окно HWND от VST-хоста (сторонняя программа). Все работало, пока я не наткнулся на VST-хост, разработчик которого тоже захотел использовать GWL_USERDATA.
То есть более общий вариант решения - использовать хэш (окно=>объект).
Тем не менее, я сталкивался с одним показательным случаем, когда это не работает. Моя программа (VST-плагин) получала готовое окно HWND от VST-хоста (сторонняя программа). Все работало, пока я не наткнулся на VST-хост, разработчик которого тоже захотел использовать GWL_USERDATA.
Я, собственно, поддерживаю ответ @Alexander Zonov, он верный. Добавлю только как быть ситуации, когда нет возможности использовать GWL_USERDATA
.
В WNDCLASS
есть поля:
int cbClsExtra;
int cbWndExtra;
они используются как раз тогда, когда необходимо в объекте класса или в объекте окна хранить дополнительные данные. По умолчанию, любое окно уже имеет некоторые данные и мы можем к ним обратиться через GetWindowLongPtr/SetWindowLongPtr
. Константы GWLP_xxx
- это всего лишь смещения внутри объекта окна на эти данные. Для того, чтобы их не путать с пользовательскими данными, все служебные смещения сделали отрицательными (просто сдвинули все служебные данные в отрицательную плоскость).
В нашем случае, для обеспечения стабильной работы необходимо зарезервировать в объекте окна размер указателя на объект С++:
wc.cbWndExtra = sizeof(void*);
после регистрации такого класса окна, любое окно этого класса будет иметь дополнительные 4/8 байт, к которым можно будет обратиться через GetWindowLongPtr/SetWindowLongPtr
, передавая в качестве смещения значение 0
.
Таким образом, без GWL_USERDATA
можно вполне обойтись.
Есть еще вариант, когда и этот сценарий не сработает: когда необходимо наследовать класс от другого класса, который уже использует дополнительные данные окна. Это достаточно распространенный вариант, за которым даже закреплен устоявшийся термин - superclassing.
Но тут тоже все решается довольно просто: надо запомнить значение cbWndExtra
предка, использовать его в качестве смещения для GetWindowLongPtr/SetWindowLongPtr
, а перед регистрацией класса окна просто расширить размер объекта окна:
GetClassInfo(..., &wc);
...
wc.cbWndExtra += sizeof(void*);
Т.е. оконные классы Windows достаточно гибкие. При желании, можно даже полиморфизм организовать, например: хранить в дополнительных данных класса окна указатель на функцию, которую потомки будут переписывать. Этот функционал аналогично работает через GetClassLongPtr/SetClassLongPtr
.
Ну, хоть и не совсем по делу, вынужден как ответ из-за объема.
Рассказываю о синглтоне. Если у вас есть класс, который должен иметь единственный экземпляр - то запрещаем его копирование, а конструктор делаем закрытым. Этот единственный экземпляр делается статической переменной:
class Singleton
{
Singleton(){};
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton(){}
public:
static Singleton& get()
{
static Singleton s;
return s;
}
void out() { cout << "It's me :)\n"; }
};
int main(int argc, const char * argv[])
{
Singleton& s = Singleton::get();
s.out();
}
Примерно так. Вы не сможете ни создать, ни скопировать новый объект Singleton
. Зато любая функция знает, как к нему обратиться - через вызов Singleton::get()
.
Ну нет дополнительного параметра в обработчике сообщений...
Еще вариант - привязать объект к дескриптору окна, но, по-моему, это хоть и несколько обобщеннее, но ничуть не лучше и уж точно сложнее...
P.S. Пока писал - этот последний способ привязки предложил в своем ответе @Alexander Zonov.
Оборудование для ресторана: новинки профессиональной кухонной техники
Частный дом престарелых в Киеве: комфорт, забота и профессиональный уход
Здравствуйте! Я написал свой класс-контейнерИ теперь мне нужно написать класс iterator, чтобы мой контейнер смог взаимодействовать с алгоритмами...