2014-02-12 4 views
3

Этот article объясняет блестяще варианты вызова класса WndProc. Я видел this response in stackoverflow, но основная проблема ассоциирования члена класса WndProc после CreateWindow заключается в том, что некоторые сообщения будут потеряны (включая важный WM_CREATE), как описано в the mentioned article.Метод класса для WndProc

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

Резюмирующий две окончательных решений выставленных в статье (suposing, что она существует класс окна с методом WndProc):

  1. Per-окна данных с this глобального хранения указателей, защищая его с CRITICAL_SECTION, чтобы сделать его поточно (извлеченный из here):

    // The helper window procedure 
    // It is called by Windows, and thus it's a non-member function 
    // This message handler will only be called after successful SetWindowLong call 
    // We can assume that pointer returned by GetWindowLong is valid 
    // It will route messages to our member message handler 
    LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { 
        // Get a window pointer associated with this window 
        Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA); 
        // It should be valid, assert so 
        _ASSERT(w); 
        // Redirect messages to the window procedure of the associated window 
        return w->WndProc(hwnd, msg, wp, lp); 
    } 
    // The temporary global this pointer 
    // It will be used only between CreateWindow is called and the first message is processed by WndProc 
    // WARNING: it is not thread-safe. 
    Window *g_pWindow; 
    
    // Critical section protecting the global Window pointer 
    CRITICAL_SECTION g_WindowCS; 
    
    // The helper window procedure 
    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { 
        // Stash global Window pointer into per-window data area 
        SetWindowLong(hwnd, GWL_USERDATA, (long) g_pWindow); 
        // Unlock global critical section 
        g_pWindow->HaveCSLock = false; 
        LeaveCriticalSection(&g_WindowCS); 
        // Reset the window message handler 
        SetWindowLong(hwnd, GWL_WNDPROC, (long) WndProc2); 
        // Dispatch first message to the member message handler 
        return WndProc2(hwnd, msg, wp, lp); 
    } 
    

    И теперь мы можем создать окно:

    InitializeCriticalSection(&g_WindowCS); 
    // Enter the critical section before you write to protected data 
    EnterCriticalSection(&g_WindowCS); 
    
    // Set global Window pointer to our Window instance 
    // Moved the assignment here, where we have exclusive access to the pointer 
    g_pWindow = &w; 
    
    // Set a flag indicating that the window has the critical section lock 
    // Note: this must be executed after the above assignment 
    g_pWindow->HaveCSLock = true; 
    
    // Create window 
    // Note: lpParam is not used 
    HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, 0); 
    
    // Leave critical section if window creation failed and our window procedure hasn't released it 
    if (g_pWindow->HaveCSLock) 
        LeaveCriticalSection(&g_WindowCS); 
    // Destroy critical section 
    // In production code, you'd do this when application terminates, not immediately after CreateWindow call 
    DeleteCriticalSection(&g_WindowCS); 
    
  2. Использование процедуры ТОС крючок (извлеченный из here):

    // The helper window procedure 
    // It is called by Windows, and thus it's a non-member function 
    // This message handler will only be called after successful SetWindowLong call from the hook 
    // We can assume that pointer returned by GetWindowLong is valid 
    // It will route messages to our member message handler 
    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) 
    { 
        // Get a window pointer associated with this window 
        Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA); 
        // It should be valid, assert so 
        _ASSERT(w); 
        // Redirect messages to the window procedure of the associated window 
        return w->WndProc(hwnd, msg, wp, lp); 
    } 
    
    // The CBT hook procedure 
    // It is called during CreateWindow call before WndProc receives any messages 
    // Its job is to set per-window Window pointer to the one passed through lpParam to CreateWindow 
    LRESULT CALLBACK CBTProc(int code, WPARAM wp, LPARAM lp) 
    { 
        if (code != HCBT_CREATEWND) { 
        // Ignore everything but create window requests 
        // Note: generally, HCBT_CREATEWND is the only notification we will get, 
        // assuming the thread is hooked only for the duration of CreateWindow call. 
        // However, we may receive other notifications, in which case they will not be passed to other CBT hooks. 
        return 0; 
        } 
        // Grab a pointer passed to CreateWindow as lpParam 
        std::pair<Window *, HHOOK> *p = (std::pair<Window *, HHOOK> *) LPCBT_CREATEWND(lp)->lpcs->lpCreateParams; 
        // Only handle this window if it wasn't handled before, to prevent rehooking windows when CreateWindow is called recursively 
        // ie, when you create windows from a WM_CREATE handler 
        if (p->first) { 
        // Stash the associated Window pointer, which is the first member of the pair, into per-window data area 
        SetWindowLong((HWND) wp, GWL_USERDATA, (long) p->first); 
        // Mark this window as handled 
        p->first = 0; 
        } 
        // Call the next hook in chain, using the second member of the pair 
        return CallNextHookEx(p->second, code, wp, lp); 
    } 
    

    И теперь мы можем создать окно:

    // Install the CBT hook 
    // Note: hook the thread immediately before, and unhook it immediately after CreateWindow call. 
    // The hook procedure can only process window creation nofitications, and it shouldn't be called for other types of notifications 
    // Additionally, calling hook for other events is wasteful since it won't do anything useful anyway 
    HHOOK hook = SetWindowsHookEx(WH_CBT, CBTProc, 0, GetCurrentThreadId()); 
    _ASSERT(hook); 
    
    // Create window 
    // Pass a pair consisting of window object pointer and hook as lpParam 
    std::pair<Window *, HHOOK> p(&w, hook); 
    HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, &p); 
    
    // Unhook first 
    UnhookWindowsHookEx(hook); 
    

ответ

5

лично я бы не использовать ни один из этих методов. Подход глобальной переменной работает, но чувствует себя грязным. Особенно с замком. И крючок CBT, а также сверху. Хотя он указывает в правильном направлении.

Стандартный способ передачи информации о состоянии вашей процедуре окна во время создания составляет lpParam параметр CreateWindow или CreateWindowEx. Таким образом, методика заключается в следующем:

  1. Пройди свой указатель на экземпляр в параметре CreateWindow или CreateWindowExlpParam.
  2. Прочтите это значение в обработчике WM_NCCREATE. Это сообщение передает информацию как часть структуры CREATESTRUCT.
  3. Еще в WM_NCCREATE звоните SetWindowLongPtr, чтобы установить пользовательские данные окна в указатель экземпляра.
  4. Все будущие вызовы оконной процедуры теперь могут получить указатель экземпляра, вызвав GetWindowLongPtr.

Raymond Chen иллюстрирует подробности здесь: How can I make a WNDPROC or DLGPROC a member of my C++ class?

+0

В статье Raymond Chen в значительной степени так же, как мое решение этой проблемы. +1. Единственное отличие заключается в том, что функция 'pThis-> WndProc' возвращает флаг, указывающий, следует ли вызывать' DefWindowProc' (ОК, он фактически использует структуру для передачи данных в функцию и обратно, но вы получаете идея) – Skizz

+0

@DavidHeffernan спасибо, у меня была интуиция, что первый метод будет немного «ужасным».Исправьте меня, если я ошибаюсь: я первоначально отказался от этого решения, поскольку он всегда будет сначала называть «статический WndProc», затем «GetWindowLongPtr» и, наконец, «член класса WndProc». Я предположил, что разоблаченные методы в вопросе будут работать лучше, поскольку они заставляют окна напрямую обращаться к члену класса WndProc. Стоит ли делать CBT-крючок с точки зрения производительности? – Miquel

+0

Вы не можете использовать нестатический член класса как 'WndProc'. Это просто неправильное решение. Таким образом, все решения включают чтение экземпляра с помощью 'GetWindowLongPtr' и переадресацию вызова методу этого экземпляра. Все они будут одинаковыми. –