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

  • Not long ago, security was viewed as one of the biggest obstacles to widespread adoption of cloud-based deployments for enterprise software solutions. However, the combination of advancing technology and an increasing variety of threats that companies must guard against is rapidly turning the tide. Cloud vendors typically offer a much higher level of data center and virtual system security than most organizations can or will build out on their own. Read this white paper to learn the five ways that cloud …

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds