作为Windows开发系的从业者,经常会涉及C#到调用C++底层库的业务场景,或是第三方仅提供C++dll库,或是因性能要求自主编写的C++dll库。一般情况我们会有两种处理方式,第一种DLL直接导入,简单粗暴;第二种,CLR包装C++库。下面分开描述。

1.DLL直接导入


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#调用C++DLL库的两种方式_字段

然后定义一个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引用。

C#调用C++DLL库的两种方式_#include_02

在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))释放。

至此,两种方式讲述完毕,未尽之处,后面再补充。

知识从互联网来,经验分享给互联网。