C#与C++互操作

devtools/2024/9/23 7:13:23/

一、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中输入

LIBRARYEXPORTS
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 ConsoleApplication24 {5     class Program6     {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 ConsoleApplication35 {6 7     class Program8     {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 ConsoleApplication35 {  6     struct MyDate7     {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 XmlQuery2     {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 //可以直接操作stu4 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、暂时只想到这两点

最后再附上示例代码,玩得愉快。


http://www.ppmy.cn/devtools/96797.html

相关文章

冰岛数据中心技术三巨头推出由可再生能源驱动的一体化云计算解决方案

冰岛通过国内生产的各种形式的可再生能源来满足其大部分能源需求。据三家开发新数据中心服务的公司称&#xff0c;这个北欧岛国也是关键任务云应用的理想环境。 Vespertec 公司、Sardina Systems 公司和 Borealis 公司共同开发了一种创新的 IT 解决方案&#xff0c;名为冰云综合…

浅谈Winform

一、Winform简介说明 C# 是一种面向对象的编程语言&#xff0c;由微软开发并作为.NET框架的主要编程语言。C# 设计时考虑了易用性&#xff0c;并且具有丰富的特性&#xff0c;如垃圾回收、异常处理、泛型、LINQ&#xff08;Language Integrated Query&#xff09;、异步编程等。…

【python爬虫】邮政包裹物流查询2瑞数6加密

大家好呀&#xff0c;我是你们的好兄弟【星云牛马】&#xff0c;今天给大家带来的是瑞数6的补环境的总结&#xff0c;补环境肯定是需要一些基础补环境知识的&#xff0c;所以建议没有基础的小伙伴可以加入学习群进行学习&#xff0c;有基础的伙伴加入交流起来。 QQ群&#xff…

网站自动化锚文本的实现逻辑

锚文本&#xff0c;‌即超链接的文本部分&#xff0c;‌它在网页中扮演着至关重要的角色。‌通过点击锚文本&#xff0c;‌用户可以方便地在网页间进行跳转&#xff0c;‌从而极大地提升了用户体验。‌同时&#xff0c;‌在搜索引擎优化&#xff08;‌SEO&#xff09;‌领域&am…

C++ 对C的扩展

作用域运算符 符号 :: 作用 1.当局部变量与全局变量重名时&#xff0c;区分局部变量与全局变量 变量名 局部变量 ::变量名 全局变量 2.指明使用的变量所属的命名空间 命名空间名::变量名 3.实现函数的声明与定义分离 如: namespace E{ void method();//函数…

C++ 11相关新特性(lambda表达式与function包装器)

目录 lambda表达式 引入 lambda表达式介绍 lambda表达式捕捉列表的传递形式 lambda表达式的原理 包装器 包装器的基本使用 包装器与重载函数 包装器的使用 绑定 C 11 新特性 lambda表达式 引入 在C 98中&#xff0c;对于sort函数来说&#xff0c;如果需要根据不同的比较方式实现…

PostgreSQL-03-入门篇-过滤数据

文章目录 1. WHEREWHERE 子句概述WHERE 子句示例1) 使用 WHERE 子句和等于运算符 () 示例2) 使用 WHERE 子句和 AND 运算符示例3) 使用 WHERE 子句和 OR 运算符示例4) 使用 WHERE 子句和 IN 运算符示例5) 使用 WHERE 子句和 LIKE 运算符示例6) 将 WHERE 子句与 BETWEEN 运算符一…

集师知识付费小程序搭建

在这个月&#xff0c;张校长依托知识付费小程序&#xff0c;巧妙融合了线上活动与线下实践&#xff0c;成功实现了十万元的收入。小程序内&#xff0c;我精心策划了一系列高质量的课程与直播讲座&#xff0c;涵盖热门领域与专业技能&#xff0c;吸引了大量求知若渴的学员。通过…