Как инкапсулировать приложение WinAPI в класс C ++ - PullRequest
4 голосов
/ 14 июня 2010

Есть простое приложение WinAPI.Все, что он делает в настоящее время, это:

  • регистрация класса окна
  • регистрация иконки в трее с помощью меню
  • создание значения в реестре для автоматического запуска
  • и, наконец, он проверяет, является ли он уникальным, используя мьютекс

Поскольку я привык писать код в основном на C ++, а MFC не разрешен, я вынужден инкапсулировать этов классы C ++ как-то.До сих пор я придумал такой дизайн:

  • есть класс, представляющий приложение
  • , в котором хранятся все переменные wndclass, hinstance и т. Д., Где hinstanceпередается в качестве параметра конструктора, а также в icmdshow и других (см. прототип WinMain)
  • он имеет функции для регистрации класса окна, иконки в трее, информации о reigstry
  • он инкапсулирует цикл сообщений вfunction

В WinMain выполняется следующее:

Application app(hInstance, szCmdLIne, iCmdShow);
return app.exec();

и конструктор делает следующее:

registerClass();
registerTray();
registerAutostart();

Пока все хорошо.Теперь вопрос: как мне создать оконную процедуру (должна быть статической, так как это указатель на функцию в стиле c) И отслеживать, что представляет собой объект приложения, то есть хранить указатель на приложение вокруг.

Главный вопрос: так ли это обычно делается?Я слишком много усложняю?Можно ли передать hInstance в качестве параметра конструктору Application?А где WndProc?

Может быть, WndProc должен быть вне класса, а указатель приложения должен быть глобальным?Затем WndProc вызывает методы Application в ответ на различные события.

Существует еще одно возможное решение: сделать класс приложения одиночным.Затем тривиально получить дескриптор этого объекта из WndProc.

Ответы [ 4 ]

4 голосов
/ 14 июня 2010

Ответ - SetWindowLongPtr.Это позволяет вам связать пустоту * с данным hWnd.Затем в WndProc вы просто извлекаете указанный void *, приводите и вызываете метод member.Проблема решения.Есть несколько взлетов / падений с SetWindowLongPtr, вы должны вызвать какую-то другую функцию, чтобы увидеть эффекты или что-то вроде BS, и Windows отправляет сообщения до того, как CreateWindowEx возвращает, поэтому вы должны быть готовы к GetWindowLongPtr (hWnd, GWL_USERDATA) для возврата NULL.

Это, конечно, означает, что для данного WindowProc все экземпляры, которые его используют, должны иметь общий интерфейс, поскольку вы не можете многое сделать с пустотой *.

И, да, это нормально для передачиПОМОЩЬ в конструкторе приложений.Я видел примеры, которые делают что-то странное, чтобы избежать этого, но я никогда не заставлял это работать самостоятельно.

Редактировать: Не путайте Get / SetWindowLong с Get / SetWindowLongPtr.Get / SetWindowLong устарела и небезопасна.

2 голосов
/ 14 июня 2010

Не следуйте подсказке, чтобы использовать Get/SetWindowLongPtr для хранения указателя this, так как это огромная дыра в безопасности!Вам просто нужно использовать карту, чтобы связать HWND с указателем на экземпляр класса.Вы можете использовать класс <map> из STL.

Кстати, вы можете найти там очень хорошее обсуждение по этой теме: http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx

2 голосов
/ 14 июня 2010

Вы можете расширить этот класс (который я использовал здесь для ответа) по своему желанию, в зависимости от того, какие сообщения вы хотите обработать.

#pragma once 

#include <windows.h> 
#include <process.h> 
#include <iostream> 

using namespace std; 

static const char *g_AppName  = "Test"; 

class CMyWindow 
{ 
    HWND  _hWnd; 
    int _width; 
    int _height; 
public: 
    CMyWindow(const int width,const int height):_hWnd(NULL),_width(width),_height(height) 
    { 
        _beginthread( &CMyWindow::thread_entry, 0, this); 
    } 

    ~CMyWindow(void) 
    { 
        SendMessage(_hWnd, WM_CLOSE, NULL, NULL); 
    } 


private: 
    static void thread_entry(void * p_userdata) 
    { 
        CMyWindow * p_win = static_cast<CMyWindow*> (p_userdata); 
        p_win->create_window(); 
        p_win->message_loop(); 
    } 

    void create_window() 
    { 
        WNDCLASSEX wcex; 

        wcex.cbSize             = sizeof(WNDCLASSEX); 
        wcex.style              = CS_HREDRAW | CS_VREDRAW; 
        wcex.lpfnWndProc    = &CMyWindow::WindowProc; 
        wcex.cbClsExtra         = 0; 
        wcex.cbWndExtra         = 0; 
        wcex.hInstance          = GetModuleHandle(NULL); 
        wcex.hIcon              = LoadIcon(NULL, IDI_APPLICATION); 
        wcex.hCursor            = LoadCursor(NULL, IDC_ARROW); 
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1); 
        wcex.lpszMenuName   = NULL; 
        wcex.lpszClassName  = g_AppName; 
        wcex.hIconSm            = LoadIcon(NULL, IDI_APPLICATION); 

        RegisterClassEx(&wcex); 

        _hWnd = CreateWindow(g_AppName, g_AppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, GetModuleHandle(NULL), NULL); 

        ShowWindow(_hWnd, SW_SHOWDEFAULT); 
        UpdateWindow(_hWnd); 
    } 

    void message_loop() 
    { 
        MSG msg = {0}; 

        while (GetMessage(&msg, NULL, 0, 0)) 
        { 
            if(msg.message == WM_QUIT) 
            { 
                break; 
            } 

            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 

    static LRESULT WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
    { 
        switch(uMsg) 
        { 
        case WM_DESTROY: 
            PostQuitMessage(0); 
            return 0; 
        case WM_POWERBROADCAST: 
            { 
                //power management code here 
            } 

        } 

        return DefWindowProc(hWnd, uMsg, wParam, lParam); 
    } 
}; 

Вотминимальный загрузчик:

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{

    CMyWindow t(640,480);

    Sleep(10000);

    return 0;
}
0 голосов
/ 14 июня 2010

Я решил сделать его одноэлементным, потому что это основной класс приложения, и нет проблем с наличием одного экземпляра этого класса в программе.

Теперь я хотел бы задать еще одинсвязанный вопрос: предположим, у меня есть диалог предпочтений.Я создаю диалог как ресурс, затем даю ему процедуру, а в процедуре создаю объект контроллера.Это правильный путь?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...