C#程序如何调用C++ dll详细教程
作者:令狐掌门
在使用C#开发客户端时,有时需要调用C++ dll,本篇博客来介绍C#程序如何调用C++ dll。
一、创建C++ dll项目
首先使用VS2022创建C++ dll项目,具体步骤如下:
(1)选择Windows桌面向导,点击下一步, 取项目名,例如我的dll项目名是libMath
(2)选择动态项目,勾选导出符号
(3)编写动态代码,代码如下:
libMath.h
// 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 LIBMATH_EXPORTS // 符号编译的。在使用此 DLL 的 // 任何项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将 // LIBMATH_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的 // 符号视为是被导出的。 #ifdef LIBMATH_EXPORTS #define LIBMATH_API __declspec(dllexport) #else #define LIBMATH_API __declspec(dllimport) #endif // 此类是从 dll 导出的 class LIBMATH_API ClibMath { public: ClibMath(); int Add(int a, int b); int Sub(int a, int b); }; // 由于需要给C#调用,为了方便导出类,添加了函数进行导出,并且需要加extern "C" extern "C" { LIBMATH_API ClibMath* CreateMyClass(); LIBMATH_API void DeleteMyClass(ClibMath* obj); LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2); LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2); }
注意: 如果想导出C++类在C#中使用,由于语言语法差异,C++类在C#中无法使用,因为C++类通常包含成员函数、构造函数、析构函数等,而C#与C++在处理这些方面存在差异。一种可行的方法是在C++类中添加一些导出函数,这样它们可以通过C#调用。这些函数可以执行类的实例化、调用成员函数等操作。确保使用 extern “C” 以避免名称修饰, 因为C++函数在编译时,会在原有的函数名前后添加一些符号,例如add函数在编译后可能变成了@xxasd_sfdf_add_xxx之类的,但是使用extern "C"
后就是按照C语言的方式导出,函数名不变,例如我添加的一些类导出方法:
// 由于需要给C#调用,为了方便导出类,添加了函数进行导出,并且需要加extern "C" extern "C" { LIBMATH_API ClibMath* CreateMyClass(); LIBMATH_API void DeleteMyClass(ClibMath* obj); LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2); LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2); }
libMath.cpp
// libMath.cpp : 定义 DLL 的导出函数。 // #include "framework.h" #include "libMath.h" // 这是已导出类的构造函数。 ClibMath::ClibMath() { return; } int ClibMath::Add(int a, int b) { return a + b; } int ClibMath::Sub(int a, int b) { return a - b; } LIBMATH_API ClibMath* CreateMyClass() { return new ClibMath(); } LIBMATH_API void DeleteMyClass(ClibMath* obj) { delete obj; } LIBMATH_API int CallAdd(ClibMath* obj, int num1, int num2) { return obj->Add(num1, num2); } LIBMATH_API int CallSub(ClibMath* obj, int num1, int num2) { return obj->Sub(num1, num2); }
二、C#程序员调用C++ dll
生成dll后可以用命令拷贝到C#项目的exe目录,或者手动拷贝,然后在C#代码中使用import
导入即可,如下图:
代码如下:
/* C# 调用 C++ dll 将libMath.dll放到CSharpCallCppDLL/bin/Debug目录下 */ using System.Runtime.InteropServices; namespace CSharpCallCppDLL { internal class Program { private static IntPtr myClassInstance; // 定义C++类的实例,用于后面的调用 [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr CreateMyClass(); [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void DeleteMyClass(IntPtr obj); [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int CallAdd(IntPtr obj, int num1, int num2); [DllImport("libMath.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int CallSub(IntPtr obj, int num1, int num2); static void Main(string[] args) { Console.WriteLine("测试C#调用C++"); myClassInstance = CreateMyClass(); int nRet = CallAdd(myClassInstance, 1, 2); Console.WriteLine($"1 + 2 = {nRet}"); // 清理C++内存 DeleteMyClass(myClassInstance); } } }
注意:由于C++的编译特点,C++项目有Debug、Release、x86、x64之分,特别需要注意dll的放置位置,放错了可能就链接失败了,以及C++在x64于x86下的指针4字节与8字节的区分,这些都会导致在C#调用C++ dll代码失败或者crash。此外,确保你的应用程序具有访问和加载DLL所需的适当权限。
运行结果如下:
在C#中可以创建一个对应C++类的C#包装类,使用 DllImport 属性声明导出函数,例如下面的代码:
public class MyClassWrapper { private IntPtr myClassInstance; [DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr CreateMyClass(); [DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void DeleteMyClass(IntPtr obj); [DllImport("YourCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void CallMyMethod(IntPtr obj); public MyClassWrapper() { myClassInstance = CreateMyClass(); } ~MyClassWrapper() { DeleteMyClass(myClassInstance); } public void MyMethod() { CallMyMethod(myClassInstance); } }
使用C#包装类:
在C#中,可以实例化MyClassWrapper类,并调用其中的方法,这将转发调用到C++类的实例。
MyClassWrapper myInstance = new MyClassWrapper(); myInstance.MyMethod();
这是一个简单的例子,实际情况可能更为复杂,特别是在处理类的继承、虚函数等方面。确保在进行实际应用时进行充分的测试,以确保互操作性正常运作。
三、C++与C#数据类型对应
C#在调用C++ DLL时,需要通过P/Invoke技术来完成。P/Invoke是.NET Framework用于调用非托管代码库的一种方式。在这个过程中,我们需要处理两种语言之间的数据类型转换,因为它们的数据类型不完全一致。
基本数据类型对应表
以下是C++和C#之间的一些常见数据类型的对应表(请注意,这并不是一个完全的列表,只是一些常见类型的示例):
C++ | C# |
---|---|
bool | bool |
char / BYTE | byte |
short | short |
int | int |
long | int |
float | float |
double | double |
char* (C-style string) | string |
wchar_t* (Unicode string) | string |
在C#中,我们使用DllImport
特性来声明对C++ DLL的函数调用。例如,如果我们有一个C++函数如下:
extern "C" __declspec(dllexport) int Add(int a, int b);
在C#中,我们可以这样声明和使用它:
[DllImport("MyLibrary.dll")] public static extern int Add(int a, int b); public void Main() { int result = Add(2, 3); }
对于更复杂的数据类型,如结构体和类,我们需要在C#中创建对应的类或结构体来匹配。例如,如果我们有一个C++结构体:
struct Person { char* name; int age; };
我们可以在C#中创建一个类来匹配它:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class Person { public string name; public int age; }
注意我们使用了StructLayout
特性并设置CharSet
为CharSet.Ansi
,因为C++中的char*
类型对应于ANSI字符串。
在处理C++ DLL中的函数时,也需要注意函数调用的约定(cdecl
,stdcall
等)。默认情况下,C#假定被调用的函数使用stdcall
调用约定。如果函数使用不同的调用约定,你需要在DllImport
特性中指定它,例如:
[DllImport("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
在处理更复杂的情况,如回调函数,复杂的数据结构,和C++类时,可能需要更多的处理。处理这些情况通常需要对两种语言都有深入的理解,并且可能需要手动管理内存。
C++指针类型与C#类型
C++ 指针在C#中的相应类型取决于指针指向的数据类型和你如何打算使用它。这里有几种常见的情况:
指向简单类型的指针:如果指针指向一个简单的数值类型(如
int*
、float*
等),你可以在C#中使用IntPtr
类型来表示它。你可以使用Marshal
类中的方法(如Marshal.ReadInt32
、Marshal.WriteInt32
等)来读取或写入指针指向的数据。指向字符串的指针:如果指针指向一个字符串(如
char*
或wchar_t*
),你可以在C#中使用string
类型来表示它。在DllImport
特性中,你可以使用MarshalAs
特性来指定字符串的编码方式。指向结构体的指针:如果指针指向一个结构体,你可以在C#中创建一个对应的类或结构体,并使用
ref
关键字或者IntPtr
类型来表示指针。使用ref
关键字时,.NET运行时会自动处理内存管理。使用IntPtr
时,你需要手动管理内存。
例如,假设你有一个C++函数如下:
extern "C" __declspec(dllexport) void ModifyPerson(Person* person);
在C#中,你可以这样使用它:
[DllImport("MyLibrary.dll")] public static extern void ModifyPerson(ref Person person); // 或者 [DllImport("MyLibrary.dll")] public static extern void ModifyPerson(IntPtr personPtr);
- 指向数组的指针:如果指针指向一个数组,你可以在C#中使用数组类型来表示它。你也可以使用
MarshalAs
特性来指定数组的大小。
对于更复杂的情况,例如指向函数的指针(函数指针),你可能需要使用Marshal.GetDelegateForFunctionPointer
方法将其转换为C#委托。
需要注意的是,当你使用IntPtr
来管理指针时,你需要自己负责内存的分配和释放。你可以使用Marshal.AllocHGlobal
和Marshal.FreeHGlobal
方法来分配和释放非托管内存。
总结
到此这篇关于C#程序如何调用C++ dll的文章就介绍到这了,更多相关C#调用C++ dll内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!