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

Тема в разделе "Общие вопросы по С и С++", создана пользователем Igorg, 3 мар 2008.

  1. Igorg

    Igorg Гость

    Здравствуйте. Может кто-нибудь сможет предложить какое-нибудь решение следующей задачи:
    Имеется класс 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]
    Может моя концепция вообще не верна? Или я туплю и не вижу очевидного решения? Черт... Заранее прошу извинить, если так -- голова не варит...
     
  2. Pasha

    Pasha Гость

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

    Igorg Гость

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

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    используй boost, MFC-like решение приведет к сильной связности.
     
  5. European

    Регистрация:
    4 сен 2006
    Сообщения:
    2.580
    Симпатии:
    0
  6. Igorg

    Igorg Гость

    Всем спасибо за помощь. Возникли следующие комментарии:
    Kmet, что имеете в виду под "сильной связностью"? Если переносимость между Борландом и ВиСи, то решение со статическим map<> вполне подходит, т.к. класс из STL (проверил -- следующий код обоими компиляторами транслируется успешно):
    Код (Text):
    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).
     
  7. Kmet

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    возможно я не совсем четко подобрал русскоязычный аналог, я имел ввиду High Coupling (http://en.wikipedia.org/wiki/Coupling_%28computer_science%29)

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

    Igorg Гость

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

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    для одного класаа нет, но в случае дальнейшего развития приложения\библиотеки по такому принципу в высокой вероятностью приведет.

    рискуете получить проблемы с производительностью на многопроцессорных\многоядерных системах.
     
  10. Igorg

    Igorg Гость

    Что посоветуете вместо мьютексов?
     
  11. Kmet

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    в твоем случае можно поробовать использовать CriticalSeaction, время операций с мапой мало, и блокировка будет происходить на спин-локе, а не проваливаться каждый раз в ядро. но это полумера. я бы избавился от необходимости синронизации или поробовал написать lock-free алгоритм.
     
  12. Igorg

    Igorg Гость

    От синхронизации никак не избавиться, так как приложение изначально многопоточное. Возможно, стоит подумать над 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).
     
  13. Kmet

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    элементарно, нет разделяемых данных, нет необходимости в синхронизации. используйте boost или atl trunks или передавайте уаказатель на класс через userdata поле окна или...
    зря, на многопроцессорных системах использование "класических" примитивов синхронизации зачастую становится узким местом.
    на однопроцессорных CriticalSection == Mutex
    на многопроцессорных CriticalSection > Mutex
    к какому буферу? телепаты в отпуске.
     
  14. Igorg

    Igorg Гость

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

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    из твоего кода СОВСЕМ не очевидно, как этот буфер будет шарится между потоками и зачем вообще нужно его шарить.

    обоснуй.
    гугл, об этому уже писали все кому не лень. даже на этом форуме эта тема поднималясь.
     
  16. Igorg

    Igorg Гость

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

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    повторюсь: как этот буфер будет шарится между потоками и зачем вообще нужно его шарить, то что метод паблик еще ни о чем не говорит.
    в данном случае не будет надобности шарить потоками, следовательно и затереть из другого потока без БОЛЬШОГО желания не получится.
    отладку "гонок" не назовешь прозрачной...
    Велосипедостроение зло. boost, в большей своей части, войдет в будующий стандарт спп. ничего общего с vcl он не имеет, да и вообще с гуи он не имеет. не нужно путать теплое с мягкий.
    с чего ты взял, что у кого-то есть время что бы искать для тебя приямые ссылки?! направление задали - вперед.
    можно и нужно.
     
  18. Igorg

    Igorg Гость

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

    Pasha Гость

    Напиши 2 варианта для своей конкретной задачи и померяй. Все остальное - гадание.
    Более, менее... где результаты сравнения? Тебе пытаются впарить феррари, а ты предпочитаешь изобрести велосипед.
     
  20. Kmet

    Kmet Well-Known Member

    Регистрация:
    25 май 2006
    Сообщения:
    1.017
    Симпатии:
    1
    это был совет, следовать ему или нет решать тебе, доказывать что-либо я не считаю нужным.

    стоило бы глянуть на буст, перед тем как делать такие резкие заявления.
    Так что про теплое с мягким вопрос остается открытым.
    пишут многие и разное...
    вопрос в другом, чье мнение весомее.
    то что приложение многопоточное отнюдь не означает, что гонки неизбежны. в каждом конкретном случае это отдельный вопрос.
    а вот тупики возникают изза ошибок прогаммиста. ты пытаешься использовать выход по таймауты в качестве костыле, а он не для этого предназначен.
    если что-то у кого-то не работает, еще не значит, что инструментарий плох. проблема может быть и в квалификации программиста.
    ни о чем не говорит. ты предлагаешь на каждый паблик метод вешать мьютекс?! ну тогда конечно без таймаута не обойтись=)
     
Загрузка...

Поделиться этой страницей