0

I'm working on my own personalized winapi wrapper. My desired syntax is such:

// #define wndproc(name) void name (Window & hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
// #define buttonproc(name) void name (Button & hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

wndproc (rightClick) { //evaluates to function to handle window message
    ::msg ("You right clicked the window. Closing window...");
    hwnd.close(); //close() is implemented in my Window class
}

buttonproc (buttonClick) { //same thing basically
    ::msg ("You clicked this button. I'm going to hide the other one...");

    //if text on this button is "One button", find the one belonging to parent
    //with the text "Other button" and hide it, or vice-versa
    hwnd.text == "One button"
    ? hwnd.parent().button ("Other button").hide();
    : hwnd.parent().button ("One button").hide();
}

int main() {
    Window win; //create default window
    win.addmsg (WM_LBUTTONDOWN, rightClick); //look for l-click message and call that

    Button b1 (win, "One button", 100, 100, 50, 20, buttonClick); //parent, coords, size, clicked
    Button b2 (win, "Other button", 200, 100, 50, 20, buttonClick);  

    return messageLoop(); //should be self-explanatory
}  

The thing is, in wndproc, hwnd is a Window & and in buttonproc, hwnd is a Button &. I might be able to get away with saying:

msgproc (Window, rightClick){...} 
msgproc (Button, buttonClick){...}

The problem is in the fact that I have to call these procedures and give them the right hwnd. My main window procedure is implemented in my Window class. It gets the four normal arguments. If I need to pass on a WM_COMMAND message to the right button procedure, I'd like to give it the corresponding Button object.

The way it is currently, I pass a pointer to the superclass of both Window and Button. Of course it creates convoluted code such as:

((Window *)hwnd)->operator()() //get HWND of the Window

It doesn't seem to really work that well anyways. Unfortunately, the only way I can think of at the moment to do so is to keep a list of every Button created and pull the right one out. I could even extend this to all possible recipients.

The advantage to doing it this way is that my Button class has a static window procedure that is called any time a WM_COMMAND message is found. I haven't added other controls, but it is designed to work by checking the id with existing ones and calling the procedure you specify when you create the button if it's a match. The thing is, when this is done, any other things (like a checkbox) that add the WM_COMMAND handler will be called as well.

I was thinking of keeping a list in Window of every HWND child and its corresponding object. This way I can just nuke the extra procedures in every class like the Button, which will cause a lot of extra processing to occur, and replace proc [i] ((BaseWindow *)hwnd, msg, wParam, lParam) with something like proc [i] (control [loword(wParam)], msg, wParam, lParam) for WM_COMMAND, using lParam to see whether it's a control.

It seems like I'm missing something big though. Chances are I'll start to implement this and then run into a major problem. Is there a better way to do all of this?

While I'm at it, is there a way to make a control() function that returns the correct object type (Button, Checkbox...) depending on which one it finds the id to correspond to instead of just an array of varying objects (which I'm pretty sure I've seen a way to do)?

chris
  • 60,560
  • 13
  • 143
  • 205
  • You're clearly using C++, so why use macros in the first place? – Cody Gray - on strike Feb 24 '12 at 22:32
  • @Cody Gray, The reasoning behind that was to eliminate the commonly used list of parameters. It is quite a bit to type out every time. – chris Feb 24 '12 at 22:37
  • Right, I get that. Why not use a virtual function? You're already defining `Window` and `Button` classes anyway. – Cody Gray - on strike Feb 24 '12 at 22:47
  • Could you explain how I could use one? I'm not picking up on the hint. – chris Feb 24 '12 at 23:14
  • If you don't know what virtual functions are, you need to get [a book that teaches you the C++ language](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) before going any further. Once you've learned C++, start with Raymond Chen's [C++ scratch program](http://blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx) and customize accordingly. – Cody Gray - on strike Feb 24 '12 at 23:16
  • I remember virtual functions well. I'm just not sure what you're suggesting. – chris Feb 24 '12 at 23:21

1 Answers1

1

The question was how to handle WM_COMMAND messages which get delivered to the parent rather than the button class (or whatever).

The simplest fix is to add a WM_COMMAND handler to the window base class to forward the message to the control that generated it. The message will then get handled in the control's class.

The code in the WM_COMMAND handler might look something like this:

if (lParam != 0)
{
    // lParam non zero so this is a control notification.
    if ((HWND)lParam == hWnd)
    {
        // The message has arrived at its destination
        return OnNotify(HIWORD(wParam), LOWORD(wParam));
    }
    else
    {
        // Reflect the message back to the control.
        return SendMessage((HWND)lParam, WM_COMMAND, wParam, lParam);
    }
}

I originally misunderstood the question. The following describes a couple of ways of routing messages to objects:

The MFC way

Every window uses the same window procedure. You have a global map from HWND to window object. (The map is actually per-thread, but in most apps this is unnecessary.) When a message arrives you look up the object and dispatch the message to it. If Button derives from Window then it's very easy to do per-class processing.

One slight complication is that if you want to capture messages generated while the window is being created you need to add the window to your map in the global window procedure.

The ATL way

Every window has its own window procedure (possibly its own class too, to make it easy to set the window procedure initially; I forget). The window procedure is a generated stub that loads up a pointer to the object* and jumps to the base class's window procedure (which is a non-static member function). (To keep it simple, the stub should jump to a non-virtual wndproc in the base class which calls the "real" virtual wndproc.) Apart from changing the way HWNDs map to objects this is, in other respects, basically the same as the MFC model.

*On x86 the stub puts the object pointer in ECX before jumping to the wndproc. This would also work on x64 (though I don't know if it does work like this) but the object pointer would overwrite the HWND (so the base class non-virtual wndproc wouldn't have an HWND parameter).

arx
  • 16,686
  • 2
  • 44
  • 61
  • One problem I have with MFC is that it's using managed code to write a wrapper. I think it defeats the learning experience a bit, and I'm much more familiar with native code anyways. The way it's set up now is your ATL way. My superclass of which both Window and Button derive from has the window procedure, and it forces each inherited class to implement its own, which it calls when it's passing messages along. The thing is, though, that when a control message comes through, it is a WM_COMMAND to the parent. The destination is the wrong procedure for my needs. – chris Feb 24 '12 at 23:18
  • I would also like to think that Button could derive from Window, but they have completely different functions. The relationship is instead parent-child. While my Window class can become topmost, go fullscreen, close, a button wouldn't be able to use any of those. – chris Feb 24 '12 at 23:20
  • MFC predates .Net. I haven't used it for a while, but are you sure it's using managed code? That would kill one of its main advantages (not needing the .Net runtime). Yes, WM_COMMAND goes to the parent but (for controls) it contains an HWND so you just forward it to the right object. And why not have Button and TopLevelWindow (which does fullscreen, close, etc.) both deriving from Window? – arx Feb 24 '12 at 23:52
  • My understanding of managed code may have been wrong. I meant wrapped code, as it basically puts everything into classes and such, but misses out on the main things I want my wrapper to be (personal, either easy to read or easy to write, depending on the situation). I think I see what you're saying with the forwarding now, I never thought of it this way before. Basically the procedure in Window (your TopLevelWindow) passes along WM_COMMAND the same way that BaseWindow (your Window) passes along everything? – chris Feb 24 '12 at 23:57
  • Fair enough; managed code means it runs on .Net. Even for a small wrapper, I do think it's clearer to put everything in classes. The easiest way to forward WM_COMMAND it just to call SendMessage again (with the new HWND), then it will automatically arrive at the Button. This code should go in BaseWindow rather than Window because child windows can themselves contain further child windows. – arx Feb 25 '12 at 00:07
  • Thanks, if this works out it'll save me a lot of time and keeping track of things. – chris Feb 25 '12 at 00:10
  • I got it working well with Window messages. I added a Button WndProc to call the one specified in the constructor. I added the following lines in the fake window procedure (BaseWindow) right before the ones that call other window procedures: `if (msg == WM_COMMAND && lParam != 0) ((BaseWindow *)GetWindowLongPtr ((HWND)lParam, GWLP_USERDATA))->_WndProc (hwnd, msg, wParam, lParam);` The `(HWND)lParam` is to get the HWND of the button. Each derived window fills a pointer to the object into USERDATA on creation. The `(LPARAM)hwnd` is to pass the parent. It AVs (0xC0000005) on click. – chris Feb 25 '12 at 01:44
  • The WndProc of Button just calls the proc with `(*this, msg, wParam, (LPARAM)hwnd)` The lParam (button handle) basically dies and the hwnd becomes the lParam. When I call this function directly from main after creating the button it works. `Button b (...); b._WndProc (win(), WM_COMMAND, makelong (0, b.id()), b());` SendMessage basically sends it back to the fake procedure I would think. In any case, SendMessage did nothing when I clicked the button. – chris Feb 25 '12 at 01:49
  • It's not clear from your code if this is a problem, but: when the message arrives at the button, are you trying to forward it again? – arx Feb 25 '12 at 02:02
  • I apologize if that last bit seemed a bit rude. I was being rushed to go do something and wanted to get it out there if there was an obvious solution. I'll try your WM_COMMAND handler (I've never seen `OnNotify`, but of course I'll look it up :)). To answer your question, yes. In the Button constructor, I save the procedure address you give it to call. In the WndProc, I call that address, but I use `*this` for the first argument and `LPARAM(hwnd)` for the last, so the procedure will have easy access to the parent, since lParam is normally the button handle, which can be accessed by `hwnd()`. – chris Feb 25 '12 at 02:49
  • Oh, I didn't realize `OnNotify` was an MFC function. Is there an alternative I'd be able to use instead? I can't really compile MFC at all as I don't have a full VC++ and I use C::B for all my programming needs, which has worked out pretty well so far. If it's MFC though, there should be a (hard-to-find) alternative in raw winapi right? – chris Feb 25 '12 at 03:18
  • Actually, I sort of found an answer to my above post. Anyways, when the button is clicked, the `SendMessage` portion is called. The thing is that `(HWND)lParam`'s window procedure is the very same one this code is in. – chris Feb 25 '12 at 03:40
  • Okay, I fixed my problem :) I'm just not *exactly* sure why. When the BaseWindow create() is called, it sets the last argument for createwindowex to `this`. In the wndproc, it catches WM_NCCREATE and uses SetWindowLongPtr to set GWLP_USERDATA. I tried changing it so that it uses a pointer to the derived object instead, no luck. Then I just moved the SetWindowLongPtr to the create() function, right after CreateWindowEx. This worked fine. I then noticed the original was passing a long, not LONG_PTR. Still didn't work like that, so now it's back in create(). Do controls not get WM_NCCREATE sent? – chris Feb 25 '12 at 14:37
  • In the process, I also worked in create() as a template function in order to pass a pointer to the real object for use in GWLP_USERDATA. This made me realize later that a `parent()` function is really easy. `return ((BaseWindow *)GetWindowLongPtr (GetParent (_hwnd), GWLP_USERDATA));` I'm *pretty* sure I don't need the template though. Anyways, you got me on the right track, very close to where I needed to be and provided perfect syntax. I should've thought of following that process earlier and provided the code. Thanks for the help :) – chris Feb 25 '12 at 14:45
  • @chris: OnNotify is coincidentally an MFC function, but in the context of the code snippet I posted it's just a function you implement yourself to handle the command. Controls do get WM_NCCREATE. Spy++ is a useful tool for seeing what's going on with messages. – arx Feb 26 '12 at 15:30