4
votes

I'm writing a mixed mode C++/CLI assembly bridge in order to be able to call into my .NET class library from old C++ application.

In one of my classes in the .NET library one can attach to an event whenever some message needs to be displayed (to console or whatever depending on calling application).

class NetApi
{
    public event EventHandler<MessageEventArgs> MessageReported;
} 

To call this from native C++ application, I defined the following pointer/delegate bridge:

typedef void(*MessageHandler)(const char* msg);
delegate void ManagedMessageHandler([MarshalAs(UnmanagedType::LPStr)] String^ msg);

Omitting from glue for connecting everything (attaching to MessageReported, removing sender from EventHandler, etc...), here is how I create managed delegate from native function pointer:

class NetApiBridge
{
    public:
      void SetMessageHandler(MessageHandler handler)
      {
          wrappedListener = (ManagedMessageHandler^)Marshal::GetDelegateForFunctionPointer((IntPtr)handler, ManagedMessageHandler::typeid);        
      }          

    private:
        msclr::auto_gcroot<NetApi^ > wrappedApi;
        msclr::auto_gcroot<ManagedMessageHandler^ > wrappedListener;

        // In another helper ref class in fact, but here pseudo code to simplify
        void onMessageReported(Object^ sender, MessageEventArgs^ e)
        {
            if (!wrappedListener) { return; }

            wrappedListenter(e->Message); // Send message to native function pointer
        }
}

And I'm almost there when creating dummy C++ test code:

void messageHandler(const char* s)
{
    cout << s; 
}
void main()
{
   NetApiBridge api = new NetApiBridge();
   api->SetMessageHandler(&messageHandler);

   api->Measure();
   delete api;
}

Everything goes fine, events are reported correctly except .... except I receive a PInvokeStackImbalance from Managed Debugging Assistant when leaving the native handler and I clearly don't know why ?

What's wrong with marshaling const char* as UnmanagedType::LPStr here with GetDelegateForFunctionPointer ?

NB: C++ bridge is compiled in x86 if it is important to know here.

1

1 Answers

4
votes
typedef void(*MessageHandler)(const char* msg);
delegate void ManagedMessageHandler([MarshalAs(UnmanagedType::LPStr)] String^ msg);

Your delegate declaration is not compatible with the function pointer declaration in 32-bit code. The default calling convention in native code is almost always __cdecl. The default for delegates is __stdcall. A somewhat quirky choice but inspired because interop was assumed to be useful to make OS calls, Windows and COM use __stdcall.

The mismatch right now causes the delegate stub to pop the arguments off the stack. So does the native code so the stack gets imbalanced by 4 bytes. The MDA is there to help you diagnose this common mishap.

You'll have to help and get them to agree. Either with the delegate declaration:

   using namespace System::Runtime::InteropServices;
   ...
   [UnmanagedFunctionPointer(CallingConvention::Cdecl)]
   delegate void ManagedMessageHandler(String^ msg);

Or the function pointer declaration:

   typedef void (__stdcall * MessageHandler)(const char* msg);