September 5 2010




 
Search Blog Entries:



What is this?

Article Details
 
Asynchronous callbacks from native Win32 code

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 whoever 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 PocketPC. 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 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. Figure 1 shows the user interface for the sample application.

The application running inside the PocketPC 2003 emulator
Figure 1: The application running inside the PocketPC 2003 emulator

Unmanaged code to interface with MessageWindow

The unmanaged DLL is written in 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.

////////////////////////////////////////////////////////////////
// The MyMessageWnd class is used to receive Windows messages
// in an actual Windows procedure.
////////////////////////////////////////////////////////////////
public class MyMessageWnd : MessageWindow
{
  private const int WM_USER = 0x400;
  private const int WM_ASYNCDATA_RECEIVED = WM_USER + 1;
  private Form1 destinationForm;
  // Constructor of MyMessageWnd. The object stores the 
  // "owner" of the MessageWindow class, to be able to obtain
  // a Windows handle for that particular owner.
  public MyMessageWnd(Form1 destinationForm)
  {
    this.destinationForm = destinationForm;
  }
  // Windows procedure trapping the messages send from 
  // unmanaged code
  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) {
    // wakeup at least once every 10 seconds
    DWORD dwSleepTime = rand() % 10000;   
    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);
      // Inform the ‘listener’ that data is available. 
      SetEvent(g_hEvent);
    } 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.

////////////////////////////////////////////////////////////////
// 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.
////////////////////////////////////////////////////////////////
public class MyBroadcaster : IDisposable
{
  private bool disposed = false;
  private bool bDone = false;
  public delegate void AsyncDataEventHandler (object source,
                                              AsyncDataEventArgs e);
  public event AsyncDataEventHandler OnAsyncDataHandler;
  // 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.
  public MyBroadcaster()
  {
    UnmanagedAPI.StartDataRetrieval();
    Thread workerThread = new Thread(new ThreadStart(CheckForData));
    workerThread.Start();
  }
  // 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.
  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.


////////////////////////////////////////////////////////////////
// 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.
////////////////////////////////////////////////////////////////
public void RunWithPInvoke()
{
  if (mb == null)
  {
    mb = new MyBroadcaster();
  }
  mb.OnAsyncDataHandler +=
      new MyBroadcaster.AsyncDataEventHandler(OnNewData);
}
////////////////////////////////////////////////////////////////
// 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.
////////////////////////////////////////////////////////////////
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.

////////////////////////////////////////////////////////////////
// Event handler that is called whenever new data is received
// from unmanaged code. The date itself is stored in the
// UnmanagedCodeEventArgs
////////////////////////////////////////////////////////////////
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 CF 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.

Maarten Struys

Additional Resources

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

For more information about smart device development, see the Official Microsoft Smart Devices Developer Community.

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

Backgrounds, articles, FAQ’s and code samples using the .NET Compact Framework can be found on the OpenNETCF Web site.

 
Back








SpiralFX Technology Solutions
www.spiralfx.com


Do you want to learn developing a full blown Windows Mobile Application? This article and accompanying multimedia content will help you to do so. It will be extended over the upcoming weeks / months, so check back regularly.
 
Read Full Article