Code Repo    |     RSS
MD's Technical Sharing



Wednesday, May 13, 2009

P/Invoking C++ callback functions crashes when .NET application is not in focus

A C++ DLL exports one function having its only parameter as a pointer to another function:


#define AFX_EXT_CLASS __declspec(dllexport)
typedef void (CALLBACK* CALLBACKFUNC)(INT param1);
AFX_EXT_CLASS INT
TestCallback(CALLBACKFUNC lpCallback);

INT
TestCallback(CALLBACKFUNC lpCallback)
{
MessageBox(GetActiveWindow(), L"Press OK to make the callback", L"Testing", MB_OK);
lpCallback(1);
}


From .NET code, P/invoke the function TestCallback()


Public Delegate Sub MyCallback(ByVal param1 As Integer)

<DllImport(DllName)> _
Public Function TestCallback(ByVal lpCallback as MyCallback) As Integer
End Function

Public Function MyCallbackFunc(ByVal param1 As Integer) As Integer
MessageBox.Show("Callback works. Param = " + param1.ToString)
End sub

TestCallback(
AddressOf MyCallbackFunc)


It seems to work fine for a while. However, if the C++ function TestCallback calls lpCallback() when the .NET application does not have focus, or after it's been running for sometime, the .NET application will crash.


The reason is probably that the callback delegate has been garbage collected and the function pointer that was passed to unmanaged code has been invalidated. You have to ensure that the delegate is alive for as long as the function pointer is used, by holding a reference to it. This means you can't create the delegate inline in the same way it's done in the above code sample:


TestCallback(AddressOf MyCallback) 'VB.NET

TestCallback(new MyCallback(MyCallback) //C#

The correct declaration would be:

Dim MyCallbackEvent As MyCallback = AddressOf CallbackHandler
TestCallback(MyCallbackEvent)


It is advised to keep these delegate as global variables, otherwise GC.Collect() and GC.KeepAlive() may be needed

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.