Export (0) Print
Expand All
10 out of 11 rated this helpful - Rate this topic

Asynchronous callbacks from native Win32 code

.NET Compact Framework 1.0
 

Maarten Struys
PTS Software

December 2003

Applies to:
   
Microsoft® .NET Compact Framework 1.0
   Microsoft Visual Studio® .NET 2003
   Microsoft eMbedded Visual C++® 4.0

Summary: In the .NET Compact Framework newsgroup several people have asked questions about passing delegates to native Win32 DLL's to be used as asynchronous callbacks to P/Invoke back into managed code after an unmanaged function wants to report back to its caller. Unfortunately, passing delegates to unmanaged code is not supported by the .NET Compact Framework. As an alternative, the .NET Compact Framework contains the MessageWindow class. This class can be used to pass Windows messages from unmanaged code to managed code. The functionality of MessageWindow is great if you need to pass Windows messages to a managed control. In certain solutions however, using the MessageWindow class is somewhat artificial and not a true replacement for delegates. With a little extra coding effort it is possible to setup an event handler mechanism that exposes itself to the managed application developer as if true delegates exist to call back from unmanaged code into managed code. In this article we provide you with the details of this mechanism. (15 printed pages)


Download sample.

Contents

Introduction
Scenario
Unmanaged code to interface with MessageWindow
Setting up a MessageWindow in managed code
Unmanaged code to interface with managed code
Consuming the asynchronous data inside managed code
Conclusion
About the Author
Additional Resources

Introduction

Suppose we have an application that needs to perform some specific (UI) action upon reception of an asynchronous event that is raised in unmanaged code. We want as much of our functionality to be written in managed code. In an ideal world, we would want to callback from unmanaged code into managed code whenever the asynchronous event is raised. The preferred way of realizing this would be by supplying a delegate method or an event handler to the unmanaged code. Since this approach is not supported in the .NET Compact Framework, we need a work around that simulates this behavior.

Scenario

To explain asynchronous callback mechanisms we create a simple unmanaged DLL containing a thread that wakes up at random. On waking up, the thread passes some information to whomever is listening. We also have a managed application that just displays the information received from the unmanaged DLL. In general, polling is a bad option, especially if we run on a battery powered Windows CE device like a Pocket PC. Batteries are sparse so we want our application to consume as little processor time as possible. Instead we want to make use of a callback mechanism, in which the unmanaged thread informs the managed application that new information is available and that the user interface requires an update. The documented way of realizing this kind of functionality is by making use of the MessageWindow class. As Alex Yakhnin explains in his article "Using the Microsoft .NET Compact Framework MessageWindow Class", the MessageWindow class is very helpful in situations where you need access to managed controls to be able to send Windows messages to them. However, in the scenario we are talking about, both the managed application and the unmanaged DLL are not aware of Windows messages. If the only possible way to pass asynchronous information between the DLL and the application would be to make use of Windows messages, this would almost be a step back in time in my personal opinion. In a managed world we were finally relieved from Windows messages. And yet, to pass information back and forth between unmanaged and managed code in an asynchronous manner, we suddenly need Windows messages again. That is the reason behind the here presented alternative solution.

To concentrate on the mechanism itself the sample code is very simple and straight forward. Inside an unmanaged DLL, written in Microsoft eMbedded Visual C++ a worker thread is created that sleeps for a random amount of time. On waking up, the thread passes the sleeping time to potential listeners. To compare the use of a MessageWindow with a solution making use of an event handler we show code samples for both approaches. For simplicity reasons the unmanaged DLL contains separate worker threads for both the MessageWindow and the event handler solution. The user interface for the sample application is shown in Figure 1.

Figure 1 - The application running in the PocketPC 2003 emulator

Unmanaged code to interface with MessageWindow

The unmanaged DLL is written in Microsoft eMbedded Visual C++ 4.0. The thread feeding the MessageWindow is shown in Listing 1.

///////////////////////////////////////////////////////////////////////////////////////////
//
// RandomWndDataFeeder is used to pass data to the caller, whenever this worker thread has
// data available. The data is passed in a Windows message and send to the "Window" for 
// which we have stored the Window handle during StartWndCommunication.
//
// Any "Window" interested in these messages must capture them in its WndProc.
//
///////////////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI RandomWndDataFeeder (LPVOID lpParameter)
{
    srand( (unsigned)GetTickCount() );

    while (! g_bWndReady) {

        DWORD dwSleepTime = rand() % 10000;

        if (WaitForSingleObject(g_hWndTerminateEvent, dwSleepTime) == WAIT_TIMEOUT) {
            wsprintf(g_pszWndSource,
                     _T("Awake after %d.%3.3d s."), 
                     dwSleepTime / 1000, 
                     dwSleepTime % 1000);
            SendMessage(g_hWndManaged, WM_ASYNCDATA_RECEIVED, 0, (LPARAM)g_pszWndSource);
        } else {
            g_bWndReady = TRUE;
        }
    }
    return 0L;
}

Listing 1 - Worker thread sending messages to a listener

The worker thread simply sleeps a random time, with a maximum of 10 seconds. Whenever WaitForSingleObject returns with WAIT_TIMEOUT, another sleeping period has passed and we send the number of seconds slept to a Window, identified by the g_hWndManaged Window handle. To be able to exit this worker thread g_hWndTerminateEvent might be set.

To be able to start and stop generating Windows messages, two separate functions are available, that should be called from within managed code using P/Invoke. To start message generation, we need to pass a Window Handle to be able to send messages, and we create the worker thread.

///////////////////////////////////////////////////////////////////////////////////////////
//
// StartWndCommunication is used to create all necessary synchronisation objects, allocate
// memory and starts the worker thread that immediately starts collecting data at random.
//
// StartDataRetrieval can be called from unmanaged code or via P/Invoke from managed code.
//
///////////////////////////////////////////////////////////////////////////////////////////
RANDOMDATA_API void StartWndCommunication (HWND hWnd)
{
    g_hWndManaged = hWnd;
    g_bWndReady = FALSE;
    g_hWndTerminateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    g_hWndThread = CreateThread(NULL, 0L, RandomWndDataFeeder, NULL, 0L, NULL);
    g_pszWndSource = new wchar_t[80];
}

Listing 2 - Start generating Windows messages

To stop message generation, we P/Invoke to the following function that sets an event to terminate the worker thread and releases allocated resources.

///////////////////////////////////////////////////////////////////////////////////////////
//
// StopWndCommunication cleans up resources and forces the RandomWndDataFeeder API to fall
// through its WaitForSingleObject to make sure to terminate it. Since we don't know in 
// case of termination if the passed Windows handle is still valid, we don't inform the 
// caller about termination.
//
// StopDataRetrieval can be called from unmanaged code or via P/Invoke from managed code.
//
///////////////////////////////////////////////////////////////////////////////////////////
RANDOMDATA_API void StopWndCommunication (void)
{
    SetEvent(g_hWndTerminateEvent);
    WaitForSingleObject(g_hWndThread, INFINITE);
    CloseHandle(g_hWndTerminateEvent);
    if (g_pszWndSource != NULL) {
        delete g_pszWndSource;
        g_pszWndSource = NULL;
    }
}

Listing 3 - Stop generating Windows messages

Setting up a MessageWindow in managed code

To send messages to a managed application we have to provide the application with a Window Handle. This is exactly what the MessageWindow class is intended for. Simply speaking the MessageWindow class encapsulates a Window Handle. It allows communication between unmanaged code and managed code by means of Windows messages. To use a MessageWindow, we derive a class from MessageWindow and store the main form instance in this class to be able to call methods of it on the reception of Windows messages. In our class derived from MessageWindow we override the WndProc method to be able to act on particular messages. Upon receiving a WM_ASYNCDATA_RECEIVED message we retrieve the message parameters and call a member function of the main form to update the user interface. Since Windows messages are not capable of passing entire strings, we need to pass a pointer to a string containing the relevant information for our application. To retrieve the string from the message's LParam parameter we need a pointer, thus using unsafe code. In our sample we use SendMessage from unmanaged code to pass information to managed code. SendMessage immediately processes the message in managed code (as if it is a function call). This implies it is not necessary to protect the data we want to pass against being overwritten. It would have been possible to make use of PostMessage to pass the message to managed code. In this case, the message would have been placed in a message queue, allowing the unmanaged thread to immediately continue executing. In this case special precautions against overwriting data to be passed from unmanaged to managed code would have been necessary.

/// <summary>
/// The MyMessageWnd class is used to receive Windows messages in an actual Windows procedure.
/// </summary>
public class MyMessageWnd : MessageWindow
{
    private const int WM_USER = 0x400; 
    private const int WM_ASYNCDATA_RECEIVED = WM_USER + 1;

    private Form1 destinationForm;

    /// <summary>
    /// Constructor of MyMessageWnd. The object stores the "owner" of the MessageWindow
    /// class, to be able to obtain a Windows handle for that particular owner.
    /// </summary>
    public MyMessageWnd(Form1 destinationForm)
    {
        this.destinationForm = destinationForm;
    }

    /// <summary>
    /// Windows procedure trapping the messages send from unmanaged code
    /// </summary>
    protected override void WndProc(ref Message msg)
    {
        switch (msg.Msg) 
        {
            case WM_ASYNCDATA_RECEIVED:
                unsafe 
                {
                    string str = new string((char *)msg.LParam.ToPointer());
                    destinationForm.TakeAction(str);
                    break;
                }
        }
        base.WndProc(ref msg);
    }
}

Listing 4 - Inherited MessageWindow to receive Windows messages

In cases where we want to capture messages that are sent by the operating system to managed controls, the MessageWindow provides us with a great mechanism to do so. However, the necessity to deal with Windows messages when we only want to pass some information to a managed application is not ideal. It would be much more natural in a managed world if we would not need knowledge about Windows messages at all.

Unmanaged code to interface with managed code

Similar to the previous code we again have a worker thread in unmanaged code. This time instead of sending a Windows message, the thread sets an event whenever data is available. A helper function in unmanaged code simply waits for the event to be set. When we P/Invoke from managed code to this helper function in unmanaged code, data can be passed from unmanaged to managed code in an asynchronous manner. Listing 5 shows the worker thread in unmanaged code. This worker thread contains additional synchronization functionality to make sure that data to be passed to managed code is not overwritten by the worker thread, since the helper function runs on another (managed) thread.

///////////////////////////////////////////////////////////////////////////////////////////
//
// This is the interface from unmanaged to managed code using a simulated delegate approach
//
///////////////////////////////////////////////////////////////////////////////////////////
//
// RandomDataFeeder is just a thread, waiting random (but acurately) on time-outs.
// Whenever WaitForSingleObject returns with a time-out we set an event that the function
// GetData is waiting for. GetData can be invoked from unmanaged code or via P/Invoke from
// managed code. Note that we don't use polling at all to keep the processor available for
// other duties (and in case of battery operated devices to consume as little power as
// possible).
//
// When the g_hTerminateEvent is set, we are informed that this thread can terminate
// execution. In this case we set one more event for GetData to make sure that nobody
// remains waiting for data that will never come anymore.
//
///////////////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI RandomDataFeeder (LPVOID lpParameter)
{
    srand( (unsigned)GetTickCount() );

    while (! g_bReady) {

        DWORD dwSleepTime = rand() % 10000;    // wakeup at least once every 10 seconds

        if (WaitForSingleObject(g_hTerminateEvent, dwSleepTime) == WAIT_TIMEOUT) {

            // Create a global string, containing the sleeping time.
            // Because the same string is used to pass information to a listener as well,
            // we built a critical section around it.
            EnterCriticalSection(&cs);
            wsprintf(g_pszSource, _T("I slept for %d.%3.3d sec."),
                     dwSleepTime / 1000,
                     dwSleepTime % 1000);
            LeaveCriticalSection(&cs);
            SetEvent(g_hEvent);    // Inform the ‘listener' that data is available.
        } else {
            // If we receive an actual event it is time to terminate this thread.
            g_bReady = TRUE;
            EnterCriticalSection(&cs);
            *g_pszSource = '\0';
            LeaveCriticalSection(&cs);
            // Set g_hEvent for one last time to allow the listener to continue
            SetEvent(g_hEvent);
        }
    }

    return 0L;
}

Listing 5 - Worker thread passing asynchronous data to a listener

The helper function simply waits until new data is available from the worker thread. This function can be called from either managed or unmanaged code. It blocks execution until the g_hEvent is set, indicating that new data is available to be passed to the caller of this function. In effect this means that the thread on which this function is executed will block until new data is available. The data is copied to a buffer, provided by the caller to guarantee that data will not be overwritten whenever the worker thread wakes up again. To make this mechanism full proof, the global data buffer inside the DLL is protected by a critical section so the worker thread can not overwrite the data before it is copied to the destination buffer. The simplest solution would be to P/Invoke this function from a Form in managed code, but since this function is blocking, that would result in a non responsive user interface in managed code as long as we are waiting for data.

//////////////////////////////////////////////////////////////////////////////////////////////
//
// GetData is used to pass data to the caller, whenever the worker thread has data available.
//
// GetData can be called from unmanaged code or via P/Invoke from managed code.
//
//////////////////////////////////////////////////////////////////////////////////////////////
RANDOMDATA_API BOOL GetData (wchar_t *lpDestination, int iDestinationSize)
{
    WaitForSingleObject(g_hEvent, INFINITE);
    if (g_pszSource != NULL) {
        EnterCriticalSection(&cs);
        wcsncpy(lpDestination, g_pszSource, iDestinationSize - 1);
        LeaveCriticalSection(&cs);
        *(lpDestination+iDestinationSize) = '\0';
    } else {
        *lpDestination = '\0';
    }
    return TRUE;
}

Listing 6 - Function that blocks until new, asynchronous data is available

Code to create the worker thread and to terminate the worker thread is omitted in this article, but the full source code of the sample application is available for download.

Consuming the asynchronous data inside managed code

To be able to receive asynchronous data from unmanaged code and to pass it to a control on a Form, we have created a class called MyBroadcaster. This class contains a separate managed worker thread that P/Invokes to the unmanaged function GetData that is shown in listing 6. When new data is available, this thread wakes up in GetData and after copying the data we return to managed code. Inside the managed member function CheckForData event handlers will be called when they are signed up to listen to events, passed by AsyncDataEventArgs. If no event handler is signed up to receive data, we simply immediately return to unmanaged code to wait for more data. Event handlers listening to these events must have the following signature:

public delegate void AsyncDataEventHandler (object source,
                                            UnmanagedCodeEventArgs e);
public event AsyncDataEventHandler OnUnmanagedCodeHandler;

AsyncDataEventArgs is simply derived from EventArgs. It contains a placeholder for the received data as well as a property to access the data. A simplified version of MyBroadcaster is shown in listing 7.

/// <summary>
/// The actual receiver of asynchronous "callbacks" from unmanaged code.
/// This class listens to unmanaged code and broadcasts events upon unmanaged
/// activity to its managed listeners.
/// </summary>
public class MyBroadcaster : IDisposable
{
    private bool disposed = false;
    private bool bDone = false;
    public delegate void AsyncDataEventHandler (object source, AsyncDataEventArgs e);
    public event AsyncDataEventHandler OnAsyncDataHandler;

    /// <summary>
    /// Constructor of MyBroadcaster class. In the constructor we first activate
    /// the unmanaged DLL (in which a thread is collecting data for us at random.
    /// Then a managed worker thread is started that waits for data from the unmanaged
    /// DLL and passes it on to any event handler that is interested in the data.
    /// </summary>
    public MyBroadcaster()
    {
        UnmanagedAPI.StartDataRetrieval();
        Thread workerThread = new Thread(new ThreadStart(CheckForData));
        workerThread.Start();
    }

    /// <summary>
    /// This is the worker thread of the MyBroadcaster class. After construction
    /// of the object this class runs continuously, until MyBroadcaster is disposed.
    /// The worker thread P/Invokes into unmanaged code to GetData. Inside GetData
    /// execution is blocked until new (asynchronous) data is available. If data is
    /// available and if we have a listener (event handler), we pass the data on. Note
    /// that CheckForData remains active (in combination with the unmanaged DLL), even
    /// if there are no listeners for data.
    /// </summary>
    private void CheckForData()
    {
        StringBuilder sb = new StringBuilder(80);

        while (! bDone) 
        {
            UnmanagedAPI.GetData(sb, 80);

            if (sb.Length != 0) 
            {
                AsyncDataEventArgs e = new AsyncDataEventArgs (sb.ToString());
                if (OnAsyncDataHandler != null) 
                {
                    OnAsyncDataHandler(this, e);
                }
            }
        }
    }
}

Listing 7 - MyBroadcaster class

Even though an extra thread is needed, in comparison to the solution using a MessageWindow, the beauty of this approach is that we can now inform our user interface about new available data using an event handler. Using event handlers assures loose coupling between data provider and data consumer. It is even possible to have multiple subscribers to the same event without having to change the code of MyBroadcaster. Especially with the upcoming Generics, it is probably possible to turn this class into a true generic solution, call it a pattern, ready for use with all kinds of asynchronous callbacks from either managed or unmanaged code.

In the form hosting this class, we simply instantiate MyBroadcaster and add or remove event handlers to take action upon reception of data, received from the unmanaged worker thread.

/// <summary>
/// Start capturing asynchronous events from unmanaged code using a delegate.
/// First we check if our worker thread (to be found in the MyBroadcaster class)
/// is running. If not, we instantiate a new object which creates a worker thread
/// in the constructor.
/// Then we add an event handler that is called whenever the worker thread receives
/// an event from unmanaged code.
/// </summary>
public void RunWithPInvoke()
{
    if (mb == null) 
    {
        mb = new MyBroadcaster();
    }
    mb.OnAsyncDataHandler += new MyBroadcaster.AsyncDataEventHandler(OnNewData);
}

/// <summary>
/// To terminate the reception of asynchronous events, simply remove our event handler.
/// The worker thread can continue to run, since other event handlers might still be
/// interested in asynchronous events from unmanaged code.
/// </summary>
public void StopRunningWithPInvoke()
{
    mb.OnAsyncDataHandler -= new MyBroadcaster.AsyncDataEventHandler(OnNewData);
}

Listing 8 - Instantiating MyBroadcaster and adding, removing event handlers

The event handler itself is very simple and straight forward. It receives the data to be shown to the user as part of the AsyncDataEventArgs.

/// <summary>
/// Event handler that is called whenever new data is received from unmanaged code.
/// The date itself is stored in the UnmanagedCodeEventArgs
/// </summary>
private void OnNewData(object source, AsyncDataEventHandler e)
{
    listBox1.Items.Add(e.ReceivedData);
    listBox1.SelectedIndex = listBox1.Items.Count - 1;
}

Listing 9 - The event handler, acting upon received data

Conclusion

Using this approach to callback from unmanaged code into managed code gives us a clean architecture, which closely resembles the use of true delegates. On the downside we need an extra managed thread to set up this mechanism, but since creating a thread is really easy by means of the .NET Compact Framework this is not a big drawback. Not having to deal with Windows messages is a big advantage, since we are not much aware of Windows messages in a managed environment anyway.

About the Author

Maarten Struys works at PTS Software where he is responsible for the real-time and embedded competence centre. Maarten is an experienced Windows (CE) developer, having worked with Windows CE since its introduction. Since 2000, Maarten has worked with managed code in .NET environments. He is also a freelance journalist for the two leading embedded systems development magazines in the Netherlands. He recently opened a website with information about .NET in the embedded world at www.dotnetfordevices.com. Maarten has professionally evaluated the real-time behavior of the .NET Compact Framework, using a mix of managed and unmanaged code in combination with Windows CE .NET 4.1. The whitepaper on this subject was awarded by Microsoft with the WinHEC 2003 Whitepaper Award earlier this year.

Additional Resources

For more information about Windows CE .NET, see the Windows Embedded Web site.

For more information about smart device development, see the MSDN Mobile and Embedded Developer Center.

For more information about Microsoft Visual Studio® .NET, see the Visual Studio Product information site.

Backgrounds, articles, FAQ's en code samples using the .NET Compact Framework can be found on the OpenNETCF website.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.