Delegates, function pointers, and evil unmanaged code that should die

I'm trying to write a plugin for a popular program in C# instead of C/C++. I decided to make an API so people couldeasilywrite these plugins in C#. The basic requirements are as follows.

Make a dll that exports the following two functions:


extern "C"int __declspec(dllexport) plugin_version( );
extern "C"int __declspec(dllexport) plugin_main(char* eventname,void* ptr );

In the first call to from the main program toplugin_main in the plugin,ptr points to a structure with an element that is a pointer to a function in the main program with the following signature. I also listed the delegate I made for it on the C# code.



int plugin_send(char *guid,char *event,void *data);

internaldelegateint plugin_function_send( [MarshalAs( UnmanagedType.LPStr )]string guid, [MarshalAs( UnmanagedType.LPStr )]string eventname, [In, Out, MarshalAs( UnmanagedType.AsAny )]object data );

All communication FROM the plugin TO the main program goes through this callback.

Problem #1:
While debugging, my calls from the managed code to this callback (using the delegate formed from usingMarshal.PtrToStructure where the field in the structure is specified as this delegate type) result in an error from the managed debugging assistant that says:

Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in '<mainexecutable.exe>'.

Additional Information: A call to PInvoke function 'InteropTest!InteropTest.plugin_function_send::Invoke' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.

If, instead of directly calling through the delegate, I create a function in my wrapper dll andDllImport it with the Cdecl calling convention explicitly specified and give it the function pointer for the delegate and the parameters that should be passed to it (wrap the callback back into the main program), it works without the error. It seems the delegate needs to be called with the Cdecl calling convention but the managed code isn't doing that? Unfortunately the callback in the main program is not exported as a symbol and can only be reached through the function pointer that it gives the plugin, so I can'tDllImport it with the Cdecl calling convention like I can the functions in my wrapper.

In this callback to the main program, I pass an instance of a class (pointer to struct in C) in thedata parameter, one element of which is a delegate that is to be used as a callback back into the plugin code. (My head hurts already). Aside from the two exported functions above, ALL calls from the unmanaged code will be to some function with the following signature, again with the delegate I have made for it:



typedefint (*ttkCallback)(int windowID,char *subwindow,char *eventname,void *data,void *userData);

internaldelegateint ttkCallback(int windowID, [MarshalAs( UnmanagedType.LPStr )]string subwindow, [MarshalAs( UnmanagedType.LPStr )]string eventname, [In, Out]IntPtr data, IntPtr userData );

data in most cases is either a pointer to a structure (there are many MANY different ones), a pointer to a string, or NULL. You know which one it is by reading the string passed ineventnameand looking up the matching data type in the API, and castingvoid* data to that type (if it was C code). In my current test code, I am testing just one of these callbacks. I useMarshal.PtrToStructure to readdata into the type of structure I know it is.This works, regardless of whether or not I wrap the call toplugin_send. The fields of the resulting structure the main program sends the callback it was given look just as they should. One field in the particular structure sent in my test case ischar* text. On return from the callback, that field holds the text that should be printed to the window in the main program. According to the SDK documentation, returning a NULL pointer in this field (AKA leaving it alone since it starts as NULL) means no text should be printed.

Problem #2: Even if I justreturn 0; (callback must return 0) and don't touch the pointer in thevoid* data parameter or the memory it points to, I get an error. The error is a buffer overrun detected. Just before that, the plugin callback I talk about in the paragraph just above this one is called AGAIN by the main program. This timedata contains "the same pointer to a text string that was returned by the plugin callback on the previous call." In this case, it should be NULL, but the debugger proves that it points to some location in memory that I haven't identified, but by looking at the memory before and after that location I can tell it's somewhere in the program's data memory.

I tried again, this time usingMarshal.StringToCoTaskMemAnsi andMarshal.WriteIntPtr to set the pointertext indata on the first call to "Test string," just in case returning a NULL pointer was not in fact acceptable. The buffer overrun detected error still occurred, and the pointer given to the callback the second time in thedata parameter pointed to the same location as before, and not to the string previously allocated byMarshal.StringToCoTaskMemAnsi.

[7717 byte] By [280Z28] at [2008-3-3]
# 1
I wrapped the callback in my C++ wrapper dll. I made an instance of a class that stored the delegate I was having trouble with and exported a function with Cdecl calling convention from the dll that calls that function. I passed a pointer to the instance of that class in the userData parameter so it gets returned in the callback.

Everything seems to be working now. :)

It is somewhat a pain that you can't easily mark a delegate as having a certain calling convention.

280Z28 at 2007-9-9 > top of Msdn Tech,.NET Development,Common Language Runtime...
# 2
You can easily add calling convention to delegate in Whidbey using UnmanagedFunctionPointer attribute, e.g.:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int MyEvent ( sender object, Exception e);

jkotas at 2007-9-9 > top of Msdn Tech,.NET Development,Common Language Runtime...
# 3
Tongue Tied
280Z28 at 2007-9-9 > top of Msdn Tech,.NET Development,Common Language Runtime...
# 4
Where can i found UnmanagedFunctionPointer? What's its namespace? What's the corresponding code in vb.net?
Reiguz at 2007-9-9 > top of Msdn Tech,.NET Development,Common Language Runtime...
# 5
It's an attribute, and can be found in the System.Runtime.InteropServices namespace.

Sorry I can't help you with VB code. I don't understand it at all.

280Z28 at 2007-9-9 > top of Msdn Tech,.NET Development,Common Language Runtime...

.NET Development

Site Classified