C#调用C++DLL库的两种方式
作为Windows开发系的从业者,经常会涉及C#到调用C++底层库的业务场景,或是第三方仅提供C++dll库,或是因性能要求自主编写的C++dll库。一般情况我们会有两种处理方式,第一种DLL直接导入,简单粗暴;第二种,CLR包装C++库。下面分开描述。
1.DLL直接导入
发现https://www.cnblogs.com/answercard/p/5002839.html文章中已经描述的非常详细。这里补充一点C#结构体给C++同名同结构的结构体传参的问题说明。
C++结构体定义(节选)如下:
struct RECT { int left; int right; int top; int bottom; RECT() { left = right = top = bottom = 0; } int Width() const { return right - left; } int Height() const { return bottom - top; } }; struct RECORD_INFO { char screen_file_name[1024]; BOOL is_screen_record_video; BOOL is_record_mic; RECT screen_video_capture_rect; int screen_video_dst_width; int screen_video_dst_height; int video_frame_rate; RECORD_INFO() { memset(screen_file_name, 0, 1024); is_screen_record_video = false; is_record_mic = false; screen_video_dst_width = 0; screen_video_dst_height = 0; } };
为了能在C#中传递该结构体,需要在C#中命名同名结构体,定义(节选)如下:
public struct RECORD_INFO { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] public byte[] screen_file_name;//屏幕视频保存文件全路径(含文件名)只支持MP4格式 public int is_screen_record_video;//是否开启记录屏幕视频 1为开启 public int is_record_mic;//是否开启记录麦克风音源 1为开启 public RECT screen_video_capture_rect;//屏幕录屏区域 public int screen_video_dst_width;//屏幕视频宽度 public int screen_video_dst_height;//屏幕视频高度 public int video_frame_rate;//所有视频帧率 }
这里需要说明的是结构体中定义的字段顺序应严格相同,结构体的内存空间是按整体占用分配空间,在64位系统下如上的C++结构体空间占用为1060,其中BOOL是int型typedef,sizeof(BOOL)=4。在C#传递结构体参数时,在C++中认为是把1060字节数据进行memcpy复制到RECORD_INFO的结构体中,如果C#定义时字段顺序不一致,将会导致在C++中字段值张冠李戴。
2.CLR语言包装
公共语言运行时(CLR)是一套完整的、高级的虚拟机,它被设计为用来支持不同的编程语言,并支持它们之间的互操作。使用CLR,需要了解它的语法,如何将C++程序混合在CLR中开发,直接上代码进行说明。
打开VS,新建一个项目,选择 C++ CLR类库项目,确定。

然后定义一个C++类 demo。demo.h内容如下:
#pragma once #include <string> namespace CLRDemos { class demo { public: demo(); int add(int param1,int param2); std::string preSubString(char* src,char splitChar); }; }
demo.cpp内容:
#include "stdafx.h" #include "demo.h" CLRDemos::demo::demo() { } int CLRDemos::demo::add(int param1, int param2) { return param1 + param2; } std::string CLRDemos::demo::preSubString(char * src, char splitChar) { std::string s = src; int loc = s.find_first_of(splitChar); return s.substr(0, loc); }
定义CLR类 ClrDemo,CLRTest.h内容如下:
// CLRTest.h #pragma once #include "demo.h" using namespace System; using namespace System::Runtime::InteropServices; using namespace System::Collections::Generic; using namespace System::Text; using namespace CLRDemos; namespace CLRTest { public ref class ClrDemo { private: demo* d; public: int Add(int p1, int p2); String^ PreSubString(String^ src, char splitChar); String^ PreSubString(String^ src, String^ splitChar); ClrDemo(); ~ClrDemo(); }; }
CLRTest.cpp内容:
// 这是主 DLL 文件。
#include "stdafx.h" #include "CLRTest.h" int CLRTest::ClrDemo::Add(int p1, int p2) { return d->add(p1,p2); } String ^ CLRTest::ClrDemo::PreSubString(String ^ src, char splitChar) { char* temp = (char*)(Marshal::StringToHGlobalAnsi(src)).ToPointer(); std::string s = d->preSubString(temp, splitChar); Marshal::FreeHGlobal(IntPtr(temp)); return gcnew String(s.c_str()); } String ^ CLRTest::ClrDemo::PreSubString(String ^ src, String^ splitChar) { char* temp = (char*)(Marshal::StringToHGlobalAnsi(src)).ToPointer(); char* c = (char*)(Marshal::StringToHGlobalAnsi(splitChar)).ToPointer(); std::string s = d->preSubString(temp, c[0]); Marshal::FreeHGlobal(IntPtr(temp)); Marshal::FreeHGlobal(IntPtr(c)); return gcnew String(s.c_str()); } CLRTest::ClrDemo::ClrDemo() { d = new demo(); } CLRTest::ClrDemo::~ClrDemo() { if(d!=nullptr) delete d; }
再新建一个C# 控制台程序,添加CLRTest引用。

在main函数中添加内容:
static void Main(string[] args) { CLRTest.ClrDemo t = new CLRTest.ClrDemo(); int total = t.Add(5, 15); string str = t.PreSubString("1234567_wqtet","_"); sbyte b = (sbyte)'_'; string str1 = t.PreSubString("12345_wqtet",b); }
编译执行,调试下即可发现完成C++函数调用。
参数传递时,基本数据类型传递无需进行类型转换,例如调用Add方法时,int类型无需转换。在传递复杂类型如示例中给出的PreSubString两个重载函数中C#的字符串,转换为C++字符指针,调用了 (char*)(Marshal::StringToHGlobalAnsi(src)).ToPointer(),再完成调用后,需要Marshal::FreeHGlobal(IntPtr(temp))释放。
至此,两种方式讲述完毕,未尽之处,后面再补充。
知识从互联网来,经验分享给互联网。

浙公网安备 33010602011771号