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进行互操作的方法和技巧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/874885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

外六角半螺纹螺丝主要应用领域

外六角半螺纹螺丝&#xff0c;作为一种常见的紧固件&#xff0c;因其独特的设计和多样的功能而在多个工业领域中占据着重要的地位。这种螺丝的一端具有完整的螺纹&#xff0c;而另一端则可能没有螺纹或螺纹较短&#xff0c;这样的设计使其在某些应用场景中具有独特的优势。 应用…

docker文件挂载和宿主主机文件的关系

一、背景 在排查docker日志时发现在读取docker的文件时找不到文件&#xff0c;在宿主主机上可以查到对应的文件。这里就要理解docker文件目录和宿主主机上的文件的关系。 二、Docker文件系统和宿主系统 Docker文件和宿主文件之间的关系主要体现在Docker容器的运行环境中。Docke…

【目录】8051汇编与C语言系列教程

8051汇编与C语言系列教程 作者将狼才鲸创建日期2024-07-23 CSDN文章地址&#xff1a;【目录】8051汇编与C语言系列教程本Gitee仓库原始地址&#xff1a;才鲸嵌入式/8051_c51_单片机从汇编到C_从Boot到应用实践教程 一、本教程目录 序号教程名称简述教程链接1点亮LCD灯通过IO…

【分布式锁】Redission实现分布式锁

接着上一节&#xff0c;我们遇到了超卖的问题&#xff0c;并通过Redis实现分布式锁&#xff0c;进行了解决。本节 我将换一种方式实现分布式锁。 前提&#xff1a; nginx、redis、nacos 模块1&#xff1a; provider-and-consumer 端口 8023 模块2 rabbitmq-consumer 端口 8021 …

成都云飞浩容文化传媒有限公司电商服务新领航者

在电商的海洋里&#xff0c;无数企业如同繁星般闪烁&#xff0c;但其中有一颗璀璨的明星——成都云飞浩容文化传媒有限公司&#xff0c;以其专业的电商服务和独特的创新理念&#xff0c;成为行业的佼佼者。今天&#xff0c;就让我们一起走进云飞浩容&#xff0c;探索这家公司的…

# Redis 入门到精通(八)-- 服务器配置-redis.conf配置与高级数据类型

Redis 入门到精通&#xff08;八&#xff09;-- 服务器配置-redis.conf配置与高级数据类型 一、redis 服务器配置–redis.conf 配置 1、服务器端设定 1&#xff09;设置服务器以守护进程的方式运行&#xff1a; daemonize yes|no 2&#xff09;绑定主机地址&#xff1a; bin…

【防火墙】防火墙NAT、智能选路综合实验

实验拓扑 实验要求 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 8&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 9&#xff0c;多出口环境基于带宽比例进行选路…

自动驾驶AVM环视算法–更新超广角视图算法和exe测试demo

1、测试环境 opencv310vs2022 2、使用的编程语言 c和c 3、测试的demo的获取 更新&#xff1a;测试的exe程序&#xff0c;无需解压码就可以体验算法测试效果 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1ndDcJ6VwexZoLUyUXlwLwg 提取码&#xff1a;q1…

python-爬虫实例(1):获取京东商品评论

目录 前言 道路千万条&#xff0c;安全第一条 爬虫不谨慎&#xff0c;亲人两行泪 获取京东商品评论信息 一、实例示范 二、爬虫四步走 1.UA伪装 2.获取Url 3.发送请求 4.获取响应数据进行解析并保存 总结 前言 道路千万条&#xff0c;安全第一条 爬虫不谨慎&#xff0c;…

手机怎么恢复回收站清空的照片?2个技巧大公开(适用各类安卓机)

站在时间的边缘&#xff0c;凝视着那些即将消逝的回忆&#xff0c;心里的惆怅不断增加。我们到底该怎么恢复回收站删除的照片呢&#xff1f;请相信&#xff0c;这些回忆并未真正消失。 无论你是因为误操作还是其他原因导致回收站被清空&#xff0c;这篇文章都将为你提供有力的…

Centos7_Minimal安装Cannot find a valid baseurl for repo: base/7/x86_6

问题 运行yum报此问题 就是没网 解决方法 修改网络信息配置文件&#xff0c;打开配置文件&#xff0c;输入命令&#xff1a; vi /etc/sysconfig/network-scripts/ifcfg-网卡名字把ONBOOTno&#xff0c;改为ONBOOTyes 重启网卡 /etc/init.d/network restart 网路通了

慎用Float和Double进行浮点运算

背景 在之前的开发过程中, 遇到了一些小问题. 就是在某功能计算时, 按照当时的设想是需要保留两位小数并向下取整. 当时没有太好的思路, 于是请教了好朋友gpt同志. 而gpt给出3种思路: 使用String.format方法 double value 123.456789; String formattedString String.forma…

Redis从入门到超神-(五)Redis实现分布式锁原理

引言 什么是分布式锁&#xff1f; 分布式锁是分布式系统中用于控制多个进程或线程对共享资源的访问的一种机制。在分布式系统中&#xff0c;由于存在多个服务实例或节点&#xff0c;它们可能会同时尝试访问或修改同一份数据或资源。如果没有适当的同步机制&#xff0c;就可能导…

labview实现两台电脑共享变量传输及同步

因为工作需要&#xff0c;需要实现多台主机间进行数据传输&#xff0c; 有两个备选方案&#xff0c; 1&#xff1a;建立tcp&#xff0c;然后自己解包 2&#xff1a;就是通过共享变量传输 虽然共享变量也是建立在TCP/IP上面的&#xff0c;但是不用自己解包呀 关于共享变量网络上…

mysql面试(四)

前言 本章节有些长&#xff0c;主要的篇幅是介绍缓存页的算法&#xff0c;如何快速的定位哪些是没有用过的&#xff0c;哪些是用过的&#xff0c;哪些是要淘汰掉的。 建议可以阅读一下这里面LRU算法相关的内容&#xff0c;和很多组件里面基本原理都是想通的&#xff0c;比如re…

聊聊 C# 中的顶级语句

前言 在 C# 9.0 版本之前&#xff0c;即使只编写一行输出 “Hello world” 的 C# 代码&#xff0c;也需要创建一个 C# 类&#xff0c;并且需要为这个 C# 类添加 Main 方法&#xff0c;才能在 Main 方法中编写代码。从 C# 9.0 开始&#xff0c;C# 增加了 “顶级语句” 语法&…

redis原理之底层数据结构-跳表

1.什么是跳表 1.1 链表及其不足 链表是在程序设计中最常见的数据结构之一&#xff0c;它通过指针将多个链表节点连接起来&#xff0c;这样就可以将逻辑上同一类的数据存储到不连续的内存空间上。链表结构如下&#xff1a; 但是链表有一个问题&#xff0c;就是当链表需要查询一…

3.1 FreeRTOS详细移植步骤(自己的实操)

[TOC](3.1 FreeRTOS详细移植步骤(自己的实操)) 自己使用阿波罗F767的内存管理实验和定时器实验&#xff0c;进行复刻。 FreeRTOS源码版本是FreeRTOS 202212.01。官网和Github都有下载。 按照STM32F767FreeRTOS开发手册V1.1进行移植复刻。 注:这个开发手册不是开发指南。跟视频里…

关于Qt部署CMake导致“Failed to set working directory to”的问题

2024年7月23日补充&#xff1a;该目录过深的情况只在Win10上有发现&#xff0c;Win11则没有问题&#xff0c;且Win11可以在DevHome中设置LongPath。 --------------------------------------------------------------------------------------------------------------- 使用qt…

Spark_Oracle_II_Spark高效处理Oracle时间数据:通过JDBC桥接大数据与数据库的分析之旅

接前文背景&#xff0c; 当需要从关系型数据库&#xff08;如Oracle&#xff09;中读取数据时&#xff0c;Spark提供了JDBC连接功能&#xff0c;允许我们轻松地将数据从Oracle等数据库导入到Spark DataFrame中。然而&#xff0c;在处理时间字段时&#xff0c;可能会遇到一些挑战…