tags:

views:

151

answers:

4

There is a simple WinAPI application. All it does currently is this:

  • register a window class
  • register a tray icon with a menu
  • create a value in the registry in order to autostart
  • and finally, it checks if it's unique using a mutex

As I'm used to writing code mainly in C++, and no MFC is allowed, I'm forced to encapsulate this into C++ classes somehow. So far I've come up with such a design:

  • there is a class that represents the application
  • it keeps all the wndclass, hinstance, etc variables, where the hinstance is passed as a constructor parameter as well as the icmdshow and others (see WinMain prototype)
  • it has functions for registering the window class, tray icon, reigstry information
  • it encapsulates the message loop in a function

In WinMain, the following is done:

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

and the constructor does the following:

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

So far so good. Now the question is : how do I create the window procedure (must be static, as it's a c-style pointer to a function) AND keep track of what the application object is, that is, keep a pointer to an Application around.

The main question is : is this how it's usually done? Am I complicating things too much? Is it fine to pass hInstance as a parameter to the Application constructor? And where's the WndProc?

Maybe WndProc should be outside of class and the Application pointer be global? Then WndProc invokes Application methods in response to various events.

There's one more possible solution : make the application class a singleton. Then it's trivial to obtain the handle to that object from the WndProc.

+4  A: 

The answer is SetWindowLongPtr. It allows you to associate a void* with a given hWnd. Then, in the WndProc, you just extract said void*, cast, and call the member method. Problemo solvo. There's a few ups/downs with SetWindowLongPtr, you must call some other function to see the effects or somesuch BS, and Windows sends messages before CreateWindowEx returns, so you must be prepared for GetWindowLongPtr(hWnd, GWL_USERDATA) to return NULL.

This of course means that for a given WindowProc, all instances that use it must have a common interface, since there's not much you can do with a void*.

And, yes, it's fine to pass HINSTANCE to the App constructor. I've seen samples that do something strange to avoid this but I never made it work myself.

Edit: Don't confuse Get/SetWindowLong with Get/SetWindowLongPtr. Get/SetWindowLong is deprecated and unsafe.

DeadMG
This is just what I've been looking for. So WndProc should be outside of the application class, and this is the preferred design? (In general, making a C-style API work with a C++ design may seem flawed and a pain in the neck, but I have to do this)...
Semen Semenych
You can have WndProc as a static member of the class. Pass `this` via SetWindowLongPtr, retrieve it in WndProc and then dispatch to the other class members (which can then be virtual as well). Problem solved.
Axel Gneiting
This is no longer used even by MFC, as it is insecure! What if someone else uses SetWindowLongPtr and changes your this pointer to some random value?
Lorenzo
MFC uses a global Map `<HWND, CWnd *>` (roughly). ATL uses a WNDPROC-Stub that is generated on the fly for each window, and passes the instance pointer instead of the HWND. Anyway, either if the three methods (including setWindowLongPtr) is ok, but the latter two also work with windows where you don't control the WNDCLASS (such as when you are subclassing someone elses EDIT control).
peterchen
Lorenzo, that's why you encapsulate the HWND. I mean, what if someone else dereferences a NULL pointer? What if someone else has a buffer overflow? You can't enforce the security of other people's code, it's incumbent on them not to mess with your code in stupid ways.
DeadMG
You can't enforce other people's security, but you have to write defensive code to protect yourself. Today any serious framework would use GWL_USERDATA to store class pointer.
Lorenzo
Yes. "you must be prepared for GetWindowLongPtr(hWnd, GWL_USERDATA) to return NULL."
DeadMG
@Lorenzo: maybe I'm just slow, but what are you saying is insecure, and what are you recommending instead? As I read the comment,s you're first saying that storing the pointer as GWL_USERDATA is insecure because it can be overwritten, and then that "any serious framework" would do just that. Am I missing something obvious here?
jalf
Sorry, my English was bad. Of course, I actually mean that today NO serious framework would use GWL_USERDATA to store class pointer...
Lorenzo
If you're writing a framework, it's a different thing to writing one application. So what if someone else can override or read your class pointer? It's not my responsibility to stop other people writing stupid code, and if an attacker is already in your address space, then you have bigger problems.
DeadMG
Good point Lorenzo. Never thought about it that way. That's of course a show stopper for doing it that way.Is there really no way to prevent applications to mess with my WindowLongPtrs?
Axel Gneiting
@DeadMG: bad programming practices are still bad even if you are developing "just one application".
Lorenzo
@Axel: there's no way, it is something that can be manipulated freely by other processes, like the window text or a window style. However, we should check with Vista/7 isolated processes.
Lorenzo
According to msdn, `Windows XP/2000: The SetWindowLongPtr function fails if the window specified by the hWnd parameter does not belong to the same process as the calling thread.`, I'm not sure how vista/7 handles this, though it's most likely the same. Also, as DeadMG says, if it is a framework, and the "developer" changes the pointer, then it's his problem. If it is an application, SetWindowLongPtr will not work from "outside". Defending against overwriting raw memory by figuring out where the pointer is located is the operating system's job.
Neruz
+1  A: 

You could extend this class (which I used for an answer here) as you wish, depending on what messages you want to handle.

#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); 
    } 
}; 

Here's a minimal bootstrapper:

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

    CMyWindow t(640,480);

    Sleep(10000);

    return 0;
}
Indeera
And where's WinMain here? Or do you simply create a CMyWindow object in the main? And, most important, where's hInstance? (Sorry, I'm no expert in WinAPI yet, I'm just reading the Petzold book, and I have seen nothing of the kind in there)
Semen Semenych
This snippet just takes care of creating the window and providing the `WndProc()`. You create your own WinMain, create the HINSTANCE, then instantiate this class.
spoulson
You can retrieve the executable's HINSTANCE by calling GetModuleHandle(NULL).
shambulator
+2  A: 

Don't follow the hint to use Get/SetWindowLongPtr to store your this pointer, as it is a huge security hole! You just have to use a map to associate the HWND to the pointer to the class instance. You can use the <map> class from STL.

By the way, you can find a very good discussion on this topic there: http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx

Lorenzo
Good point. An attacker could rummage through data found by retrieving this pointer from another process.
spoulson
Thank you for the link; however, that code uses SetWindowLogPtr as well, if I get it right.
Semen Semenych
Sure: you have to follow the interesting discussion in the comments.
Lorenzo
The discussion in the comments in which Raymond Chen is unconcerned about someone else setting GWLP_USERDATA? (unless in the case of writing a control to be used by others.) After seeing the (admittedly, very clean to the end-user) hideous thunking chicanery that ATL pulls to enable this scenario, I decided to go the SetWindowLongPtr route too. Initially I had the same concern that @Lorenzo does, but really - what's going to happen except that your application crashes? An attacker who can rummage using a "this" pointer already has access to your address space anyway!
shambulator
You're absolutely right, an attacker that already has rights to access your address space will likely not change your this pointer. However, even if malicious code could just crash your code, this could still lead to security issues in the form of denial of service. I agree that the ATL way is hideous, but we have to admit it is the cleanest path for class users.
Lorenzo
I'm afraid I won't be able to use the method described in the article, as I'm using the CreateDialog function with a dialog template to create my main window. So there's no CreateWindowEx call and thus I can't pass any additional parameters to WndProc.
Semen Semenych
You can use the same trick with templated dialogs by using CreateDialogParam and DWLP_DLGPROC.Storing a this pointer in GWLP_USERDATA isn't a security risk because you can't call SetWindowLongPtr on higher privilege processes' windows (or any other process' windows in 2000/XP).
VenusianKarate
A: 

I've decided to make it a singleton, because it's the main application class and there's no problem in having a single instance of that class in the program.

Now I'd like to ask one more related question : suppose I have a preference dialog. I create the dialog as a resource, then give it a procedure, and in the procedure, create a controller object. Is that the right way?

Semen Semenych
So your justification for making a singleton is that "it's no problem"? A sane programmer would use a singleton **if it is beneficial**, not just if "it does no harm here and now". Singletons are a terrible idea at least 99.9% of the time.
jalf
Well yes they are, but under these circumstances, you see, a singleton is the only solution that doesn't deal with the Set/GetWindowLongPtr functions, so it is beneficial.
Semen Semenych