一、C#调用C++库
1、创建C++库
打开VisualStudio,创建一个C++工程,输入项目名称HelloWorldLib
确定,然后下一步。选择应用程序类型为DLL
单击完成,我们就创建好了一个C++库的项目。
这里为了方便,我们直接在HelloWorldLib.cpp里定义函数
C++库导出有两种方式
一、以C语言接口的方式导出
这种方法就是在函数前面加上extern "C" __declspec(dllexport)
加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
1 #include "stdafx.h"
2 #include<iostream>
3
4 extern "C" __declspec(dllexport) void HelloWorld(char* name);
5
6
7 extern "C" __declspec(dllexport) void HelloWorld(char* name)
8 {
9 std::cout << "Hello World " << name << std::endl;
10 }
二、以模块定义文件的方式导出
在工程上右键,选择添加-》新建项
然后选择代码-》模块定义文件
在Source.def中输入
LIBRARY
EXPORTS
HelloWorld
EXPORTS下面就是要导出的函数,这里不需要添加分号隔开,直接换行就行。
此时,我们函数的定义如下
1 #include "stdafx.h"
2 #include<iostream>
3
4 void HelloWorld(char* name);
5
6
7 void HelloWorld(char* name)
8 {
9 std::cout <<"Hello World "<< name << std::endl;
10 }
编译,生成dll。这里需要注意的是,如果生成是64位的库,C#程序也要是64位的,否则会报错。
2、使用C#调用
接下来我们新建一个C#控制台项目
打开前面C++库生成的目录,将HelloWorldLib.dll复制到C#工程的Debug目录下。也可以不复制,只需在引用dll的时候写上完整路径就行了。这里我是直接复制到Debug目录下
1 using System.Runtime.InteropServices;
2
3 namespace ConsoleApplication2
4 {
5 class Program
6 {
7 [DllImport("HelloWorldLib.dll")]
8 public static extern void HelloWorld(string name);
9
10 //可以通过EntryPoint特性指定函数入口,然后为函数定义别名
11
12 [DllImport("HelloWorldLib.dll", EntryPoint = "HelloWorld")]
13 public static extern void CustomName(string name);
14 static void Main(string[] args)
15 {
16 HelloWorld("LiLi");
17 //跟上面是一样的
18 CustomName("QiQi");
19 }
20 }
21 }
运行程序,结果如下:
这样就成功创建了一个C#可以调用的C++库
下面我们动态调用C++库,这里委托的作用就比较明显了。把委托比喻为C++的函数指针,一点也不为过。
我们在C++库中再新增一个函数GetYear(),用来获取当前年份。
1 int GetYear();
2
3 int GetYear()
4 {
5 SYSTEMTIME tm;
6 GetLocalTime(&tm);
7
8 return tm.wYear;
9 }
记得在导出文件中(Source.def)增加GetYear。编译,生成新的DLL
再新建一个C#控制台程序
代码如下:
1 using System;
2 using System.Runtime.InteropServices;
3
4 namespace ConsoleApplication3
5 {
6
7 class Program
8 {
9 [DllImport("kernel32.dll")]
10 public static extern IntPtr LoadLibrary(string lpFileName);
11
12 [DllImport("kernel32.dll")]
13 public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
14
15 [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
16 public static extern bool FreeLibrary(IntPtr hModule);
17
18 //声明委托,这里的签名,需要跟C++库中的对应
19 delegate int GetYearDelegate();
20
21 static void Main(string[] args)
22 {
23 GetYearDelegate m_fGetYear;
24 IntPtr hModule = LoadLibrary("HelloWorldLib.dll");
25 if(hModule != IntPtr.Zero)
26 {
27 IntPtr hProc = GetProcAddress(hModule, "GetYear");
28 if(hProc != IntPtr.Zero)
29 {
30 m_fGetYear = (GetYearDelegate)Marshal.GetDelegateForFunctionPointer(hProc, typeof(GetYearDelegate));
31
32 //在这里可以调用
33 int year = m_fGetYear();
34 Console.WriteLine("年份是:" + year);
35 }
36 }
37 }
38 }
39 }
运行结果:
好的,前面函数里面涉及的都是简单数据类型,下面来介绍一下复杂数据类型。这里指的是结构体
在C++库中定义一个GetDate()的函数,代码如下。这里也要记得在导出文件中添加(Source.def)
struct MyDate
{
int year;
int month;
int day;
};
MyDate GetDate();
MyDate GetDate()
{
SYSTEMTIME tm;
GetLocalTime(&tm);
MyDate md;
md.day = tm.wDay;
md.month = tm.wMonth;
md.year = tm.wYear;
return md;
}
新建一个C#控制台程序,完整代码如下
1 using System;
2 using System.Runtime.InteropServices;
3
4 namespace ConsoleApplication3
5 {
6 struct MyDate
7 {
8 public int Year;
9 public int Month;
10 public int Day;
11 }
12
13
14 class Program
15 {
16 [DllImport("kernel32.dll")]
17 public static extern IntPtr LoadLibrary(string lpFileName);
18
19 [DllImport("kernel32.dll")]
20 public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
21
22 [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
23 public static extern bool FreeLibrary(IntPtr hModule);
24
25 delegate IntPtr GetDateDelegate();
26
27 static void Main(string[] args)
28 {
29 GetDateDelegate m_fGetDate;
30 IntPtr hModule = LoadLibrary("HelloWorldLib.dll");
31
32 if (hModule != IntPtr.Zero)
33 {
34 IntPtr hProc = GetProcAddress(hModule, "GetDate");
35 if (hProc != IntPtr.Zero)
36 {
37 m_fGetDate = (GetDateDelegate)Marshal.GetDelegateForFunctionPointer(hProc, typeof(GetDateDelegate));
38 IntPtr ptr = m_fGetDate();
39 if(ptr != IntPtr.Zero)
40 {
41 MyDate md = (MyDate)Marshal.PtrToStructure(ptr, typeof(MyDate));
42 Console.WriteLine("{0}年-{1}月-{2}日",md.Year,md.Month,md.Day);
43 }
44 }
45 }
46 }
47 }
48 }
运行结果如下:
C#与C++互操作,很重要的一个地方就是,要注意数据类型的对应。有时还需要加上一些限制,
关于C#与C++数据类型对应
可以参考以下链接:
https://www.cnblogs.com/zjoch/p/5999335.html
大部分硬件厂商提供的SDK都是需要C++来调用的,有了上面的知识,使用C#来调用一些硬件的SDK就比较容易了。只需要使用C++再进行一次封装就行了。
二、C++调用C#库
这里用到是C++/CLI,就是如何用C++在·NET中编程。就是因为有这个东西的存在,C++才能调用C#的库
下面新建一个C#类库CSharpLib
这里我们使用C#封装一个读取XML节点的函数(仅供演示)
先创建一个XML文件,并保存为Student.xml
1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 <Student>
3 <Name>自由在高处</Name>
4 <Age>17</Age>
5 <Size>40</Size>
6 </Student>
在CSharpLib工程中创建一个XmlQuery类(包含XPathQuery和GetFirstStudent两个成员函数)和一个Student结构体,代码如下:
1 public class XmlQuery
2 {
3 private string fileName;
4 private XDocument doc;
5
6 public XmlQuery(string fileName)
7 {
8 this.fileName = fileName;
9 doc = XDocument.Load(fileName);
10 }
11
12 public XmlQuery()
13 {
14
15 }
16
17 public string XPathQuery(string xPath)
18 {
19 if (doc == null)
20 return "";
21
22 var result = doc.XPathSelectElement(xPath);
23
24 if (result == null)
25 return "";
26
27 return result.Value;
28 }
29
30 public Student GetFirstStudent()
31 {
32 if (doc == null)
33 return new Student();
34
35 var root = doc.Root;
36
37 Student student = new Student();
38 student.Name = root.Element("Name").Value;
39 student.Age = Convert.ToInt32( root.Element("Age").Value);
40 student.Size = Convert.ToInt32(root.Element("Size").Value);
41
42 return student;
43 }
44 }
45
46 public struct Student
47 {
48 /// <summary>
49 /// 需要限定长度,否则会转换失败
50 /// </summary>
51 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
52 public string Name;
53
54 /// <summary>
55 /// 基本数据类型注意封送时对应的类型即可
56 /// </summary>
57 public int Age;
58
59 public int Size;
60 }
然后我们创建一个C++控制台程序UseCSharpLib
创建完成后,将前面编译的CSharpLib.dll和Student.xml拷贝到编译目录下和代码目录下
然后到属性页里开启公共语言运行时支持
使用#using引用前面编译并复制到代码目录下的C#库。这里的相对路径是相对代码工程文件所在的位置
1 #using "CSharpLib.dll"
包含必要的头文件及引入命名空间
1 #include<msclr\marshal_cppstd.h>
2
3 using namespace System;
4 using namespace msclr::interop;
5 using namespace std;
6 using namespace CSharpLib;
使用gcnew实例化对象,并调用XmlQuery类的成员函数XPathQuery:
1 XmlQuery^ query = gcnew XmlQuery("Student.xml");
2 System::String ^str = query->XPathQuery("Student/Name");
XPathQuery函数返回类型是System.String类型,可以直接使用.Net中String类提供的功能,也可以转换成char*来进行下一步的操作
1 //直接使用System.String类中的属性和函数
2 System::Console::WriteLine(str);
3 System::Console::WriteLine(str->Length);
4
5 //将System.String转换成char*
6 System::IntPtr ptr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(str);
7 char* chData = (char *)(void *)ptr;
8
9 cout << chData << endl;
10 cout << strlen(chData) << endl;
11
12 //释放内存
13 System::Runtime::InteropServices::Marshal::FreeHGlobal(ptr);
运行结果如下:
GetFirstStudent函数返回了一个Student结构体,下面的代码演示了调用该函数后,将返回值转换为C++中的结构体。
首先在C++中定义一个结构体:
1 struct StudentCPP
2 {
3 char Name[256]; //大小要跟C#中的保持一致
4 int Age;
5 int Size;
6 };
调用GetFirstStudent
1 CSharpLib::Student stu = query->GetFirstStudent();
2
3 //可以直接操作stu
4 int age = stu.Age;
5 System::String^ name = stu.Name;
6 int size = stu.Size;
7
8 //也可以转换为C++中的结构体
9 System::IntPtr stuPtr = System::Runtime::InteropServices::Marshal::AllocHGlobal(System::Runtime::InteropServices::Marshal::SizeOf(stu));
10 System::Runtime::InteropServices::Marshal::StructureToPtr(stu, stuPtr,true);
11 StudentCPP* student = (StudentCPP*)(void*)stuPtr;
12
13 cout << "Name: " << student->Name << endl
14 << "Age: " << student->Age << endl
15 << "Size: " << student->Size << endl;
16
17 //释放内存
18 System::Runtime::InteropServices::Marshal::FreeHGlobal(stuPtr);
运行如果如下:
除了C++/CLI这种方试,还有两种方式可以实现C++调用C#,但是这里没做详细介绍了,使用前面的方法基本能满足工作需求了。
向 COM 公开 .NET Core 组件
可以参考以下的链接:
向 COM 公开 .NET Core 组件 - .NET | Microsoft Learn
编写自定义 .NET 主机以从本机代码控制 .NET 运行时
可以参考以下的链接:
编写自定义 .NET 运行时主机 - .NET | Microsoft Learn
说明:
1、需要注意C#和C++在进行互操作时的数据类型对应 。可以参考以下链接
https://www.cnblogs.com/zhaotianff/p/12896297.html
2、C#中的类和结构体可以转换成C++中的结构体,但要注意一些原则,可以参考以下链接
https://www.cnblogs.com/zhaotianff/p/12510286.html
https://www.cnblogs.com/zhaotianff/p/13300438.html
3、暂时只想到这两点
最后再附上示例代码,玩得愉快。