Thread: template fn replacements for msg macros

  1. #1
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227

    Question template fn replacements for msg macros

    I've been toying with template functions as a first step/ exploration/attempt to devise a c++ replacement for message macros from mainly windowsx.h. I've read some stuff on line - comparisons of different templatised techniques, functors etc and am using Stroustrup's C++ and Josuttis for reference.

    In general: I have a base wnd class (C++) from which is derived various windows and controls, the hierarchy is something like BaseWnd::Wnd::MainWnd where MainWnd is a 'container' or frame for just about everything else. Currently I use a static wndproc which forwards messages to a virtual class wndproc which is overridden in descendant classes as required. I use msg macros in these virtual wndprocs as follows, eg:
    Code:
    LRESULT CALLBACK MainWnd::MsgProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
    {
    switch (uMsg)
      {
      HANDLE_MSG(hwnd,WM_ERASEBKGND,OnEraseBkGrd);
      }
    }
    where the HANDLE_MSG macro expands into the case statement and the call to the OnEraseBkGrd handler for WM_ERASEBKGND as defined in windowsx.h. I have included one msg only for simplicity.

    This is the template fn to 'replace' it:
    Code:
    template <class T,class W>
    inline OnEraseHandler(T *ObjPtr,BOOL (W::*FnPtr)(HWND,HDC),HWND hwnd,WPARAM wParam,LPARAM lParam)
    {
    (ObjPtr->*FnPtr)(hwnd,reinterpret_cast<HDC>(wParam));
    }
    and the virtual wndproc becomes:
    Code:
    LRESULT CALLBACK MainWnd::MsgProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
    {
    switch (uMsg)
      {
      case WM_ERASEBKGND:
        return OnEraseHandler(this,&MainWnd::OnEraseBkGrd,hwnd,wParam,lParam);
      }
    }
    Which works ok but is ugly.

    The problem: I want the template to function like the macro (I wouldn't mind if the syntax was equally simple too!) in that I can call any handler within the class hierarchy from it, eg:
    Code:
    HANDLE_MSG(hwnd,WM_ERASEBKGND,Wnd::OnEraseBkGrd);
    will call the required fn; if I try the same thing with the template fn ie:
    Code:
    return OnEraseHandler(this,&Wnd::OnEraseBkGrd,hwnd,wParam,lParam);
    It fails (presumably because it's using this to determine the object. This fails too:
    Code:
    return OnEraseHandler<Wnd,Wnd>(this,&Wnd::OnEraseBkGrd,hwnd,wParam,lParam);
    When I speak of failure what I mean is that the handler called is always the one currently in scope ie MainWnd::OnEraseBkGrd.

    So, if anyone has been there and done that could you please give me some hint as to how I might coerce the template to call any handler in the class hierarchy I might explicitly choose or perhaps an explanation of why it's not possible to do so. Note that the handler functions (C++), in this case OnEraseBkgrd, are virtual.

    Thanks in advance for your time and consideration.

    edit: typos, formatting
    Last edited by Ken Fitlike; 10-24-2002 at 03:06 PM.

  2. #2
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    Ok, so are you saying that you have something like:

    baseClassA->classB->classC->classD;

    ...where each class may have defined f(), and you now want object of type classD use classB's f() ?

    I had the same problem, but never ended up solving it due to other issues[ templates<can suck sometimes> ]

    BUT I may be working on that problem in the next few,
    if I stumble upon anything I'll PM or EM you....

    One thing, though. From the looks of it, you are passing the MainWin's "this" to every handler. Am I wrong? Or is it that you just have a WindProc for every object? Confused there...FYI, I have discarded the WindProc in favor of a simpler method. In fact, I'll just tell you about it real quick Ok. Basically, a class simply "registers" a message it's interested in and at the same time, passes in the handler. The handler is cast to a pointer to the very lowest base-class type, and it and the message are added to a list that every object builds up. A sort of internalized message loop.

    Register(&MainWindow::CloseProgram, WM_DESTROY);

    for example, expands to:

    #define Register(F,D) _register_message_(static_cast<void(Base::*)(void) >(F),(D))

    The one and only WindProc is run from the MainWin object, and never gets modified. It looks like this:


    Code:
    static LRESULT CALLBACK Processor(HWND curr_hwnd, UINT message, WPARAM wParam, LPARAM lParam)
     {
      BaseWindow* curr_window
       = (BaseWindow*)GetWindowLong(curr_hwnd, GWL_USERDATA);
    
      if( curr_window == NULL ) {
       return CallWindowProc(DefWindowProc, curr_hwnd, message, wParam, lParam);
       }
    
     curr_window->Respond(message, wParam, lParam);
    
    return CallWindowProc(DefWindowProc, curr_hwnd, message, wParam, lParam);
    }
    Respond() just traverses the list of that object that was packed by Register(), and if it holds that message, the coresponding function pointer is invoked.

    I chose this route because I wanted to stop having to modify the WindProc. So for that, it works exceptionally. In fact, here is how flexible everything works out:

    Code:
    class button : public Button {
    public:
    
    button(int w, int h) {
     SetWidthHeight(w, h);
     Register(&button::Hello, WM_LBUTTONDOWN);
    }
    
    void Hello() {
     Alert("Stop clicking me!!!");
    }
    };
    
    button b(100, 25);
    
    int WINAPI WinMain(/**/){
     
     win.Create(); //..use defaults, for clarity.
     
     b.Create(&win);
    
     return win.RunMessageLoop();
    }


    Now that may not lend itself to your coding preferences very much (void functions, etc...) but the message mapping facility is handy. Also, the lack of a visible WindProc makes coding move quicker. The above program compiles and runs as is, and although simple, is a hundred lines shorter than the straight API version. Again, I realize you are close to finishing that framework, but there are crumbs that can be scavanged here.. I would really like to see what route you're taking, in more detail, if you don't mind. I've revised mine so much I don't know which I'm working on at the moment, and I always seem to be yearning for better ideas =)

    -Cheers.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  3. #3
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    First off, thanks very much for your thoughtful and detailed reply,Sebastiani - much appreciated.

    >>...where each class may have defined f(), and you now want object of type classD use classB's f() ?<<

    Yes; although, realistically, I would probably only want the facility for 'completeness' and debugging purposes.

    >>One thing, though. From the looks of it, you are passing the MainWin's "this" to every handler. Am I wrong? Or is it that you just have a WindProc for every object?<<

    Yes, I am passing this to each global inline template function msg cracker. This is a first pass using templates so before I start encapsulating their functionality with c++ classes I want to see how the syntax works and the processes resolve themselves: in short it's experimental. As I said previously I have a static wndproc that works pretty much the same as your own with the GetWindowLong/SetWindowLong for retrieval and setting of a 'window c++ class object' pointer. This then calls a virtual WndProc (called 'MsgProc') which is overriden in derived classes. The top level class is the only one that really does any handling/cracking of messages, those lower in the hierarchy exist simply for default msg handling.

    >>#define Register(F,D) _register_message_(static_cast<void(Base::*)(void) >(F),(D))<<

    This is the reason for my interest in templates: I want to purge all macros (if possible). Otherwise i'm very happy with using HANDLE_MSG macros as they do everything I require (apparently safely), when I require and where I require. I have examined similar techniques of using message 'registration' fns with or without look-up tables but am currently of the opinion that they are an unnecessary complication - that view may change.

    >>Again, I realize you are close to finishing that framework, but there are crumbs that can be scavanged here<<

    Actually the 'framework' was complete a couple of years ago, the only new thing is the experiment with templates. Previously i've been reluctant to even consider dropping HANDLE_MSG macros but I keep reading that macros in c++ are the devil's work..

    >>I would really like to see what route you're taking, in more detail, if you don't mind.<<

    I will pm you.

    >>I've revised mine so much I don't know which I'm working on at the moment<<

    Mine is fairly stable and only minor tweaks to test newer notions from time to time.

    >>and I always seem to be yearning for better ideas<<

    OK. Here's a possible one, if you haven't already investigated to circumvent the hassle of this being unavailable through GetWindowLong until the arrival of the WM_NCCREATE msg: using a CBTProc hook procedure, setting the hook before the call to CreateWindowEx and unhooking on return from the CreateWindowEx. By registering your app as a computer based training app, you get the wnd handle and therefore access to lpCreateParams(?) before any message is dispatched to your window procedure. There are two downsides, one minor the other major. The minor downside is that the hook procedure is slower but since it is active only during window creation the very tiny performance hit is negligible. The second downside is a major pain (unless you maybe use global variables which will impact on thread safety): mdi windows can't easily be tamed by this method and cause horrible crashes that vary in seriousness between win98 and win2k. Of course, if you're not using mdi's then this technique is the way to go. I'm sure there is probably a way to avoid the troubles I have had with mdi's too.

    The various strategies people use to tame win32 API to c++ classes is a minor obsession of mine, so I fully understand and share your 'yearning' in this area.

    edit: typos, formatting
    Last edited by Ken Fitlike; 10-26-2002 at 09:52 AM.

  4. #4
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    So you want to emulate the macros of windows API with templates because macros are gross. hmmmm... I've always been fine with just a virtual int CallBack(UINT message, WPARAM wParam, LPARAM lParam); in the top base class. I usually don't look to hide the way the API works when I write my code. a class with an HWND, a CallBack(), a CreateWindow(), etc... should get the job done nicely. Do you just not like seeing the switch?

    edit: I just realized what you're doing. I don't however see the need for the HANDLE_MSG macro and I usually remove it from code where I see it. I would rather see what's going on behind that macro in the code in front of me. That's just me though.
    Last edited by FillYourBrain; 10-25-2002 at 12:26 PM.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  5. #5
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Thanks for sharing your observations, FYB.

    >>I don't however see the need...That's just me though.<<

    Each to their own, eh?
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  6. #6
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    I vaccilate between two extremes: Liking the "look" of straight code ( it looks complex and sophisticated ) and wanting the most expedient method to accomplish something ( looks like Java, ugh! ). To be honest, I can't make up my mind! What I should do is write a program that will expand the latter into the former and condense the former into the latter, on command. /*strikes head: "doh! no more projects!"*/ In a way, C++ is a sickness because it encourages profuse abstraction, and at the end of the day you find yourself writing programs you loved to hate as a C programmer:

    Code:
    int main() {
    
    Application launch(true, false, true, true, sizeof(Application));
    
    return launch.outcome();
    }
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  7. #7
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    really, I'm not saying abstraction is bad. But using templates as if they are macros gives you all the bad things we hate about macros! obvious exceptions include being able to step through the thing in the debugger.

    I like the abstraction/structure that C++ gives you but I believe templates serve a much bigger purpose than just a glorified or C++ macro. That's all I'm saying. I think abstraction should live behind the boundaries of class interface, that's all. The code should be straight forward and macro free. My angle is not one of disliking abstraction but one of not obuscating my code. U C ?
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  8. #8
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    >>FYB: really, I'm not saying abstraction is bad<<

    Where are you getting the impression that anyone thinks that you have said so? ::surprised::

    >>FYB:But using templates as if they are macros<<

    >>KenF: as a first step/ exploration/attempt to devise a c++ replacement for message macros<< and >>KenF: in short it's experimental.<<

    >>FYB: obvious exceptions include being able to step through the thing in the debugger.<<

    >>KenF: I would probably only want the facility for 'completeness' and debugging purposes.<<

    >>FYB: That's all I'm saying.<<

    Yep. Got that. Twice now.

    >>FYB: not obuscating my code.<<

    What's obfuscating to one is crystal clear to another. For instance:
    Code:
    HANDLE_MSG(hwnd,WM_ERASEBKGND,OnEraseBkGrd);
    is a lot clearer to me personally than:
    Code:
    OnEraseBkGrd(hwnd,(HDC)wParam);
    or:
    Code:
    OnEraseBkGrd(hwnd,reinterpret_cast<HDC>(wParam));
    or even:
    Code:
    OnEraseHandler(this,&MainWnd::OnEraseBkGrd,hwnd,wParam,lParam);
    I'm in good company too: J. Richter of Programming Apps for Ms Windows (4th ed.) fame uses msg macros - in c++ too - and has even gone so far as to devise macros to ensure the correct return type for dialogprocs (ie BOOL). As another example, I am not content to use 'int CallBack(UINT message, WPARAM wParam, LPARAM lParam);' because the API lists LRESULT as the correct return type for a wndproc and indeed, the definitions for LRESULT are different in the aug2001psdk windef.h than in previous versions; using the ms defined LRESULT ensures better future proofing, in my view anyway.

    So, once more, each to their own, eh?

    >>FYB: The code should be straight forward and macro free.<<

    I agree, but straight forward, like obfuscation, appear to be fairly subjective.

    >>FYB: UC?<<

    Good Lord! Subtlety? At cprog? Surely not. I've a good mind to report you to the mods for that one.

    No disrespect intended to your good self, but what you're really saying, FYB, getting back to my original question of:

    >>KenF: please give me some hint as to how I might coerce the template to call any handler in the class hierarchy I might explicitly choose or perhaps an explanation of why it's not possible to do so.<<

    is that you don't know?

    While your opinions are always most welcome and interesting to consider, major appreciation points are there to be won if you can actually answer the question.

    edit: typos.
    Last edited by Ken Fitlike; 10-28-2002 at 04:26 PM.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  9. #9
    Skunkmeister Stoned_Coder's Avatar
    Join Date
    Aug 2001
    Posts
    2,572
    You should research "the command pattern" first documented in the Gang of Four book Design patterns:Elements of reuseable object-oriented software.
    You should invest in the Andrei Alexandrescu book Modern c++ design. This devotes a whole chapter to generalising "the command pattern" with functors.
    Free the weed!! Class B to class C is not good enough!!
    And the FAQ is here :- http://faq.cprogramming.com/cgi-bin/smartfaq.cgi

  10. #10
    erstwhile
    Join Date
    Jan 2002
    Posts
    2,227
    Thanks for the suggestions and book recommendation, Stoned_Coder - greatly appreciated.

    edit: typos again.
    CProgramming FAQ
    Caution: this person may be a carrier of the misinformation virus.

  11. #11
    Registered User
    Join Date
    Sep 2002
    Posts
    272
    I may be way off base here, as I gave up trying to coerce the win32 API a while back, but this coerces a derived into a base class using via global template function -

    Code:
    #include <iostream>
    #include <string>
    
    using std::cout;
    using std::string;
    
    template <class T,class W>
    void msg_handler(string a,T tp, void (W::*Fptr)(string))
    {
    	(tp->*Fptr)(a);
    }
    
    struct base_window
    {
    	virtual void call_msg(string a)
    	{
    		msg_handler<base_window*,base_window>(a,this,&base_window::msg);
    	}
    
    	virtual void msg(string a)
    	{
    		a+=" base window";
    		cout << a << '\n';
    	}
    
    };
    
    struct derived_window : public base_window
    {	
    	void msg(string a)
    	{
    		a+=" derived window";
    		cout << a << '\n';
    	}
    };
    
    int main()
    {
    	base_window* dw= new derived_window;
    	((base_window)(*dw)).call_msg("Hello");
    	dw->call_msg("Goodbye");
    
    	return 0;
    }
    Joe

  12. #12
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    woah, KF. I wasn't being nearly as confrontational as it seems I appeared. sorry chief. I just was trying to understand the rational behind what you're doing.
    Good Lord! Subtlety? At cprog? Surely not. I've a good mind to report you to the mods for that one.
    I'm probably just slow but I don't get this. (plus I didn't get much sleep last night)

    not having read the entire set of Sebastiani responses I don't know that you've come to a conclusion but your template function's T and W are being set to the base class. so you are getting a function pointer to the base class handler. You are bypassing C++'s built in polymorphism by using the passed in function. Why not use non-global methods on the ObjPtr instead? that would not bypass the polymorphism you're trying to obtain. The only other alternative is to pass in the proper T and W instead at the inherited class.

    This is what I'm referring to
    OnEraseHandler<Wnd,Wnd>(this,&Wnd::OnEraseBkGrd,hw nd,wParam,lParam);(
    Of course it will use the base class function. you pass in the &Wnd::OnEraseBkGrd pointer!

    how's that bro?
    Last edited by FillYourBrain; 10-29-2002 at 08:24 AM.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  13. #13
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    You may be looking to keep it inline thus eliminating the polymorphism aspect. fine. Then you could make the message map itself a template.

    template <class T>
    LRESULT CALLBACK T::MsgProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)

    Then it would expand to the proper handlers by sending in T as the class for the handler functions as well. Then you'd get the right class's handler functions. This way you can keep your inline and pretend to be polymorphic at the same time (which I assume is one of the goals).

    what I'm getting at:
    Code:
    //the handler
    template <class T>
    inline OnEraseHandler(T *ObjPtr,BOOL (T::*FnPtr)(HWND,HDC),HWND hwnd,WPARAM wParam,LPARAM lParam)
         {
         (ObjPtr->*FnPtr)(hwnd,reinterpret_cast<HDC>(wParam));
         }
    
    //the callback
    template <class T>
    LRESULT CALLBACK MainWnd::MsgProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
         {
         switch (uMsg)
              {
              case WM_ERASEBKGND:
                   return OnEraseHandler<T,T>(this,&T::OnEraseBkGrd,hwnd,wParam,lParam);
              }
         }
    then you can just use the appropriate MsgProc in your inherited class....

    MsgProc<InheritedClass>

    again, I apologize for any misunderstanding. I was making discussion, not arguing.
    Last edited by FillYourBrain; 10-29-2002 at 04:03 PM.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  14. #14
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    in fact, why not make "CreateWindow" a template function, thus you can just use the 'T' used to create CreateWindow<T>() to set the right callback.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

  15. #15
    pronounced 'fib' FillYourBrain's Avatar
    Join Date
    Aug 2002
    Posts
    2,297
    I have an interesting idea for you...
    check out this sample use of templates.
    Code:
    #include <iostream>
    
    template <class T>
    
    class A
         {
         public:
              static void Function()
                   {
                   T::Func();
                   }
    
              inline static void Func()
                   {
                   std::cout << "A" << std::endl;
                   }
         };
    
    class B : public A<B>
         {
         public:
              inline static void Func()
                   {
                   std::cout << "B" << std::endl;
                   }
         };
    
    class C : public A<C>
         {
         public:
              inline static void Func()
                   {
                   std::cout << "C" << std::endl;
                   }
         };
    
                         
    int main(void)
         {
         B::Function();
         C::Function();
         return 0;
         }
    in this example, "Function()" could easily be a wndproc and "Func()" could be a given message handler. This way, you derive from a template instance of A or MainWnd in your code.
    "You are stupid! You are stupid! Oh, and don't forget, you are STUPID!" - Dexter

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Code review
    By Elysia in forum C++ Programming
    Replies: 71
    Last Post: 05-13-2008, 09:42 PM
  2. Specialising a member function with a template template parameter
    By the4thamigo_uk in forum C++ Programming
    Replies: 10
    Last Post: 10-12-2007, 04:37 AM
  3. Screwy Linker Error - VC2005
    By Tonto in forum C++ Programming
    Replies: 5
    Last Post: 06-19-2007, 02:39 PM
  4. error: template with C linkage
    By michaels-r in forum C++ Programming
    Replies: 3
    Last Post: 05-17-2006, 08:11 AM
  5. oh me oh my hash maps up the wazoo
    By DarkDays in forum C++ Programming
    Replies: 5
    Last Post: 11-30-2001, 12:54 PM