//
you're reading...
Delegates

Delegates As Callbacks Part 2

1. Introduction.

1.1 This blog is a continuation of an earlier blog Delegates As Callbacks Part 1.

1.2 In this part 2, I shall demonstrate using managed methods as callbacks that can be invoked from unmanaged code.

2. Sample Code.

2.1 Listed below is a sample unmanaged code written in C++ :

typedef void (__stdcall *PCallBack)(LPCTSTR s);

LPCTSTR g_stringArray[] =
{
  "Hello",
  "World",
  "Foo",
  "Bar"
};

void __stdcall PerformActionWithCallBack(PCallBack pCallBack)
{
  if (pCallBack)
  {
    size_t stSizeOfArray = sizeof(g_stringArray)/sizeof(LPCTSTR);

    for (size_t i = 0; i < stSizeOfArray; i++)
    {
      pCallBack(g_stringArray[i]);
    }
  }
}

In the above C++ code, a function pointer type “PCallBack” is declared. The managed side callback function must be modelled based on this function pointer.

A global array of C-style string pointers is declared. We shall be using this string array for parameter passing to the managed callback.

Finally an API PerformActionWithCallBack() is defined. This API is imported into and is called in managed code. This API in turn will invoke the callback function 4 times (according to the number of string pointers in the global string pointers array). Each call to PerformActionWithCallBack() is done with one of the strings in the global array as parameter.

2.2 Over at the managed side, the callback function is declared as a delegate as usual :

public delegate void CallBack([In][MarshalAs(UnmanagedType.LPStr)] string strParam);

2.3 The API is declared as usual :

[DllImport("TestDLL.dll", CallingConvention=CallingConvention.StdCall)]
public static extern void PerformActionWithCallBack(IntPtr pCallBack);

2.4 Recall in part 1 that when an unmanaged function pointer is returned, it is transformed into a managed delegate via the Marshal.GetDelegateForFunctionPointer(). This time, with a managed function being passed as a function pointer, we use the conjugate Marshal.GetFunctionPointerForDelegate().

2.5 To achieve this, we must first instantiate a delegate object that represents the managed callback function. Let’s say we want the following C# function to be the callback :

static void CallBackFunction(string strParam)
{
  Console.WriteLine("{0:S}", strParam);
}

To create a delegate object that represents this function, we perform the following declaration :

CallBack callback_delegate = new CallBack(CallBackFunction);

where “CallBack” is the delegate declared in point 2.2 above.

2.6 We can then use the Marshal.GetFunctionPointerForDelegate() method to obtain an IntPtr that serves as a pointer to CallBackFunction() that can be used in unmanaged code :

IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(callback_delegate);

2.7 The PerformActionWithCallBack() API can then be called :

PerformActionWithCallBack(intptr_delegate);

2.8 Listed below is a full C# code listing that demonstrates managed callback invokation :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CSConsoleClient01
{
  class Program
  {
    // Declare a delegate that represents a managed callback function.
    public delegate void CallBack([In][MarshalAs(UnmanagedType.LPStr)] string strParam);
    // Declare the API that will be called in managed code.
    // The API will in turn call the managed callback before it returns.
    [DllImport("TestDLL.dll", CallingConvention=CallingConvention.StdCall)]
    public static extern void PerformActionWithCallBack(IntPtr pCallBack);

    static void DemonstrateCallBack()
    {
      // Create an instance of a CallBack delegate for CallBackFunction().
      CallBack callback_delegate = new CallBack(CallBackFunction);
      // Convert callback_delegate into a function pointer that can be
      // used in unmanaged code.
      IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(callback_delegate);
      // Call the API passing along the function pointer.
      PerformActionWithCallBack(intptr_delegate);
    }

    // This managed function will be called from unmanaged code.
    static void CallBackFunction([In][MarshalAs(UnmanagedType.LPStr)] string strParam)
    {
      Console.WriteLine("{0:S}", strParam);
    }

    static void Main(string[] args)
    {
      DemonstrateCallBack();
    }
  }
}

3. Beware of Garbage Collection.

3.1 Section 2 above provided a basic demonstration of a managed callback. One serious problem with managed callbacks is related to the fact that a delegate is a managed object. As such, it is subject to garbage collection just like any other managed object.

3.2 The following C++ and C# code listings will demonstrate this :

C++

PCallBack g_pCallBack = NULL;

void __stdcall SetCallBack(PCallBack pCallBack)
{
  g_pCallBack = pCallBack;
}

void __stdcall PerformAction()
{
  if (g_pCallBack)
  {
    size_t stSizeOfArray = sizeof(g_stringArray)/sizeof(LPCTSTR);

    for (size_t i = 0; i < stSizeOfArray; i++)
    {
      g_pCallBack(g_stringArray[i]);
    }
  }
}

The C++ code above uses the same “PCallBack” function pointer declaration as the code in section 2. The SetCallBack() and PerformAction() APIs basically splits up the PerformActionWithCallBack() API that we saw earlier into 2 separate functions with SetCallBack() keeping the passed in function pointer in a global variable and PerformAction() calling the callback in a fashion similar to the one seen in PerformActionWithCallBack().

C#

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetCallBack(IntPtr pCallBack);

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void PerformAction();

static void DemonstrateCallBack_GarbageCollected()
{
  IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(new CallBack(CallBackFunction));
  SetCallBack(intptr_delegate);
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
  PerformAction();
}

Notice that I have deliberately included Garbage Collector activation calls (GC.Collect()). When you run the DemonstrateCallBack_GarbageCollected() function, you will get an error message when the PerformAction() API is called. The error message will read partly as follows :

“…A callback was made on a garbage collected delegate of type ‘CSConsoleClient01!CSConsoleClient01.Program+CallBack::Invoke’. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called…”

The specific location where the error will appear is at the code location where g_pCallBack() is invoked (highlighted in bold below) :

void __stdcall PerformAction()
{
  if (g_pCallBack)
  {
    size_t stSizeOfArray = sizeof(g_stringArray)/sizeof(LPCTSTR); 

    for (size_t i = 0; i < stSizeOfArray; i++)
    {
      g_pCallBack(g_stringArray[i]);
    }
  }
}

This is because by the time we reach this C++ code, the delegate, first created in the following code :

IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(new CallBack(CallBackFunction));

is garbage collected.

3.3 To overcome this problem, always make sure that the callback delegate is kept alive when the unmanaged API uses its function pointer.

3.4 The following is an example function (modification of DemonstrateCallBack_GarbageCollected()) that will overcome the Garbage Collection problem :

static void DemonstrateCallBack_GC_Protected()
{
  // Declare an instance of the CallBack delegate.
  CallBack callback_delegate = new CallBack(CallBackFunction);
  // Use GCHandle to hold the delegate object in memory.
  GCHandle gch = GCHandle.Alloc(callback_delegate);
  // Obtain an unmanaged function pointer for the delegate as usual.
  IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(callback_delegate);
  // Set the function pointer in unmanaged code.
  SetCallBack(intptr_delegate);
  // Perform Garbage Collection.
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
  // Invoke the managed callback from unmanaged code.
  // Everything will be OK.
  PerformAction();
  // Free the GCHandle.
  gch.Free();
}

This code uses the GCHandle class to hold the delegate object “callback_delegate” locked in memory. Note that it need not be fixed at any specific memory location. Hence the version of GCHandle.Alloc() that takes a GCHandleType parameter :

GCHandle gch = GCHandle.Alloc(callback_delegate, GCHandleType.Pinned);

need not be used.

3.5 Hence the earlier DemonstrateCallBack() function may be enhanced with the use of GCHandle as follows :

static void DemonstrateCallBack_With_GCHandle()
{
  // Declare an instance of the CallBack delegate.
  CallBack callback_delegate = new CallBack(CallBackFunction);
  // Use GCHandle to hold the delegate object in memory.
  GCHandle gch = GCHandle.Alloc(callback_delegate);
  // Obtain an unmanaged function pointer for the delegate as usual.
  IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(callback_delegate);
  // Invoke the managed callback from unmanaged code.
  PerformActionWithCallBack(intptr_delegate);
  // Free the GCHandle.
  gch.Free();
}
About these ads

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

3 thoughts on “Delegates As Callbacks Part 2

  1. Amazing article, thank you very much !!!

    Posted by Eric | May 9, 2013, 10:27 pm

Trackbacks/Pingbacks

  1. Pingback: Delegates As Callbacks Part 2 | 福利网 - August 29, 2013

  2. Pingback: New Windows Tray / Notification Manager is here! | hianz - September 3, 2013

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: