当前位置: 首页 > news >正文

C#与C++交互开发系列(三):深入探讨P/Invoke基础知识

在这里插入图片描述

欢迎来到C#与C++交互开发系列的第三篇。在这篇博客中,我们将深入探讨P/Invoke(Platform Invocation Services)的基础知识。P/Invoke是C#调用非托管代码的一种机制,能够让C#直接调用C++编写的动态链接库(DLL)中的函数。在这篇文章中,我们将介绍P/Invoke的基本概念、使用方法以及一些实际代码示例。

3.1 什么是P/Invoke?

在这里插入图片描述

P/Invoke(Platform Invocation Services)是一种在托管代码中调用非托管代码的服务。通过P/Invoke,C#可以直接调用C++编写的DLL中的函数,从而实现跨语言的互操作性。P/Invoke主要用于调用Windows API函数和其他非托管代码库。

P/Invoke 的核心在于能够让托管代码和非托管代码协同工作,使得开发者可以利用现有的非托管代码库,而不必重写这些功能。P/Invoke 通过引入元数据和属性,定义托管代码如何与非托管代码进行交互,从而简化了开发过程。

3.2 P/Invoke 的基本使用方法

3.2.1 关键步骤

P/Invoke的使用涉及到几个关键步骤:

  • 声明外部函数:在C#代码中使用[DllImport]属性声明要调用的C++函数。这个声明包含DLL的名称和函数的调用约定。
  • 编译C++代码:将C++代码编译为DLL,以供C#程序调用。
  • 调用函数:在C#代码中调用已声明的外部函数。

为了理解P/Invoke的基本使用方法,我们来看一个简单的示例,展示如何在C#中调用C++函数。

Step 1: 编写C++代码

首先,我们创建一个简单的C++库,包含一个求和函数。

// MyCppLibrary.cpp
extern "C" {__declspec(dllexport) int Add(int a, int b) {return a + b;}
}

Step 2: 编译C++代码

将上述C++代码编译成动态链接库(DLL)。在Visual Studio中创建一个新的C++项目,并将其配置为DLL项目。编译后会生成MyCppLibrary.dll文件。

Step 3: 在C#中调用C++函数

接下来,我们在C#项目中调用这个C++函数。创建一个新的C#控制台应用程序,并添加如下代码:

using System;
using System.Runtime.InteropServices;class Program
{// 声明C++函数[DllImport("MyCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]public static extern int Add(int a, int b);static void Main(){int result = Add(3, 4);Console.WriteLine($"3 + 4 = {result}");}
}

在这个示例中,我们使用了DllImport属性来声明C++函数,并指定了DLL的名称和调用约定。然后,在Main方法中调用了这个函数并输出结果。

3.2.2 DllImport的基本语法

using System.Runtime.InteropServices;
class Program
{[DllImport("user32.dll")]public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);static void Main(){MessageBox(IntPtr.Zero, "Hello, World!", "Hello Dialog", 0);}
}

在上面的示例中,我们通过DllImport特性引入了user32.dll中的MessageBox函数,并在Main方法中调用它。

3.2.3 DllImport的属性配置

在这里插入图片描述

DllImport 特性在 C# 中用于调用非托管代码时,提供了多种属性配置选项,用来精确控制调用的行为和参数。以下是DllImport特性常用的属性配置:

  1. EntryPoint
  • 用法:[DllImport(“library.dll”, EntryPoint = “FunctionName”)]
  • 功能:指定非托管函数在 DLL 中的名称,如果与 C# 中的函数名不同,可以使用该属性进行指定。
  1. CallingConvention
  • 用法:[DllImport(“library.dll”, CallingConvention = CallingConvention.Cdecl)]
  • 功能:指定调用约定,确保调用方和被调用方使用相同的调用约定(如Cdecl、StdCall等)。
  1. CharSet
  • 用法:[DllImport(“library.dll”, CharSet = CharSet.Unicode)]
  • 功能:指定字符集,确保正确地处理非托管函数中的字符串参数,有CharSet.Ansi和CharSet.Unicode两个选项。
  1. ExactSpelling
  • 用法:[DllImport(“library.dll”, ExactSpelling = true)]
  • 功能:指定是否精确匹配 DLL 中的函数名称。默认情况下,C# 会尝试匹配与方法名相同的入口点。
  1. SetLastError
  • 用法:[DllImport(“library.dll”, SetLastError = true)]
  • 功能:指示是否在调用失败时设置 Marshal.GetLastWin32Error,用来获取操作系统返回的错误码。
  1. PreserveSig
  • 用法:[DllImport(“library.dll”, PreserveSig = false)]
  • 功能:指定是否保留原始的签名。如果设置为 false,COM 互操作将把 HRESULT 返回值转换为异常。
  1. BestFitMapping
  • 用法:[DllImport(“library.dll”, BestFitMapping = false)]
  • 功能:指定是否启用最佳匹配映射规则来处理非托管函数中的 ANSI 字符串和 Unicode 字符串之间的转换。
  1. ThrowOnUnmappableChar
  • 用法:[DllImport(“library.dll”, ThrowOnUnmappableChar = true)]
  • 功能:指定是否在遇到无法映射的字符时抛出异常。

除了以上列举的常见属性外,DllImport 还支持其他一些配置选项,例如处理非托管类型、结构体布局等,这些选项可以根据具体需要来配置,以确保与非托管代码的良好互操作性和性能。

[DllImport("user32.dll", EntryPoint = "MessageBox", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

在这里插入图片描述

3.3 数据类型的映射

在使用P/Invoke时,正确地映射C#和C++的数据类型非常重要。以下是一些常见的数据类型映射:

C++ 类型C# 类型
intint
floatfloat
doubledouble
charsbyte
char*string
void*IntPtr
boolbool (C++11)

例如,C++中的int类型在C#中映射为int,而C++中的char*类型在C#中映射为string

3.4 常见错误及调试技巧

在使用P/Invoke时,常见错误包括DLL文件找不到、函数声明不正确、数据类型不匹配等。以下是一些调试技巧:

  1. 检查DLL路径:确保DLL文件放置在可访问的路径中,通常是C#项目的输出目录或系统路径中。
  2. 验证函数声明:确保C#中声明的函数签名与C++中定义的函数匹配,包括参数类型和调用约定。
  3. 使用调试工具:使用Visual Studio等调试工具,设置断点并检查变量值和内存地址,帮助定位问题。

3.5 代码示例

为了进一步展示P/Invoke的使用方法,我们来看一个更复杂的示例,包含字符串和结构体的传递。

Step 1: 编写C++代码

// MyCppLibrary.cpp
#include <cstring>extern "C" {__declspec(dllexport) const char* ConcatStrings(const char* str1, const char* str2) {static char result[256];strcpy(result, str1);strcat(result, str2);return result;}struct Point {int x;int y;};__declspec(dllexport) int AddPoints(Point p1, Point p2) {return (p1.x + p2.x) + (p1.y + p2.y);}
}

Step 2: 编译C++代码

将上述C++代码编译成动态链接库(DLL)。

Step 3: 在C#中调用C++函数

using System;
using System.Runtime.InteropServices;class Program
{// 声明C++函数[DllImport("MyCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]public static extern IntPtr ConcatStrings(string str1, string str2);[StructLayout(LayoutKind.Sequential)]public struct Point{public int x;public int y;}[DllImport("MyCppLibrary.dll", CallingConvention = CallingConvention.Cdecl)]public static extern int AddPoints(Point p1, Point p2);static void Main(){// 调用ConcatStrings函数IntPtr resultPtr = ConcatStrings("Hello, ", "World!");string result = Marshal.PtrToStringAnsi(resultPtr);Console.WriteLine(result);// 调用AddPoints函数Point p1 = new Point { x = 1, y = 2 };Point p2 = new Point { x = 3, y = 4 };int sum = AddPoints(p1, p2);Console.WriteLine($"Sum of points: {sum}");}
}

在这个示例中,我们展示了如何传递字符串和结构体。ConcatStrings函数返回一个C风格字符串,我们在C#中使用Marshal.PtrToStringAnsi将其转换为托管字符串。AddPoints函数接受两个结构体参数,我们在C#中定义了相应的结构体并传递给函数。
在这里插入图片描述

3.6 总结

在这篇博客中,我们介绍了P/Invoke的基本概念、使用方法以及常见的数据类型映射。通过实际代码示例,我们展示了如何在C#中调用C++函数,并处理字符串和结构体的传递。在下一篇博客中,我们将进一步探讨使用C++/CLI进行互操作的方法和技巧。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 大模型额外篇章三:vercel搭建openai中转服务器
  • sql注入前期准备(相关函数和原理)
  • 使用sqlalchemy查询mysql的JSON字段
  • 【体外诊断】ARM/X86+FPGA嵌入式计算机在免疫分析设备中的应用
  • 探索Perl的图形用户界面开发:工具、技巧与实践
  • 最新全新UI异次元荔枝V4.4自动发卡系统源码
  • Python 实现PDF和TIFF图像之间的相互转换
  • SpringBoot整合Elastic-Job 2.1.53版本任务调度,手动任务,动态添加任务演示
  • py3.7.4离线安装openpyxl等错误,无法安装openpyxl...
  • yolo5图片视频、摄像头推理demo
  • Unity UGUI 之 ScrollBar与ScrollView
  • HarmonyOS应用开发者高级认证,Next版本发布后最新题库 - 单选题序号2
  • 生成式人工智能之路,从马尔可夫链到生成对抗网络
  • 探索LLM世界:新手小白的学习路线图
  • 密码学
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • ES6 ...操作符
  • Java方法详解
  • Magento 1.x 中文订单打印乱码
  • nginx 负载服务器优化
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • Python中eval与exec的使用及区别
  • Redis字符串类型内部编码剖析
  • Vue 2.3、2.4 知识点小结
  • 前端之React实战:创建跨平台的项目架构
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 译自由幺半群
  • 智能合约开发环境搭建及Hello World合约
  • 如何用纯 CSS 创作一个货车 loader
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ‌分布式计算技术与复杂算法优化:‌现代数据处理的基石
  • #NOIP 2014#Day.2 T3 解方程
  • #每天一道面试题# 什么是MySQL的回表查询
  • (20)docke容器
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (Oracle)SQL优化技巧(一):分页查询
  • (TOJ2804)Even? Odd?
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • .NET企业级应用架构设计系列之开场白
  • @ComponentScan比较
  • @EnableConfigurationProperties注解使用
  • @PostConstruct 注解的方法用于资源的初始化
  • @Value获取值和@ConfigurationProperties获取值用法及比较(springboot)
  • []Telit UC864E 拨号上网
  • [1]-基于图搜索的路径规划基础
  • [2669]2-2 Time类的定义
  • [AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]
  • [C++]AVL树怎么转