Contents

C与C#的交互

在C#执行C编写的DLL时可能会出现很多问题,而绝大部分问题都出自数据封装传送上,接下来总结了本次交互实验。

环境使用MSVC2019,使用__declspec(dllexport)作为DLL导出关键字,使用__stdcall作为调用协议。

为了更方便使用,声明两个宏:(WINAPI就是__stdcall,来自于windows.h)

#define DLLEXPORT __declspec(dllexport)
#define CALLAPI WINAPI

C#中使用char*和sbyte*等指针使用new string(指针)的方式来构建。

C中可能会出现一些Windows定义的类型,如LPCWSTR,代表着(long pointer const wstring),实际类型为const wchar_t*,像这样的类型还有很多,但他们都是通用的。

实验1:返回一个字符串

{{EJS0}}

C#端代码:

{{EJS1}}

使用string接受返回char*,执行结果:崩溃

{{EJS2}}

使用char*接受返回char*,执行结果:乱码

应该是大小不符导致的,C#的char是2字节的,因此尝试sbyte。

{{EJS3}}

sbyte有符号字节和C的char一样,执行结果:正确

总结:

因为C#侧没有预分配内存空间,所以只能使用指针来接受后在构建字符串。

 

实验2:返回一个宽字符串

{{EJS4}}

C#端代码:

{{EJS5}}

执行结果:崩溃

{{EJS6}}

执行结果:正确

 

实验3:通过参数指针传出字符串

{{EJS7}}

C#端代码:

{{EJS8}}

string内存不可写,执行结果:异常。

{{EJS9}}

执行结果:正确

{{EJS10}}

执行结果:乱码

{{EJS11}}

执行结果:正确

同样是数据大小的差异问题

 

实验4:通过参数传出一个字符串数组

{{EJS12}}

C#代码:

{{EJS13}}

实验结果:正确

先分配一个指针数组,在让数组里的指针指向字符数组(字符串),就是字符串数组了

c中使用char则C#使用sbyte,同样的,c中使用wchar_t则C#中使用char

{{EJS14}}

实验结果:意料之外的结果

 

实验5:结构体的传递

{{EJS15}}

C#端

{{EJS16}}

无法封送Stringbuilder类型,执行结果:异常

{{EJS17}}

声明封送非托管类型为字符数组,执行结果:正确

{{EJS18}}

声明封送非托管字符串,执行结果:正确

{{EJS19}}

固定的数组大小,执行结果:正确

 

实验6:函数指针与委托传递

{{EJS20}}

C#代码:

{{EJS21}}

执行结果:

{{EJS22}}

需要给委托的传参和返回值标示为宽字符的字符串

{{EJS23}}

再次执行结果:正确。

 

也可以直接使用char*(对应c的wchar_t*)来声明委托

{{EJS24}}

实现:

{{EJS25}}

执行结果:正确

虽然此段代码正确但并不安全,数组是托管对象,可能会在返回后被移动。但返回str指针是安全的。

 

 

问题与实验总结

当编写C时,指针最好使用参数传出的方式让C#调用。如果只是返回全局或静态指针还可以,如果是局部变量会造成悬垂,如果分配堆内存的话C#侧是不知道的,内存不好释放。所以让C#来准备对象,C侧把数据填入C#侧准备好的内存中,然后在C#侧管理内存。

在程序编写的过程中一定要清楚哪些数据是C侧的,哪些是C#侧的,指向C侧的指针数据不会移动,而指向C#侧的指针可能会被GC移动,要格外小心。

 

踩坑

在传递数组的时候需要注意个问题,就是在C侧的数组有可能只写了部分内存,比如固定的8长度数组,但是只写了4长度,剩下的内存没写并且没有在写数据之前将内存清0,则剩下的内存中乱七八糟的数据在C#中构造则会出现不可预知的后果。

当C#传递给C++委托时,应该使用指向静态方法的委托,否则可能会因堆对象移动导致this失效问题。