C++/CLI: Trigger events from C++ native code and handle them in Managed code, Part I

Imagine we want to trigger an event from a native class which has a C++/CLI wrapper as an interface to managed code (C#, VB.NET). And let's deal with the general case in which we desire to pass extra information along with the event, and said information is not inside a CLR basic type, that is one which both native and managed C++ understand (int, float, char, and so on).

  1. In the native class, declare, just for convenience, a pointer to function type:
    typedef <nativeReturnType> ( __stdcall * PFOnEventCallback )( <nativeParameters> );
  2. Still in the native class, declare a field of the type declared in 1):
    PFOnEventCallback m_fireEvent;
  3. In the place in the native class where we want to trigger the event:
    if( m_fireEvent )
       m_fireEvent( <nativeParameters> );
    (This suggests you to initialize m_fireEvent to 0, or NULL if you prefer)
  4. Now we need a setter for m_fireEvent so that the C++/CLI wrapper can register the callback for the native event:
    void CNativeClass::registerOnEventCallback( PFOnEventCallback newVal ){
       m_fireEvent = newVal;
    }
  5. Moving to the C++/CLI wrapper, we need to define a delegate analogous to the native pointer to function, and an equivalent managed delegate. Both will be fields in the wrapper:
    delegate <nativeReturnType> DNativeCallback( <nativeParameters> );
    delegate <managedReturnType> DManagedCallback( <managedParameters> );
    // ...Alongside the other fields:
    DNativeCallback ^ m_hDNativeCallback;
    static DManagedCallback ^ m_hDManagedCallback;
    At item 7) we'll see why the managed callback needs to be static. EDIT: And in some post later, we'll see there is a way to make it non static, that is, a member.
  6. Still in the C++/CLI wrapper, declare the managed event which the pure managed code using the Wrapper will subscribe to:
    event DManagedCallback ^ Event{
       void add( DManagedCallback ^p ){
          m_hDManagedCallback += p;                 
       }
    
       void remove( DManagedCallback ^p ){
          m_hDManagedCallback -= p;                 
       }
    }
  7. Next, we must define the C++/CLI method which connects the native and managed callbacks. It must be static because we are using a pointer to function, which is static. As I'll show you in a future post, this can be avoided, but static works.
    static <managedReturnType> fireEvent( <nativeParameters> ){
       if ( m_hDManagedCallback != nullptr )
          return m_hDManagedCallback( <nativeParametersMarshalledIntoManagedParameters> );
       }
    Of course, marshalling need not be done right in the call. You can do it above, as long as you pass managedParameters to m_hDManagedCallback.
  8. In the C++/CI wrapper, initialize and connect the native and managed callbacks (m_pNative is a pointer to the native wrapped object):
    m_hDNativeCallback = gcnew DNativeCallback( &fireEvent );
    m_pNativeInstance->registerOnEventCallback( PFOnEventCallback( Marshal::GetFunctionPointerForDelegate( m_hDNativeCallback ).ToPointer() ) );
    Marshal::GetFunctionPointerForDelegate() is a .NET function.
  9. Don't forget to undo the registration in the wrapper's destructor and finalizer, that is, call:
    m_pNativeInstance->registeronEventoCallback( 0 ); //Or NULL, your call
  10. Finally, the wrapper's event can be captured in managed code. For example, in VB.NET:
    AddHandler CLIWrapper.Event, AddressOf SomeSubWithTheSameSignatureAsDManagedCallback
    'When done using it'
    RemoveHandler CLIWrapper.Event, AddressOf SomeSubWithTheSameSignatureAsDManagedCallback

Complicated, isn't it? It's what works... If you know a better alternative, please drop me a line. Continued in Part II

Comments

  1. Hi Dario,

    i am implementing your code but am getting compilation errors atstep 8-1 and step 8-2.

    Thanks

    ReplyDelete
  2. Sory for the uber-late reply, but just for completeness sake... That was probably because gcnew is a C++/CLI exclusive keyword. Check that your C++ project is configured to build a mixed mode assembly (/clr setting in its properties).

    ReplyDelete
  3. Just great article.
    It is sometimes hard to get specialized code , easily explained.

    ReplyDelete

Post a Comment

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x