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).
- In the native class, declare, just for convenience, a pointer to function type:
typedef <nativeReturnType> ( __stdcall * PFOnEventCallback )( <nativeParameters> );
- Still in the native class, declare a field of the type declared in 1):
PFOnEventCallback m_fireEvent;
- In the place in the native class where we want to trigger the event:
(This suggests you to initialize m_fireEvent to 0, or NULL if you prefer)if( m_fireEvent ) m_fireEvent( <nativeParameters> );
- 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; }
- 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:
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.delegate <nativeReturnType> DNativeCallback( <nativeParameters> ); delegate <managedReturnType> DManagedCallback( <managedParameters> ); // ...Alongside the other fields: DNativeCallback ^ m_hDNativeCallback; static DManagedCallback ^ m_hDManagedCallback;
- 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; } }
- 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.
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.static <managedReturnType> fireEvent( <nativeParameters> ){ if ( m_hDManagedCallback != nullptr ) return m_hDManagedCallback( <nativeParametersMarshalledIntoManagedParameters> ); }
- 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. - 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
- 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
Hi Dario,
ReplyDeletei am implementing your code but am getting compilation errors atstep 8-1 and step 8-2.
Thanks
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).
ReplyDeleteJust great article.
ReplyDeleteIt is sometimes hard to get specialized code , easily explained.