Invoking .NET Events from Native C++

Introduction

When writing .NET wrapper classes to interface with native C++ code, there are occasions when the native code will need to raise events in the .NET wrapper class. Examples of this are either virtual overrides (for example, OnReceive and OnClose in the CAsyncSocket class) or message mapped functions on CWnd-derived classes (as in OnSize, OnMove, and so forth).

However, .NET only allows events to be raised from inside of the containing class. To do what we want, we must have a native C++ object that invokes the appropriate event via a public member function on the managed wrapper class.

The following diagram shows a virtually overridden function invoking an event on a .NET class through a public member function.

However, this isn't good design. We don't want a class anywhere in the system to be able to raise these events. We want only our native class to be able to do it. Never mind the fact it clutters up the class definition needlessly.

In native C++, the way around such issues was to use 'friends'—one class could call protected and private members on another. But .NET doesn't allow friends. So what is the solution to our problem?

Solution

The solution is to have an intermediate managed class existing in between the native class and the .NET class with the same events as the .NET class.

Thus, in code terms we have:

class CNative
{
protected:
   virtual void OnEvent();
} ;

__gc class NativeInterface;
class CNativeDerived : public CNative
{
public:
   CNativeDerived(NativeInterface *pInterface)
   {
      m_pInterface = pInterface;
   }

protected:
   virtual void OnEvent()
   {
      m_pInterface->RaiseEvent();
   }

private:
   gcroot<NativeInterface *> m_pInterface;
} ;

__gc class NativeInterface
{
public:
   NativeInterface()
   {
      m_pDerivedClass = new CNativeDerived(this);		
   }

   virtual ~NativeInterface()
   {
      delete m_pDerivedClass;
   }

   __event System::EventHandler *Event;

   void RaiseEvent()
   {
      if (Event != NULL)
      {
         Event(this, NULL);
      }
   }

private:
   CNativeDerived *m_pDerivedClass;
} ;

public __gc class ManagedWrapper
{
public:
   ManagedWrapper()
   {
      m_pInterface = new NativeInterface;
      m_pInterface->Event += new EventHandler(this, OnEvent);
   }

   __event System::EventHandler *Event;

protected:
   virtual void OnEvent(System::Object *object, System::EventArgs *args)
   {
      if (Event != NULL)
      {
         Event(this, null);
      }
   }

private:
   NativeInterface *m_pInterface;
} ;

Therefore, if this functionality is put into an assembly in a DLL, only the ManagedWrapper class and its event will be visible.

Example

The example demonstrates one method of inter-thread communications in native code—using windows messages—and provides this functionality to .NET projects. It consists of a C++.NET assembly that contains a CWnd-derived class (CMessengerWindow) that posts a message to itself to signal one thread from another.

There is a managed interface class (MessengerInterface) that has a 'RaiseMessage' member function that is called by the window when it receives its signal message. This in turn raises its event, which is processed by the visible wrapper class (Messenger) causing it to raise its own event to signal clients.

The advantage of using this method is that each thread never explicitly calls methods on the other, removing the need for synchronisation. As an example of use, a C# project is included; it creates an instance of the Messenger on the main thread and starts a seperate thread to send messages to the main thread.

There is a message list class that the seperate thread uses to send communications to the main thread. The thread directly calls the main thread as well as going through the Messenger, and information about the thread ID is displayed in the list box.

As can be seen, when called through the messenger the method is always executed on the main thread. Obviously, this method assumes that the thread on which the Messenger class is created contains a message loop: This method, for instance, won't work in a console application.

Another useful item in the C# project is how to terminate threads cleanly. Calling Abort() on a thread will cause it to exit at the first available opportunity—which means as a developer you don't know on which statement in the thread function the thread will exit. The thread in the example waits on an event to inform it when to exit. This is a safer way of exiting the thread because you can be sure that all the thread functions have completed before the thread exist.

Conclusion

We have seen how to avoid the lack of 'friends' in .NET when processing messages from native classes. We have also seen an example of use demonstrating how to communicate between threads in a thread-safe manner.



About the Author

David McClarnon

He first encountered Windows programming using Visual C++/MFC version 1.5 on Windows 3.11 a very long time ago. He is now a contract developer specialising in .NET/native interop with p/invoke.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

  • Download the Information Governance Survey Benchmark Report to gain insights that can help you further establish business value in your Records and Information Management (RIM) program and across your entire organization. Discover how your peers in the industry are dealing with this evolving information lifecycle management environment and uncover key insights such as: 87% of organizations surveyed have a RIM program in place 8% measure compliance 64% cannot get employees to "let go" of information for …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds