C#调用C++动态库 dll 时遇到的一些常见问题以及解决方案

在 C# 调用 C++ 生成的 dll 时,如果是新手,会有如下一些问题需要解决:

一:参数匹配的问题,特别是字符串与 char* 参数类型

二:编译时提示没有找到相关的函数名字

三:C#如何注册C++回调函数

 

解决方案:

首先给大家推荐一款软件,叫做CLRInsideOut,其中一个的功能就是把C++下的结构体或者函数声明转换成C#下的定义,效果如下:

C#调用C++动态库 dll 时遇到的一些常见问题以及解决方案_第1张图片

一:指针、地址相关的问题很多,这里只说我遇到的几个问题

1.C#调用C++函数时传入字符串,代码如下:

C++头文件:

#define __EXPORT__ _declspec(dllexport)

extern "C"
{
	__EXPORT__ void __stdcall Test3(char *str);
}

C++源文件:

__EXPORT__ void __stdcall Test3(char *str)
{
	printf(str);
	printf("\n");
}

C#导入C++的dll方法:

[DllImport("test.dll", EntryPoint = "Test3", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern void Test3();

C#调用C++函数接口:

string str3 = "大江东去,浪淘尽,千古风流人物";
cPlus.Test3(str3);

执行结果:

C#调用C++动态库 dll 时遇到的一些常见问题以及解决方案_第2张图片

2.C#调用C++函数时只传出常量字符串,代码如下:

extern "C"
{
	__EXPORT__ char* __stdcall Test2();
}
__EXPORT__ char* __stdcall Test2()
{
	return "大江东去,浪淘尽,千古风流人物";
}
[DllImport("test.dll", EntryPoint = "Test2", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr Test2();
IntPtr intptr = cPlus.Test2();
string str2 = Marshal.PtrToStringAnsi(intptr);
Console.WriteLine("str2 = {0}", str2);

C#调用C++动态库 dll 时遇到的一些常见问题以及解决方案_第3张图片

3.C#调用C++函数时只传出变量字符串字符串,代码如下:

extern "C"
{
	__EXPORT__ void  __stdcall Test1(char* buf, int len);
}
__EXPORT__ void __stdcall Test1(char* buf, int len)
{
	//str的长度必须小于len,这里不做处理,但是要注意
	char* str = "大江东去,浪淘尽,千古风流人物";
	for (unsigned int i = 0; i < strlen(str); i++)
	{
		buf[i] = str[i];//只能用这种方式保存数据
	}
	buf[strlen(str)] = '\0';
}
[DllImport("test.dll", EntryPoint = "Test1", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern void Test1([Out, MarshalAs(UnmanagedType.LPStr)]StringBuilder outHello, int len);
StringBuilder str1 = new StringBuilder();
str1.Capacity = 256;
cPlus.Test1(str1, str1.Capacity);
Console.WriteLine("str1 = {0}", str1.ToString());

C#调用C++动态库 dll 时遇到的一些常见问题以及解决方案_第4张图片

二:C#提示找不到函数入口,我曾经在这里栽过很多跟头,十分恼火,最终是这样解决的

1.首先确定C++代码没有问题,生成的dll也没有问题

2.特别要注意的是C++导出一定要用C语言的形式导出,生成的dll中,C#是根据函数的名字以及所有参数的数据类型所占的总长度来决定的,特别要注意的就是经常把参数列表中的数据类型指针类型与这种数据类型混淆,因为指针一定占4字节,而参数数据类型就不一定了,所以C#判断的时候,参数数据长度总是不匹配,然后就找不到函数入口

例如:

struct STest 
{
	char str[16];
	int a;
	double b;
};

extern "C"
{
	//下面两个函数的参数总长度是不一样的
	__EXPORT__ void  __stdcall Test4(STest *p);
	__EXPORT__ void  __stdcall Test5(STest p);
}

 

三:注册回调函数的方法如下:

1.C++回调函数形式

typedef int(WINAPI *pCallbackConsole)(int systemId, int nodeId, EMS_PLUGIN_FEATURECODE *pFcArray, int iFcSize, int commandId, EMS_PLUGIN_CONSOLE_PARAM *pParamArray, int iParamSize, void *pContext, char* strError512);
extern "C"
{
	ExPort bool __stdcall EMS_InitEx(EMS_PLUGIN_INIT pInitParam, pCallbackConsole fnCallback, void *pContext);
}

2.C#委托并实现回调函数,C#函数都是在类中定义的,委托也一样

[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public delegate int CallbackDelegate(int systemId, int nodeId, IntPtr pFc, int iFcSize, int commandId, IntPtr pParam, int iParamSize, IntPtr pContext, ref string strError512);
public static int CallbackFunc(int systemId, int nodeId, IntPtr pFc, int iFcSize, int commandId, IntPtr pParam, int iParamSize, IntPtr pContext, ref string strError512)
{
    //回调函数体
}
[DllImport("EMS_CSharp.dll", EntryPoint = "EMS_Init_", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
public static extern bool EMS_Init_(EMS_PLUGIN_INIT pInitParam, CallbackDelegate fnCallback, IntPtr pContext);

3.C#调用C++接口并注册回调函数,特别要注意委托变量一定要是对象属性,因为对象属性在对象生命周期内不会被垃圾回收机制回收。而函数体内的变量会在函数执行完后被回收,这样在调用回调函数时,函数体内的委托变量早就失效,导致程序崩溃,而且找不到问题所在,所以只能是最长生命周期的对象属性才行

EMS_SDK.CallbackDelegate fnCallback = EMS_SDK.CallbackFunc;
EMS_SDK.EMS_Init_(plugin, fnCallback,IntPtr.Zero)

好了,以上就是我以前常遇到的问题,欢迎大家阅读,多多交流,谢谢!

你可能感兴趣的:(基础技术)