C# callback from native code

B

Boris

Hello,

There's an example in .Net docs where C# delegate is called by native code
( EnumWindows -
http://msdn.microsoft.com/en-us/library/d186xcf0(v=VS.71).aspx ). The doc
states:
4.Ensure that the garbage collector does not reclaim the delegate before the
callback function completes its work. When you pass a delegate as a
parameter, or pass a delegate contained as a field in a structure, it
remains uncollected for the duration of the call. So, as is the case in the
following enumeration example, the callback function completes its work
before the call returns and requires no additional action by the managed
caller.
<<<PASTE<<<<<<<<

- so, because garbage collection is disabled for duration of the call
(EnumWindows) - everything is fine.

The doc also states:
If, however, the callback function can be invoked after the call returns,
the managed caller must take steps to ensure that the delegate remains
uncollected until the callback function finishes. For detailed information
about preventing garbage collection, see Interop Marshaling with Platform
Invoke.
<<<PASTE<<<<<<<<

I couldn't find any detailed description on how to handle this later case in
the docs (even though it says "see Interop Marshaling with Platform Invoke".
I have code that uses this very scenario and would like to know if my code
(below) is safe in regards to garbage collection. My concern: garbage
collector could move the delegate around, but native code would still use
old address for callback.

Code on managed side (C#) - error handling skipped for brevity:

public class TheForm : System.Windows.Forms.Form
{
...
private SomeClass m_SomeClass;
...
private void TheForm_Load(...)
{
...
m_SomeClass = new SomeClass(this);
m_SomeClass.Start();
...
}
...
}
public class SomeClass
{
....
public delegate void MyDelegateCallback(String strText);
public static MyDelegateCallback m_callback;
private IntPtr m_hListener;
private ArrayList cachedTextList;
....
[System.Runtime.InteropServices.DllImport("SomeDll.dll",
Charset=...Ansi)]
public static extern IntPtr MyNative_CreateListener(MyDelegateCallback
pCallback);
....
public void MyCallbackFunc(String strText)
{
// Just add text to the list to be processed later
m_cachedTextList.Add(strText);
}
....
public void Start()
{
m_callback = new MyDelegateCallback(MyCallbackFunc);
m_hListener = MyNative_CreateListener(m_callback);
}
}

Code on native side - in some DLL ( C ) - error handling skipped for
brevity:

typedef void ( __stdcall *PFUNCEVENT) (char *strText);

DWORD WINAPI MyThreadProc(
__in LPVOID pParameter
)
{
PFUNCEVENT pCallBack = (PFUNCEVENT) pParameter;

Sleep(5000);

pCallBack("some text");

return 0;
}

__declspec(dllimport) PVOID MyNative_CreateListener(PFUNCEVENT pCallBack)
{
DWORD threadId = 0;
PDWORD pContext = malloc(sizeof(DWORD));
*pContext = 0;
CreateThread(NULL,0,MyThreadProc,(LPVOID)pCallBack,0,&threadId);
return (PVOID) context;
}
 
P

Peter Duniho

Boris said:
[...]If, however, the callback function can be invoked after the call
returns, the managed caller must take steps to ensure that the delegate
remains uncollected until the callback function finishes. For detailed
information about preventing garbage collection, see Interop Marshaling
with Platform Invoke.
<<<PASTE<<<<<<<<

I couldn't find any detailed description on how to handle this later
case in the docs (even though it says "see Interop Marshaling with
Platform Invoke".
I have code that uses this very scenario and would like to know if my
code (below) is safe in regards to garbage collection. My concern:
garbage collector could move the delegate around, but native code would
still use old address for callback.

I'm not an expert, but my understanding is that the p/invoke layer wraps
the managed delegate with its own function pointer. So the pointer that
the unmanaged code gets isn't actually a pointer directly into your
delegate instance.

You should be able to accomplish the required safety simply by keeping
your own managed reference to the delegate instance as long as the
unmanaged code is holding on to the marshaled function pointer.

Note that that's all that the code example on this page does:
http://msdn.microsoft.com/en-us/library/7esfatk4(VS.71).aspx

This article seems to agree with my understanding:
http://msdn.microsoft.com/en-us/magazine/cc164193.aspx#S7

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top