在C#中调用含有指针和结构体的C语言DLL

时间:2024-03-18 15:07:18

C和C#作为日常工作中很常见的两种开发语言,难免会遇到彼此需要调用的情况。本章重点讲一下关于C#调用C的dll的经验,希望能给有用到的朋友以借鉴。
首先制作C版本的dll,因为我的VS2017有点小故障,无法创建Win32工程,所以我选用了VC++6.0来制作。步骤如下所示:
步骤一:新建Win32DLL项目
在C#中调用含有指针和结构体的C语言DLL
第二步:创建C的SOURCE文件,并编写API函数
在C#中调用含有指针和结构体的C语言DLL
void C_API(uint8_t *Ptr, UInt32_t param, ParamStru *stru)
{
…函数实现
}

第三步:新建C的HEADER文件,并添加相应处理
在C#中调用含有指针和结构体的C语言DLL
typedef struct
{
uint32_t param1;
uint32_t param2;
}SubParamStru;
typedef struct
{
uint32_t num;
SubParamStru paramStru[];
}ParamStru;
extern “C” _declspec(dllexport) void C_API(uint8_t *Ptr, UInt32_t param, ParamStru *stru);

需要注意的是在Header文件中对接口函数的extern “C” _declspec(dllexport)修饰不可缺少。
完成之后,对工程进行编译,这时在输出路径下会有project.dll和project.lib生成,选用project.dll即可。至此,C版本的dll就已经完成了。

将dll拷贝到C#工程的输出路径下,在cs文件中添加对应的结构体和引用路径:
struct SubParamStru
{
uint32_t param1;
uint32_t param2;
}
struct ParamStru
{
uint32_t num;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
SubParamStru [] paramStru;
}

[DllImport(“project.dll”, EntryPoint = “C_API”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern void C_API(IntPtr Ptr, UInt32 param, IntPtr struPtr );

至此,C#的调用就已经完成了,对于常规处理,到这一步基本就结束了,但是我们给出的范例由于接口函数中存在指针和结构体数组,所以后续调用实现中还有很大一部分工作要做,而且是整个调用的难点所在。

首先,指针作为输入参数,在C#中调用处理如下:
byte[] tmpD = new byte[data_len]; //定义数组
//定义非托管的内存
IntPtr dataPtr = Marshal.UnsafeAddrOfPinnedArrayElement(tmpD, 0);
//其中classD.data是静态数据,将托管内存中的数据写入非托管内存中
Marshal.Copy(classD.data, 0, dataPtr, data_len);

对于结构体需要按照如下方式处理:
ParamStru currParanStru; //定义结构体变量

currParanStru.paramStru = new SubParamStru[STRUC_SIZE]; //其中STRUC_SIZE为需要开辟的数组大小

//对结构体变量初始化
currParanStru.num = 0;
for (int i = 0 ; i < STRUC_SIZE; i++)
{
currParanStru.paramStru[i] .param1 = 0;
currParanStru.paramStru[i] .param2 = 0;
}

// 计算结构体的大小
Int32 struSize = Marshal.SizeOf(currParanStru);
//开辟非托管的内存空间
IntPtr structPtr = Marshal.AllocHGlobal(struSize);
//获取结构体类型的指针
Marshal.StructureToPtr((object)currParanStru, structPtr, false);

//C接口的调用
C_API(dataPtr , param, structPtr );

此处的结构体是输出参数,故需要对结构体进行解析,解析方法如下:
ParamStru ret = (ParamStru )Marshal.PtrToStructure(structPtr, typeof(ParamStru ));

这样就完成了整个C语言的dll的调用,之后ret就完成按照结构体变量使用就可以了。注意执行结束的时候对申请的内存需要释放处理。

整片文章涵盖了以下几个要点:
1、VC++6.0中DLL的制作;
2、C#工程中DLL的调用;
3、指针入参的处理;
4、结构体输出参数以及结构体中包含结构体数组的处理;
5、结构体参数的解析。