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.