Как совместить Windowproc и экземпляр класса?

  • Автор темы Igorg
  • Дата начала
I

Igorg

#1
Здравствуйте. Может кто-нибудь сможет предложить какое-нибудь решение следующей задачи:
Имеется класс DirectDrawWindow. Экземпляры класса представляют собой окна HWND, рисуемые в главном окне (их может быть несколько, без заголовков, не MDI, главное окно является родителем). При создании экземпляра создается и окно. В классе DirectDrawWindow определен метод static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); Этот метод, естественно, обрабатывает сообщения окна, которое создается в экземпляре класса (он указывается в поле структуры WNDCLASSEX класса окна), окон может быть несколько, а этот метод один. Понятно, что доступа к полям экземпляра WindowProc не имеет (ну кроме как к HWND hWnd, который передается в качестве параметра). Так вот, вопрос в том, как этот доступ обеспечить? Для сообщений, определенных пользователем, можно указатель на экземпляр передавать в wParam (или lParam:), это понятно. А если надо обработать сообщение WM_PAINT (например, вывести в окно часть рисунка, содержащегося в буфере конкретного экземпляра)? Нестатическим WindowProc сделать не получится -- потребует передачи неявного указателя на экземпляр, чего Windows делать не будет...
[codebox]
class DirectDrawWindow
{
public:
DirectDrawWindow(HINSTANCE hInstance, HWND hParentWindow)
{
// Регистрация класса окна и прочая инициализация (которую надо бы в статическом конструкторе сделать...)
...
_window = CreateWindow(...); // а вот это уже сугубо к экземпляру относится
...
}

void Draw(unsigned char *picture)
{
memcpy(buffer, picture, _w * _h * 3);
PostMessage(_window, WM_USER_DRAW_PICTURE, (WPARAM)this, 0);
}

private:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
...
case WM_PAINT:
// ЗАГВОЗДОЧКА!
// Как отобразить buffer, например, если надо восстановить часть окна после перекрытия, если экземпляр класса не известен,
// известен лишь дескриптор окна?
...
break;
case WM_USER_DRAW_PICTURE:
// из wParam извлекаем указатель на экземпляр и так далее
...
HDC hDC = BeginPaint(...);
...
}

private:
HWND _window; // дескриптор окна
unsigned char buffer[...]; // здесь лежит какой-нибудь рисунок (в формате RGB, не суть), добавленный сюда методом Draw(...)
int _w, _h;
};
[/codebox]
Может моя концепция вообще не верна? Или я туплю и не вижу очевидного решения? Черт... Заранее прошу извинить, если так -- голова не варит...
 
P

Pasha

#2
Igorg
Можно сделать:
1. Как в MFC. Завести статический map<HWND, DirectDrawWindow*>, добавлять в него пару (_window, this) в конструкторе. Потом в статической WindowProc искать экземпляр по hwnd и работать с ним.
2. Использовать уже готовые обертки, позволяющие использовать нестатические функции для callback-ов. http://www.gamedev.ru/faq/?id=34, и дальше по ссылкам.
З.Ы.Я не плюсойд, так что звиняйте если что :(
 
I

Igorg

#3
Спасибо большое, была мысль сделать вариант, схожий с вариантом 1, теперь вижу, что мыслю в верном направлении (к сожалению (?), не знаком с MFC, поэтому не сразу и догадался:)
 

Kmet

Java Team
25.05.2006
1 036
8
#4
используй boost, MFC-like решение приведет к сильной связности.
 
I

Igorg

#6
Всем спасибо за помощь. Возникли следующие комментарии:
Kmet, что имеете в виду под "сильной связностью"? Если переносимость между Борландом и ВиСи, то решение со статическим map<> вполне подходит, т.к. класс из STL (проверил -- следующий код обоими компиляторами транслируется успешно):
Код:
class DirectDrawWindow
{
public:
DirectDrawWindow(HWND hWndParent);
~DirectDrawWindow();

void CreateDirectDrawWindow(void);	// Create the window.

...

public:
static void StaticDirectDrawWindow(HINSTANCE hInstance);	// инициализация класса окна (псевдостатический конструктор)

private:
void Present(void);				// вывод содержимого буфера в окно
private:
static LRESULT CALLBACK DirectDrawWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

private:
static WNDCLASSEX _wndClassEx;
static ATOM _registerClassExAtom;
static map<HWND, DirectDrawWindow *> _windowsMap;	// карта пар <окно, объект>
...
private:
HWND _parentWindow;
HWND _window;

...
void *_buffer;			// буфер с изображением
...
};

void DirectDrawWindow::StaticDirectDrawWindow(HINSTANCE hInstance)
{
...
_registerClassExAtom = RegisterClassEx((CONST WNDCLASSEX *)&_wndClassEx);
...
}

DirectDrawWindow::DirectDrawWindow(HWND hWndParent)
{
...
_parentWindow = hWndParent;
...
}

LRESULT CALLBACK DirectDrawWindow::DirectDrawWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
...
map<HWND, DirectDrawWindow *>::const_iterator mapIterator;
DirectDrawWindow *ddwptr;

switch (uMsg)
{
...
case WM_PAINT:
...
ddwptr = NULL;
mapIterator = _windowsMap.find(hWnd);
if (mapIterator != _windowsMap.end())
{
ddwptr = mapIterator->second;
}
...
if (ddwptr != NULL)
{
// найден объект окна
ddwptr->Present();
}
...
break;
...
}
}

void DirectDrawWindow::CreateDirectDrawWindow(void)
{
...
_window = CreateWindow(_wndClassEx.lpszClassName, "", WS_CHILD, _x, _y, _cx, _cy, _parentWindow, NULL, _wndClassEx.hInstance, NULL);
_windowsMap.insert(pair<HWND, DirectDrawWindow *>(_window, this));
...
}

...
Решение с boost слишком "тяжелое", весь остальной код программы пишется без классов (на Си).
European, опять же, чувствуется влияние Борланда на автора статьи. Эти "черные ящики" (по моему мнению) не решают проблему приведения WinAPI к более удобному виду (классы), а маскируют ее. Дело в том, что передо мной была поставлена задача перевести код, написанный ранее с использованием стиля Borland Vcl на рельсы MS VC... По грубой оценке, пришлось переписать около 70% кода -- это из-за приверженности моих коллег к "быстрой" разработке программ с использованием готовых компонентов (зачастую, медленных и несколько нестабильных, и уж точно некомпилируемых с помощью cl.exe).
 

Kmet

Java Team
25.05.2006
1 036
8
#7
возможно я не совсем четко подобрал русскоязычный аналог, я имел ввиду High Coupling (http://en.wikipedia.org/wiki/Coupling_%28computer_science%29)

и еще ваш код не пригоден для использования в многопоточных приложениях.
 
I

Igorg

#8
Понял про coupling (к стыду своему, только от вас про этот термин услышал). Но по прочтении не заметил, в чем может быть связность -- класс получился "сам в себе" (без связей, почти все поля закрыты), а типы данных (HWND и map) от которых он зависит -- вряд ли изменятся ближайшие 100 лет (в худшем случае придется перекомпилировать программу с новыми заголовками и библиотеками). Что касается многопоточности -- я для краткости поста убрал код синхронизации (доступ к карте и буферу осуществляется по, соответственно, статическому и экземплярному мьютексам (CreateMutex(), WaitForSingleObject() + ReleaseMutex())).
 

Kmet

Java Team
25.05.2006
1 036
8
#9
для одного класаа нет, но в случае дальнейшего развития приложения\библиотеки по такому принципу в высокой вероятностью приведет.

Что касается многопоточности -- я для краткости поста убрал код синхронизации (доступ к карте и буферу осуществляется по, соответственно, статическому и экземплярному мьютексам (CreateMutex(), WaitForSingleObject() + ReleaseMutex())).
рискуете получить проблемы с производительностью на многопроцессорных\многоядерных системах.
 

Kmet

Java Team
25.05.2006
1 036
8
#11
в твоем случае можно поробовать использовать CriticalSeaction, время операций с мапой мало, и блокировка будет происходить на спин-локе, а не проваливаться каждый раз в ядро. но это полумера. я бы избавился от необходимости синронизации или поробовал написать lock-free алгоритм.
 
I

Igorg

#12
От синхронизации никак не избавиться, так как приложение изначально многопоточное. Возможно, стоит подумать над lock-free алгоритмом, но я просто тяготею к мьютексам (мое субъективное решение:). Для доступа к карте действительно можно использовать критическую секцию, но на однопроцессорной машине спин-лок принят за 0 (MSDN: On single-processor systems, the spin count is ignored and the critical section spin count is set to 0 (zero)), так что в ядро будет так и так проваливаться (MSDN: ... performing a wait operation on a semaphore associated with the critical section). А для доступа к буферу как раз лучше мьютексы использовать: если кадр выводится дольше опред. времени, его проще пропустить и попытаться следующий отобразить, а то начнется рассинхронизация видео и звука (скажем, быстродействие машины позволяет только 40 кадров из 100 выводить). EnterCriticalSection не позволяет тайм-аут задать, будет ждать до победного конца. WaitForSingleObject проваливается в ядро только тогда, когда объект не свободен (MSDN: If the object's state is nonsignaled, the calling thread enters the wait state).
 

Kmet

Java Team
25.05.2006
1 036
8
#13
От синхронизации никак не избавиться, так как приложение изначально многопоточное
элементарно, нет разделяемых данных, нет необходимости в синхронизации. используйте boost или atl trunks или передавайте уаказатель на класс через userdata поле окна или...
стоит подумать над lock-free алгоритмом, но я просто тяготею к мьютексам (мое субъективное решение:).
зря, на многопроцессорных системах использование "класических" примитивов синхронизации зачастую становится узким местом.
Для доступа к карте действительно можно использовать критическую секцию, но на однопроцессорной машине спин-лок принят за 0 (MSDN: On single-processor systems, the spin count is ignored and the critical section spin count is set to 0 (zero)), так что в ядро будет так и так проваливаться (MSDN: ... performing a wait operation on a semaphore associated with the critical section)
на однопроцессорных CriticalSection == Mutex
на многопроцессорных CriticalSection > Mutex
А для доступа к буферу как раз лучше мьютексы использовать
к какому буферу? телепаты в отпуске.
 
I

Igorg

#14
Код:
void *_buffer;			// буфер с изображением
Телепаты могут оставаться в отпуске, они понадобятся, когда будет непонятное поведение программы при передаче указателя на класс в поле userdata окна (в том смысле, что с мьютексами отлаживать проще).
на многопроцессорных системах использование "класических" примитивов синхронизации зачастую становится узким местом
Можно ссылку? Сейчас все большее распространение получают всяческие Core Duo, так что это важная тема...
 

Kmet

Java Team
25.05.2006
1 036
8
#15
из твоего кода СОВСЕМ не очевидно, как этот буфер будет шарится между потоками и зачем вообще нужно его шарить.

Телепаты могут оставаться в отпуске, они понадобятся, когда будет непонятное поведение программы при передаче указателя на класс в поле userdata окна (в том смысле, что с мьютексами отлаживать проще).
обоснуй.
Можно ссылку? Сейчас все большее распространение получают всяческие Core Duo, так что это важная тема...
гугл, об этому уже писали все кому не лень. даже на этом форуме эта тема поднималясь.
 
I

Igorg

#16
Согласен, не указал открытый метод загрузки в буфер изображения public: DrawBitmap(char *source, int width, int height). Он не вызывается из DirectDrawWindowProc.
Обосновываю: если у меня записаны данные в область памяти "где-то там за окном" из другого потока я могу случайно затереть часть этой области даже сам не поняв как это произошло. Согласен, мой косяк, но от этих ошибок не застрахован никто. В то же время, если не использовать такие "интимные" системные места, то и голова не болит, не затер ли я в каком-то потоке эту область, отладка становится более прозрачной. По поводу всех этих boost'ов и прочих -- на первый взгляд сравнимо с vcl, такое же неповоротливое (субъективно). Разберусь -- напишу мнение.
По поводу отсылов к гуглю и прочее -- если есть прямая ссылка, выкладывайте, потому что в гугле чтобы найти именно то, что нужно, надо потратить некоторое время, которое можно сэкономить просто пройдя по ссылке, указанной человеком, который уже сталкивался с этой проблемой. Если не так, то ставится под сомнение целесообразность существование форумов программистов (Получается, что люди учатся на своих ошибках. Но ведь если учиться на чужих ошибках -- получается гораздно быстрее и безболезненнее). В принципе, на каждый вопрос можно ответить "ищи в гугле" (сокращенно, ИВГ, типа ИМХО, РТФМ и т. д.)
 

Kmet

Java Team
25.05.2006
1 036
8
#17
Согласен, не указал открытый метод загрузки в буфер изображения public: DrawBitmap(char *source, int width, int height). Он не вызывается из DirectDrawWindowProc.
повторюсь: как этот буфер будет шарится между потоками и зачем вообще нужно его шарить, то что метод паблик еще ни о чем не говорит.
Обосновываю: если у меня записаны данные в область памяти "где-то там за окном" из другого потока я могу случайно затереть часть этой области даже сам не поняв как это произошло.
в данном случае не будет надобности шарить потоками, следовательно и затереть из другого потока без БОЛЬШОГО желания не получится.
В то же время, если не использовать такие "интимные" системные места, то и голова не болит, не затер ли я в каком-то потоке эту область, отладка становится более прозрачной.
отладку "гонок" не назовешь прозрачной...
По поводу всех этих boost'ов и прочих -- на первый взгляд сравнимо с vcl, такое же неповоротливое (субъективно). Разберусь -- напишу мнение
Велосипедостроение зло. boost, в большей своей части, войдет в будующий стандарт спп. ничего общего с vcl он не имеет, да и вообще с гуи он не имеет. не нужно путать теплое с мягкий.
По поводу отсылов к гуглю и прочее -- если есть прямая ссылка, выкладывайте, потому что в гугле чтобы найти именно то, что нужно, надо потратить некоторое время, которое можно сэкономить просто пройдя по ссылке, указанной человеком, который уже сталкивался с этой проблемой
с чего ты взял, что у кого-то есть время что бы искать для тебя приямые ссылки?! направление задали - вперед.
В принципе, на каждый вопрос можно ответить "ищи в гугле" (сокращенно, ИВГ, типа ИМХО, РТФМ и т. д.)
можно и нужно.
 
I

Igorg

#18
Если у вас у самого нету времени, то незачем отвечать на всякие вопросы с сомнительными предложениями типа "выиграть на многопроцессорных системах используя критические секции вместо мьютексов".
с чего ты взял, что у кого-то есть время что бы искать для тебя приямые ссылки?! направление задали - вперед.
Читать умеете? Я написал: "если есть прямая ссылка,". Добавлю: если нет, то незачем в видом профи указывать на ошибки, которых нет. Про поиск за меня вроде ни слова. Даже между строк.
Велосипедостроение зло. boost, в большей своей части blah-blah-blah
Вот что про boost пишут:
...что менее эффективно...
...имеет более неудобный синтаксис...
...гораздо менее эффективна...
http://www.gamedev.ru/faq/?id=34
Когда мне пытаются впарить паровоз под видом велосипеда, приходится изобретать.
ничего общего с vcl он не имеет, да и вообще с гуи он не имеет
Имеет -- способ связывания callback-методов. Vcl это тоже не сплошняком гуи, просто имеет 90% классов для работы с гуями, а boost соответственно можно использовать при построении гуя (да больше и незачем). Про теплое с мягким надеюсь вопрос отпал.
отладку "гонок" не назовешь прозрачной...
Гонки возникают в многопоточных приложениях, от этого никуда не деться, а вот тупики, к которым они могут привести, возникают если использовать критические секции вместо мьютексов, так как они не позволяют прекращать ожидание по таймаутам. Что касается прозрачной отладки -- есть подозрение, что фирма Борланд в порыве "оптимизации" всего и вся как раз использовала такие вот места, куда "не будет надобности шарить потоками, следовательно и затереть из другого потока без БОЛЬШОГО желания не получится", потому что сталкивался сам и слышал жалобы от других о неверной работе и исключениях при освобождении контролов. Когда использовались функции АПИ вместо методов этих вэ-цэ-элевских классов, все работало нормально.
метод паблик еще ни о чем не говорит.
О многом говорит. Например о том, что его можно вызвать из любого места программы, а не только из WindowProc, которая сама по себе обычно не вызывается из основного кода программы. Но это ладно, не суть. Мне тоже некогда вникать в чужой код и думать как бы там все могло происходить.
Видимо, если бы я спросил "А как работает этот метод: int sumab(int a, int b) { return a + b; }?" получил бы ответ и все были бы счастливы:) Да вот беда -- на такие вопросы ответы действительно в книгах надо искать, а специализированные темы типа сравнения крит. секций и мьютексов не обсуждаются направо и налево, в каждой книге для чайников...
 
P

Pasha

#19
Если у вас у самого нету времени, то незачем отвечать на всякие вопросы с сомнительными предложениями типа "выиграть на многопроцессорных системах используя критические секции вместо мьютексов".
Напиши 2 варианта для своей конкретной задачи и померяй. Все остальное - гадание.
Вот что про boost пишут:
...что менее эффективно...
...имеет более неудобный синтаксис...
...гораздо менее эффективна...
http://www.gamedev.ru/faq/?id=34
Когда мне пытаются впарить паровоз под видом велосипеда, приходится изобретать.
Более, менее... где результаты сравнения? Тебе пытаются впарить феррари, а ты предпочитаешь изобрести велосипед.
 

Kmet

Java Team
25.05.2006
1 036
8
#20
Если у вас у самого нету времени, то незачем отвечать на всякие вопросы с сомнительными предложениями типа "выиграть на многопроцессорных системах используя критические секции вместо мьютексов".
это был совет, следовать ему или нет решать тебе, доказывать что-либо я не считаю нужным.

а boost соответственно можно использовать при построении гуя (да больше и незачем)
стоило бы глянуть на буст, перед тем как делать такие резкие заявления.
Так что про теплое с мягким вопрос остается открытым.
Вот что про boost пишут:
...что менее эффективно...
...имеет более неудобный синтаксис...
...гораздо менее эффективна...
http://www.gamedev.ru/faq/?id=34
Когда мне пытаются впарить паровоз под видом велосипеда, приходится изобретать.
пишут многие и разное...
“...one of the most highly regarded and expertly designed C++ library projects in the world.”
— Herb Sutter and Andrei Alexandrescu, C++ Coding Standards
вопрос в другом, чье мнение весомее.
Гонки возникают в многопоточных приложениях, от этого никуда не деться, а вот тупики, к которым они могут привести, возникают если использовать критические секции вместо мьютексов, так как они не позволяют прекращать ожидание по таймаутам
то что приложение многопоточное отнюдь не означает, что гонки неизбежны. в каждом конкретном случае это отдельный вопрос.
вот тупики, к которым они могут привести, возникают если использовать критические секции вместо мьютексов, так как они не позволяют прекращать ожидание по таймаутам
а вот тупики возникают изза ошибок прогаммиста. ты пытаешься использовать выход по таймауты в качестве костыле, а он не для этого предназначен.
есть подозрение, что фирма Борланд в порыве "оптимизации" всего и вся как раз использовала такие вот места, куда "не будет надобности шарить потоками, следовательно и затереть из другого потока без БОЛЬШОГО желания не получится", потому что сталкивался сам и слышал жалобы от других о неверной работе и исключениях при освобождении контролов. Когда использовались функции АПИ вместо методов этих вэ-цэ-элевских классов, все работало нормально.
если что-то у кого-то не работает, еще не значит, что инструментарий плох. проблема может быть и в квалификации программиста.
Цитата(Kmet @ 13:03:2008 - 20:46)
метод паблик еще ни о чем не говорит.
О многом говорит. Например о том, что его можно вызвать из любого места программы, а не только из WindowProc, которая сама по себе обычно не вызывается из основного кода программы.
ни о чем не говорит. ты предлагаешь на каждый паблик метод вешать мьютекс?! ну тогда конечно без таймаута не обойтись=)