C#与C++/CLI混合编程
最近做项目用到了一个二维码识别的SDK,想在C#里做个界面去调用该库生成和解析二维码,原本之前做过在C#里调用DLL的研究,没想到这次的尝试会异常的艰难,听我慢慢说来。
先用google搜了一通,总结了C#下调用DLL的几种方法:
1、C#下Invoke。通过DLLImport动态导入DLL中的函数,然后直接调用之。这种方法比较适合WinAPI和参数比较简单的函数(最好还是通过纯C的方式,即extern "C"的方式导出的函数),否则要在C#里构造出各种自定义的struct和指针,很麻烦而且调试起来很困难;
2、做个基于COM的DLL Warpper。除非你很懂COM,不然还是用方法1吧;
3、用C++/CLI做Warpper。以前忽视了C++/CLI的存在,觉得这就是个怪胎,而且代码看起来很丑陋。不过通过这次的项目,我发现原来是我太肤浅了。这是本人强烈推荐的方法,如果你有很多原来做的模块,想在转向.net后还能接着用,那就听我的,抽点时间来学学C++/CLI吧!
由于在C++/CLI可以同时编写托管和非托管平台的代码,因此用来做Warpper再合适不过了。关于C++/CLI的内容,我会另外开博来谈,本篇文章重点还是说说在项目中的运用。
1、要想在C#里识别你写的类和方法,那么它的参数就必须是托管平台能够识别的类型。例如:
System::Int32 QRCodeWapperCLI::QRCodeWapper::DeCodeQRFromFile(String^ filename,[System::Runtime::InteropServices::OutAttribute] String^% outQRInfo)
这个声明中托管类型有个^符号,值类型可以不用区分;%符表示ref,再加上属性[System::Runtime::InteropServices::OutAttribute] 就表示 out类型的参数。
2、托管字符串String与非托管字符串char *的转换方法,可以参考以下代码:
IntPtr iFilename=Marshal::StringToHGlobalAnsi(filename);
char *strFile= reinterpret_cast<char*>(static_cast<void*>(iFilename));
Marshal类是联系托管和非托管平台之间巨牛无比的类,有很多方法,大家可以看看MSDN,上面的资料很好。
反过来可以这样:
char* s1 = "native string1";
wchar_t* s2 = L"native string2";
String^ str1 = gcnew String( s1 );
String^ str2 = gcnew String( s2 );
3、在托管和非托管的内存缓冲间复制数据,可以参考以下代码:
cli::array<BYTE>^ bmpBytes2 =gcnew array<BYTE>(bfSize); //托管内存
pin_ptr<BYTE> p2 = &bmpBytes2[0];//非托管内存
::memcpy(p2, pMyBmp,Length * sizeof(BYTE) );
你确实没有看错,就是memcpy!
4、除了SYSTEM命名空间外,其他的名称空间除了要使用using namespace语句声明外,还要用using包含它的dll文件!如要用上Drawing名称空间,不仅要using namespace System::Drawing; ,还需要#using <system.drawing.dll>,不然编译时就会报错。
5、调试时打开C#的混合调试和C++的混合调试选项,就可以在同时对托管和非托管平台的代码进行调试了。