BEGIN_MESSAGE_MAP caused C++ Builder 10.1 to crash to desktop

1.3k views Asked by At

I am writing a VCL componenet, TGIcon, to mimic the Icons in windows desktop, it has been working fine until I decided to add MouseEnter and MouseLeave events to the component. I followed guides from: Embarcadero Community

and here is my code (header):

class PACKAGE TGIcon : public TGraphicControl
{
    private:
        AnsiString FCaption;
        TPngImage *FIcon, *FDIcon;
        TFont *FFont;
        TNotifyEvent FOnMouseEnter;
        TNotifyEvent FOnMouseLeave;

        void __fastcall CMMouseEnter(TMessage &Message);
        void __fastcall CMMouseLeave(TMessage &Message);

        BEGIN_MESSAGE_MAP
            MESSAGE_HANDLER(CM_MOUSEENTER, TMessage, CMMouseEnter)
            MESSAGE_HANDLER(CM_MOUSELEAVE, TMessage, CMMouseLeave)
        END_MESSAGE_MAP(TGIcon)

    protected:
        virtual void __fastcall Paint();
        void __fastcall SetCaption(AnsiString value);
        void __fastcall SetIcon(TPngImage *value);
        void __fastcall SetFont(TFont *value);

    public:
        __fastcall TGIcon(TComponent* Owner);
        __fastcall ~TGIcon();
        void __fastcall MakeGray(void);

    __published:
        __property AnsiString Caption = {read=FCaption, write=SetCaption, nodefault};
        __property TPngImage  *Icon   = {read=FIcon, write=SetIcon};
        __property TFont      *Font   = {read=FFont, write=SetFont};
        __property Parent;
        __property Enabled;
        __property OnClick;

        __property TNotifyEvent OnMouseEnter = {read=FOnMouseEnter, write=FOnMouseEnter};
        __property TNotifyEvent OnMouseLeave = {read=FOnMouseLeave, write=FOnMouseLeave};
};

Whenever I try to place the component on a Form, the IDE (C++ Builder Starter) would crash to desktop. I have traced the source of problem to be the "BEGIN_MESSAGE_MAP...END_MESSAGE_MAP" part. If I comment out that part, the component works fine.

I used to have the same component working in C++Builder XE5 (Professional) but since that's owned by a company I no longer work with, I don't have the binary of the component, so I have to re-write it here. As far as I can remember, what I did is exactly the same as the one I wrote in XE5, that one works but this one would crash the IDE, no error message, no Access Violation, just plain CTD.

Can someone please help, is there anything I need to do to make this work in C++ Builder 10.1 (Berlin) Starter Edition? Is this a bug of C++Builder or is this what cannot be done in Starter Edition, that it only can be done in the 'paid' editions?? Or is this method already obsolete? If so please show me how the "modernized" C++ Builder do it.

Thanks in advance.

1

There are 1 answers

1
Remy Lebeau On BEST ANSWER

Your MESSAGE_MAP is terminated incorrectly. In the END_MESSAGE_MAP macro, you must specify the base class that your component derives from (TGraphicControl).

A MESSAGE_MAP is just a fancy way to override the virtual Dispatch() method, where:

  • BEGIN_MESSAGE_MAP declares and opens the overridden method, and opens a switch statement
  • MESSAGE_HANDLER (use VCL_MESSAGE_HANDLER instead if your project uses ATL) declares case statements for the switch
  • END_MESSAGE_MAP calls the Dispatch() method of the specified class for unhandled messages, closes the switch, and closes the overridden method.

Here are the declarations from sysmac.h:

#define BEGIN_MESSAGE_MAP   virtual void __fastcall Dispatch(void *Message) \
        {                                           \
          switch  (((PMessage)Message)->Msg)        \
          {

#define VCL_MESSAGE_HANDLER(msg,type,meth)          \
          case    msg:                              \
            meth(*((type *)Message));               \
            break;

// NOTE: ATL defines a MESSAGE_HANDLER macro which conflicts with VCL's macro. The
//       VCL macro has been renamed to VCL_MESSAGE_HANDLER. If you are not using ATL,
//       MESSAGE_HANDLER is defined as in previous versions of BCB.
//
#if !defined(USING_ATL) && !defined(USING_ATLVCL) && !defined(INC_ATL_HEADERS)
#define MESSAGE_HANDLER  VCL_MESSAGE_HANDLER
#endif // ATL_COMPAT

#define END_MESSAGE_MAP(base)           default:    \
                        base::Dispatch(Message);    \
                        break;                      \
          }                                         \
        }

So, this code:

BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(CM_MOUSEENTER, TMessage, CMMouseEnter)
    MESSAGE_HANDLER(CM_MOUSELEAVE, TMessage, CMMouseLeave)
END_MESSAGE_MAP(TGIcon) // <-- error!

Gets translated by the preprocessor to this code, which is what the compiler sees:

virtual void __fastcall Dispatch(void *Message)
{
    switch (((PMessage)Message)->Msg)
    {
        case CM_MOUSEENTER:
            CMMouseEnter(*((TMessage *)Message));
            break;

        case CM_MOUSELEAVE:
            CMMouseLeave(*((TMessage *)Message));
            break;

        default:
            TGIcon::Dispatch(Message); // <-- recursive loop!
            break;
    }
}

As you can see, since you are specifying your own component class (TGIcon) instead of the base class (TGraphicControl) in END_MESSAGE_MAP, you are creating an endless recursion loop when the component receives an unhandled message. TGIcon::Dispatch() is calling TGIcon::Dispatch() again. It needs to call TGraphicControl::Dispatch() instead (so do your CMMouseEnter() and CMMouseLeave() methods):

BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(CM_MOUSEENTER, TMessage, CMMouseEnter)
    MESSAGE_HANDLER(CM_MOUSELEAVE, TMessage, CMMouseLeave)
END_MESSAGE_MAP(TGraphicControl) // <-- fixed!