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失效问题。