Bridging SigC++ to .NET via P/Invoke

If you’re a seasoned C++ developer, chances are you’ve come into contact with a signals/slots library at least once during your work, maybe you’re even using one right now.

If you haven’t heard of this before, let me give you a short introduction: a signals/slots library is nothing more than a fancy callback system: A class, let’s think of a fictional Button class for now, can provide signals like Pushed signal. A signal is just a list of callbacks that can be invoked when the signal is triggered.

Now any party interested in getting notified when the button is pushed can subscribe itself to the Pushed signal and will be notified by means of a callback. Typically you can bind any plain function, class method or functor to a signal.

Here’s a short example of what it might look like:

class Button {

  public: signal<void> OnPushed;

  public: void Push() {
    OnPushed(); // notify all subscribers of the OnPushed signal
  }

};

int buttonPushed() {
  cout << "Button Pushed" << endl;
}

int main() {
  Button myButton;

  myButton.connect(&buttonPushed);

  myButton.Push();
}

The same concept is known in the .NET world as events. Recently, I had to bridge SigC++ to .NET events using only P/Invoke (C++/CLI was not an option because the application had to work on other platforms than .NET+Windows). This article explains what I did to elegantly bridge a sigc::signal to a .NET event.

Our Example Class

For the sake of simplicity, let's assume we had a static class in C++ that triggered some sigc::signal whenever we get an email:

/// <summary>Monitors a mailbox for incoming emails</summary>
class EmailMonitor {

  /// <summary>Triggered whenever an email is received</summary>
  /// <remarks>
  ///   The two arguments are 1) sender and 2) email subject
  /// </remarks>
  public: static sigc::signal<
    void, const std::wstring &, const std::wstring &
  > OnEmailReceived;

  /// <summary>Begins monitoring the mailbox</summary>
  public: static void Start();

  /// <summary>Stops monitoring the mailbox</summary>
  public: static void Stop();

};

The first thing to do is of course to design a class with an equivalent interface that integrates nicely with the .NET world. For example, for the above C++ class, we might decide to create the following .NET classes:

/// <summary>Carries the arguments for an EmailReceived event</summary>
public class EmailEventArgs : EventArgs {
  /// <summary>Initializes a new email event argument container</summary>
  /// <param name="sender">Address from which the email originates</param>
  /// <param name="subject">The email's subject line</param>
  public EmailEventArgs(string sender, string subject) {
    this.Sender = sender;
    this.Subject = subject;
  }
  /// <summary>Email address the email originates from</summary>
  public string Sender { get; } // C# 3.0 auto-accessor
  /// <summary>Subject line of the email</summary>
  public string Subject { get; } // C# 3.0 auto-accessor
}

/// <summary>Monitors a mailbox for incoming emails</summary>
public static class EmailMonitor {

  /// <summary>Triggered whenever an email is received</summary>
  public static event EventHandler<EmailEventArgs> EmailReceived;

  /// <summary>Begins monitoring the mailbox</summary>
  public static void Start() {
    // [...]
  }

  /// <summary>Stops monitoring the mailbox</summary>
  public static void Stop() {
    // [...]
  }

}

This C# wrapper follows the .NET design guidelines nicely and any .NET developer will understand how to use it on first sight. Job accomplished.

Now to the problem at hand: How can we bridge the native code sigc::signal to such an event?

The straightforward option would be to let our C# wrapper pass a callback to the C++ code and let C++ subscribe that callback to the sigc::signal right when the program starts. We could then just put some code together in C# that fired the event whenever the callback gets invoked.

This would, however, cause the P/Invoke marshaling process to run whenever the sigc::signal triggers, event when the event has no subscribers. Not dramatic in our example, but if you have a larger system with lots of events being fired in high volume, it can become quite wasteful on CPU cycles. Now there's another option you can choose:

Creating a Manual Event

A little known feature of .NET is that you can manage the list of subscribers for an event yourself. This is done by extending the event declaration to this:

/// <summary>Monitors a mailbox for incoming emails</summary>
public static class EmailMonitor {

  /// <summary>Triggered whenever an email is received</summary>
  public static event EventHandler<EmailEventArgs> EmailReceived {
    add {
      // [...]
    }
    remove {
      // [...]
    }
  }
  
  // [...]

}

This allows us, you got it, to simply set up our managed code callback for the sigc::signal when the first subscriber adds itself to the event and to disabled our managed code callback again when the last subscriber removes itself from the event.

The C++ Side

First, we need a function signature for our managed callback. This function signature has to match a plain C function, so std::wstring becomes a const wchar_t *, bool becomes int and so on:

/// <summary>Signature of the registerable EmailReceived callback</summary>
typedef void __stdcall EmailReceivedCallback(
  const wchar_t *, const wchar_t *
);

Next, because the function signature differs from the event, we need a custom functor for the sigc::signal that transforms the std::wstring arguments into const wchar_t * arguments.

/// <summary>Functor that invokes an EmailReceived callback</summary>
class EmailReceivedCallbackFunctor : public sigc::functor_base {

  /// <summary>The return value of this functor, required for sigc++</summary>
  public: typedef void result_type;

  /// <summary>Constructs a email received callback functor</summary>
  /// <param name="callback">Callback to be invoked by the functor</param>
  public: explicit EmailReceivedCallbackFunctor(EmailReceivedCallback *callback) :
    callback(callback) {}

  /// <summary>Executes the wrapped callback</summary>
  /// <param name="sender">Address the email originates from</param>
  /// <param name="subject">Subject line of the email message</param>
  public: void operator()(const std::wstring &sender, const std::wstring &subject) {
    this->callback(sender.c_str(), subject.c_str());
  }

  /// <summary>Callback this functor is delegating to</summary>  
  private: EmailReceivedCallback *callback;

};

This functor's operator () is compatible to the sigc::signal, so it can be directly registered to the EmailReceived signal. It then performs the callback into managed code with const wchar_t *, a string format well understood by .NET.

One last thing remains on the C++ side, a P/Invoke exported function that .NET can use to subscribe and unsubscribe from the signal:

extern "C" void __stdcall EmailMonitor_SetEmailReceivedCallback(
  EmailReceivedCallback *callback
) {
  EmailMonitor::EmailReceived.clear();
  if(callback)
    EmailMonitor::EmailReceived.connect(EmailReceivedFunctor(callback));
}

As you can see, I've combined subscription and unsubscription into one function. And I've taken the shortcut of just calling clear() on the sigc::signal instead of remembering the connection returned by SigC++. Feel free to store the connection and to unsubscribe properly if you may have other parties subscribing to the event (which you would unwillingly kill off from the event's subscription list with the clear() call).

The C# Side

In C# we need to declare a delegate that is compatible with the function pointer type we declared in C++:

/// <summary>Signature for the EmailReceivedMessage callback method</summary>
/// <param name="sender">Address the email originates from</param>
/// <param name="subject">Subject line of the email message</param>
private delegate void EmailReceivedCallback(IntPtr sender, IntPtr subject);

Then we can set up the P/Invoke import for the EmailMonitor_SetEmailReceivedCallback() method. .NET will understand that the delegate means it is supposed to pass a function pointer to the unmanaged function.

[DllImport("MyNative.dll")]
private static extern void EmailMonitor_SetEmailReceivedCallback(
  EmailReceivedCallback callback
);

Now there's one very big trap .NET has set up for its unsuspecting victims: the garbage collector can not track unmanaged references to managed objects and it has no way to know that the EmailMonitor_SetEmailReceivedCallback() method will remember the function pointer beyond the scope of the function call. Thus, we need to make absolutely sure that the delegate is kept alive as long as it is being held by the unmanaged code side.

There are only few hidden references to this fact in the MSDN documentation, at least from what I could find. One mention is in the documentation of the Marshal.GetFunctionPointerForDelegate() method, which is what .NET will use behind the scenes to transform the delegate to a function pointer:

Note that you must manually keep the delegate from being collected by the garbage collector (GC) from managed code. The GC does not track reference to unmanaged code.

Thus, when we now proceed to implement our event, we will keep a reference to the delegate in a static variable as long as our wrapper is bound to the unmanaged signal:

/// Delegate currently subscribed to the unmanaged callback
/// 
///   Important: A reference to this delegate needs to be kept because unmanaged
///   code stores the function pointer. The GC cannot track references in
///   unmanaged code, thus the delegate would otherwise become a candidate for
///   garbage collection, causing a crash when C++ tries to invoke the callback.
/// 
private static EmailReceivedCallback emailReceivedCallbackDelegate;

/// List of subscribers to the EmailReceived event
private static List> emailReceivedSubscribers =
  new List>();

All that's left is the implementation of the add and remove blocks in the managed event. As explained above, add has to set up a callback when the first party subscribes to the event and remove has to unset the callback after the last party unsubscribed:

/// <summary>Triggered whenever an email is received</summary>
public static event EventHandler<EmailEventArgs> EmailReceived
  add {
    // If this is the first subscriber, set up our unmanaged code callback
    if(emailReceivedSubscribers.Count == 0) {
      emailReceivedCallbackDelegate = new EmailReceivedCallback(OnEmailReceived);
      EmailMonitor_SetEmailReceivedCallback(emailReceivedCallbackDelegate);
    }

    // Add the event handler to our list of subscribers
    emailReceivedSubscribers.Add(value);
  }
  remove {
    // Do nothing if the subscriber doesn't exist. This is conformant with
    // the behavior of the default .NET event implementation
    if(!emailReceivedSubscribers.Contains(value))
      return;

    emailReceivedSubscribers.Remove(value);

    // If we just removed the final subscriber, detach our callback again
    if(emailReceivedSubscribers.Count == 0) {
      EmailMonitor_SetEmailReceivedCallback(null);
      emailReceivedCallbackDelegate = null;
    }
  }
} // event EmailReceived

There it is, a SigC++ signal being marshaled into a .NET event using only P/Invoke!

Some things, like proper error handling, have been omitted from the code to better focus on the topic at hand. In real production code, you would of course want to either make use of SetLastError() or write your own error marshaling facility.

I chose SigC++ for this article because it's my favorite and recommended signals/slots library. You should have no problems applying the code to Boost.Signals as its design is very similar to SigC++. Other signals/slots library are likely to work on the same basis but may require minor code changes.

Leave a Reply

Your email address will not be published. Required fields are marked *

Please copy the string 5vivMO to the field below:

This site uses Akismet to reduce spam. Learn how your comment data is processed.