I have an unmanaged C++ DLL and a .net application which communicate using P/Invoke .
I need to update a dictionary within the .net app from a C++ DLL.
I tried to do that in the following way:
public delegate void MyDelegate(string address, string username);
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SaveMyDelegate(MyDelegate callback);
private static SortedDictionary<string, string> dict = new SortedDictionary<string, string>();
public void save_delegate() {
SaveMyDelegate(delegate(string address, string username) {
if (username != "") {
dict.Add(address, username);
}
});
}
On C++ side I have:
typedef void (*MANAGED_CALLBACK)(string user_address, string username);
extern "C" __declspec(dllexport) void SaveMyDelegate(MANAGED_CALLBACK callback);
MANAGED_CALLBACK my_callback;
void SaveMyDelegate(MANAGED_CALLBACK callback) {
my_callback = callback;
}
extern "C" __declspec(dllexport) VOID start_collection();
VOID start_collection() {
my_callback("test", "User");
}
On C# I do the following calls:
[DllImport("MyDLL.dll")]
public static extern void start_collection();
private void Button1_Click(object sender, EventArgs e) {
save_delegate();
start_collection();
}
When I call start_collection() I get an AccessViolation exception.
I tried to declare the delegate in the following way:
public delegate void MyDelegate(
[MarshalAs(UnmanagedType.LPStr)]string address,
[MarshalAs(UnmanagedType.LPStr)]string username);
I have also added this statement:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void MyDelegate(...)
I've not looked at every detail of your code, but some commonly seen p/invoke issues just out immediately:
You cannot use C++ strings as interop types. You should use null terminated character arrays on the C++ side. Obtain these using the c_str() method of std::string.
The delegate likely has the wrong calling convention. The unmanaged code (probably) uses cdecl and so you need to use the [UnmanagedFunctionPointer(CallingConvention.Cdecl)] attribute when declaring the delegate type in C#.
Your C# delegate is susceptible to being collected early (while your unmanaged code still holds a reference to it) because there are no managed references to it. Typically that would be solved by holding a reference to the delegate in a static variable in your C# code.
Related
I used to register a delegate from C# to a callback function pointer like this (without using Marshal.GetFunctionPointerForDelegate):
C++ (Test.dll)
typedef void (__stdcall* Callback)(int num);
Callback pCallback = 0;
void __stdcall SetCallback(Callback callback)
{
pCallback = callback;
}
void __stdcall ActionCpp()
{
if(pCallback)
{
pCallback();
}
}
C#
class Program
{
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetCallBack(Callback callback);
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void ActionCpp();
public delegate void Callback();
static void Action()
{
Console.WriteLine("Callback succeeded.");
}
static void Main(string[] args)
{
Callback myAction = new Callback(Action);
SetCallback(myAction);
ActionCpp(); //Suppose to do the same thing as Action();
}
}
It seems that this way works fine. However, I found that we can do the same thing by using Marshal.GetFunctionPointerForDelegate and register the IntPtr of the delegate to the function pointer in C++.
I would like to know what the difference is and which practice is better? (And also Why? Thanks in advance.)
Here is the C# code when using Marshal.GetFunctionPointerForDelegate. (Nothing changes in C++ code.)
C# (With Marshal.GetFunctionPointerForDelegate)
class Program
{
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetCallBack(IntPtr pCallBack); //change to type "IntPtr" this time.
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void ActionCpp();
public delegate void Callback();
static void Action()
{
Console.WriteLine("Callback succeeded.");
}
static void Main(string[] args)
{
Callback myAction = new Callback(Action);
//GCHandle gcHandle = GCHandle.Alloc(myAction); <-- Is this necessary?
IntPtr pMyAction = Marshal.GetFunctionPointerForDelegate(myAction);
SetCallback(pMyAction);
ActionCpp();
//gcHandle.Free();
}
}
I have another further but related question about whether it is necessary to use "GCHandle.Alloc" (as in my comments in the above code) to avoid any GC action as long as my Callback myAction is still alive?
I am a newbie on C# and C++ callbacks, please let me know if I have naive mistakes made.
A callback requires the start address of the method that handles the event. So your two methods are both getting the same start address. The first method is clear since you are using the name of the method. 2nd method you have more statements that accomplishes the same as first method.
c# is managed language while c++ is un-managed. Which basically means that extra protection was added to c# to prevent PC from going blue screen. So when calling un-managed c++ from c# rules must be followed to prevent blue screen. So any pointer variables passed to a c++ method must be allocated in c# as un-managed using GCHandle.Alloc or by using other Marshal methods. Marshal.StrToPtr will also allocate the un-managed memory.
I'm having trouble converting a C++ .dll function to C#.
The function is this:
void funct(void*(*handler)(void*));
I think this means passing a pointer to function taking a void pointer and returning a void pointer, as explained here:
Passing Function Pointer.
What I'm trying to do is do the same thing in C#, but I have do idea how. I tried to use delegates, but I am both unsure how to and also if they can even do what I am trying to do.
Thanks for the help!
EDIT:
Here are the C++ functions for register_message_handler and message_handler:
void register_message_handler(void*(*handler)(void*));
void *message_handler(void *raw_message);
EDIT:
xanatos has the exact explanation and conversion to C# below. Thanks a ton xanatos!
void funct(void*(*handler)(void*));
is a function that accepts a pointer and returns a pointer.
In C# it would be:
IntPtr MyFunc(IntPtr ptr);
So you'll need a delegate like:
public IntPtr delegate MessageHandlerDelegate(IntPtr ptr);
[DllImport("mydll.dll")]
public static extern void register_message_handler(MessageHandlerDelegate del);
Note that P/Invoke (calling native methods) is one of the rare cases where Action<...> and Func<...> delegates don't work, and you have to build "specific" delegate.
BUT to call it, it's a little complex, because you must save a "copy" of the delegate so that if the C functions calls this method at any time, this copy is still "alive" and hasn't been GC (see for example https://stackoverflow.com/a/5465074/613130). The most common way to do it is to encapsulate everything in a class, and put the copy in a property/field of the class:
class MyClass
{
public delegate IntPtr MessageHandlerDelegate(IntPtr ptr);
[DllImport("mydll.dll")]
public static extern void register_message_handler(MessageHandlerDelegate del);
[DllImport("mydll.dll")]
public static extern IntPtr message_handler(IntPtr message);
public MessageHandlerDelegate Del { get; set; }
public void Register()
{
// Make a copy of the delegate
Del = Handler;
register_message_handler(Del);
}
public IntPtr Handler(IntPtr ptr)
{
// I don't know what ptr is
Console.WriteLine("Handled");
return IntPtr.Zero; // Return something sensible
}
}
Note that if you use IntPtr then you don't need the unsafe.
If you want to pass message_handler to register_message_handler the safest way is to
// Make a copy of the delegate
Del = message_handler;
register_message_handler(Del);
There is a possibility that you can do directly no there isn't, checked
register_message_handler(message_handler);
and that the CLR will solve this, BUT I'm not sure of this... I can't easily test it, and I wouldn't do it. (if you want to test it, add a GC.Collect() just after the register_message_handler. If after some time you receive a CallbackOnCollectedDelegate error then you know you can't do it :-) )
Mmmh... checked. You can't do the raw register_message_handler(message_handler), you have to use MyClass.
Be very aware of something: it's better to always specify the calling convention C-side and C#-side even in function pointers. C# uses stdcall, while C uses cdecl. In x86 mode you can get very awful silent crashes (in x64 there is a single calling convention)
void __stdcall register_message_handler(void* (__stdcall *handler)(void*));
void * __stdcall message_handler(void *raw_message);
and C# side
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr MessageHandlerDelegate(IntPtr ptr);
[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void register_message_handler(MessageHandlerDelegate del);
[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr message_handler(IntPtr message);
(or everywhere cdecl)
I want to link an unmanaged C++ library to a C# app. I am using the PInvoke process because the unmanaged C++ dll has multiple dependencies that won't compile with CLR. When I compile the example code below, I am getting the following errors. I did find a reference that I need to add the dll reference, but MSVS tells me it can't add it. I also read about registering it with the regsvr32, but that appears to be specific to CLR libraries, right? So my question is, how do I get clear this error for a unmanaged dll?
ServerTerminal.cs(62,48): error CS1031: Type expected
ServerTerminal.cs(62,48): error CS1519: Invalid token ';' in class, struct, or interface member declaration
ServerTerminal.cs(64,48): error CS1031: Type expected
ServerTerminal.cs(64,48): error CS1519: Invalid token ';' in class, struct, or interface member declaration
ServerTerminal.cs:
class ServerTerminal
{
private delegate int Callback(string text);
private Callback mInstance;
public ServerTerminal()
{
mInstance = new Callback(Handler);
SetCallback(mInstance);
}
public void Test()
{
TestCallback();
}
private int Handler(string text)
{
return 0;
}
[DllImport(#"..\\lib\\DDS_Service.dll", EntryPoint="SetCallback")];
private static extern void SetCallback(Callback fn);
[DllImport(#"..\\lib\\DDS_Service.dll", EntryPoint="TestCallback")];
private static extern void TestCallback();
}
and the C++ DLL's Component.h:
typedef int (__stdcall * Callback)(const char* text);
Callback Handler=0;
class COM_Component : public CM_Component
{
// Contents not pasted
}
and the C++ DLL's Component.cpp:
extern "C" __declspec(dllexport)
void __stdcall SetCallback(Callback handler)
{
Handler = handler;
}
extern "C" __declspec(dllexport)
void __stdcall TestCallback()
{
int retval = Handler("hello world");
}
COM_Component::COM_Component( void ) : CM_Component( TDstring( "COM_Component" ) )
{
// register the observer callback methods
}
// Remainder of file not pasted
Your "invalid token" compiler errors are due to the semicolon immediately after the DllImport attributes. Additionally, you're specifiying a verbatim #"..." string with double backslashes in it. I think your declaration should look like:
[DllImport(#"..\lib\DDS_Service.dll", EntryPoint="SetCallback")]
private static extern void SetCallback(Callback fn);
If your DLL is a COM dll, then you can run regsvr32 to register it, and add a reference to it in your project. If that's the case, then you don't need to use P/Invoke: you would be able to reference it like any other library.
[DllImport(#"..\\lib\\DDS_Service.dll", EntryPoint="SetCallback")];
private static extern void SetCallback(Callback fn);
[DllImport(#"..\\lib\\DDS_Service.dll", EntryPoint="TestCallback")];
private static extern void TestCallback();
Remove the ; in the lines after the DllImport attribute.
this is my C code
extern "C"
{
__declspec(dllexport) void DisplayHelloFromDLL(string a)
{
printf ("%s\n",a)
}
}
this is my C# code
class HelloWorld
{
[DllImport("TestLib.dll")]
public static extern void DisplayHelloFromDLL(string a);
static void Main ()
{
string a = "Hello";
DisplayHelloFromDLL(a);
}
}
It built successfully but crash like this:
SO,how to use P/invoke to call my own C dll from C#?
Please help,thanx in advance.
First of all your code is C++ rather than C. Your function receives a parameter of type std::string and the use of std::string means that your code is actually C++.
Now this parameter type is the root of your problem. You cannot create a std::string in .net and instead will need to use a char* to pass the string data. The following code is what you need:
C++
__declspec(dllexport) void DisplayHelloFromDLL(char* a)
{
printf("%s\n", a);
}
C#
[DllImport("TestLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void DisplayHelloFromDLL(string a);
static void Main ()
{
string a = "Hello";
DisplayHelloFromDLL(a);
}
The default p/invoke marshalling for a .net string is to pass a char* as an [In] parameter. There is no need for the complexity of IntPtr, StringToHGlobalAnsi, FreeHGlobal as suggested by one of the other answers. If you can let the p/invoke marshaller do the work then it is preferable to do so.
Note that you also need to make sure that your calling conventions match. Under the assumption that you have not used any special compiler options when building your C++ code, that code will default to used cdecl calling convention. You can make that match with the CallingConvention parameter to the DllImport attribute.
Please take a look at marshalling string at MSDN
In a nut shell, a C# string doesn't get marshalled as std::string but a char* by default
For one thing the return type is not matching. In C it is void and in C# int.
Change your C++ param type to char* and update your C# code as following
class HelloWorld
{
[DllImport("TestLib.dll")]
public static extern void DisplayHelloFromDLL(IntPtr a);
static void Main ()
{
string a = "Hello";
var ptr = System.Runtime.Marshal.StringToHGlobalAnsi(a);
DisplayHelloFromDLL(ptr);
System.Runtime.Marshal.FreeHGlobal(ptr);
}
}
I'm trying to execute some methods (in this particular case, rdOnAllDone) from a third party DLL, written in C, and looking trough the headers files, I found this:
#ifndef TDECLSDONE
#ifdef STDCALL
#define CCON __stdcall
#else
#define CCON __cdecl
#endif
#define TDECLSDONE
#endif
#define DLLIMP __declspec (dllimport)
DLLIMP int CCON rdOnAllDone (void(CCON *)(int));
After goggling for a way to call this method, I made this:
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(Delegate d);
public delegate void rdOnAllDoneCallbackDelegate();
private static void rdOnAllDoneCallback()
{
Console.WriteLine("rdOnAllDoneCallback invoked");
}
The method was called correctly except that I couldn't get the int parameter. So I tried adding the input parameter int like this
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(Delegate d);
public delegate void rdOnAllDoneCallbackDelegate(int number);
private static void rdOnAllDoneCallback(int number)
{
Console.WriteLine("rdOnAllDoneCallback invoked " + number);
}
But now delegate is called twice and and it crashes the program with the following error " vshosts32.exe has stopped working"
What's the correct way to call this DLL method?
EDIT: Forgot to add the Main method:
public static void Main()
{
rdOnAllDoneCallbackDelegate del3 = new rdOnAllDoneCallbackDelegate(rdOnAllDoneCallback);
rdOnAllDone(del3);
while (true)
{
Thread.Sleep(1000);
}
}
Three things you need to do to make this work right:
you need to tell the pinvoke marshaller about the actual delegate type, using Delegate isn't good enough. That will create the wrong thunk that won't properly marshal the argument. Which is what you saw happening.
you need to tell the marshaller about the calling convention if it isn't __stdcall with the [UnmanagedFunctionPointer] attribute. Getting this wrong imbalances the stack with good odds for a hard crash.
you need to store a reference to the delegate object so that the garbage collector won't collect it. It cannot see references held by native code. Getting this wrong makes the native code fail with a hard crash after the next garbage collection.
So this ought to work better, tweak as necessary:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void rdOnAllDoneCallbackDelegate(int parameter);
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(rdOnAllDoneCallbackDelegate d);
class Foo {
private static rdOnAllDoneCallbackDelegate callback; // Keeps it referenced
public static void SetupCallback() {
callback = new rdOnAllDoneCallbackDelegate(rdOnAllDoneCallback);
rdOnAllDone(callback);
}
private static void rdOnAllDoneCallback(int parameter) {
Console.WriteLine("rdOnAllDoneCallback invoked, parameter={0}", parameter);
}
}
Your delegates signature has to match that of the native callback, also it has to have the UnmanagedFunctionPointerAttribute set appropriately.
In your case like so:
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void rdOnAllDoneCallbackDelegate(int parameter);
[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(rdOnAllDoneCallbackDelegate callback);
Usage:
{
rdOnAllDone(rdOnAllDoneCallback);
}
private static void rdOnAllDoneCallback(int parameter)
{
Console.WriteLine("rdOnAllDoneCallback invoked, parameter={0}", parameter);
}