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

news2024/12/29 9:15:48

在这里插入图片描述

欢迎来到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.coloradmin.cn/o/1945222.html

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

相关文章

轻松翻译,你值得拥有的PDF翻译工具分享

大家好&#xff0c;作为一名初入职场的小菜鸟&#xff0c;我发现了一个让我头疼不已的问题——那就是PDF文件的翻译。在全球化日益加剧的今天&#xff0c;我们经常会遇到需要阅读或者翻译外文PDF文件的情况。但PDF文件不同于Word或Excel&#xff0c;它通常不易直接编辑&#xf…

C语言 | Leetcode C语言题解之第278题第一个错误的版本

题目&#xff1a; 题解&#xff1a; int firstBadVersion(int n) {int left 1, right n;while (left < right) { // 循环直至区间左右端点相同int mid left (right - left) / 2; // 防止计算时溢出if (isBadVersion(mid)) {right mid; // 答案在区间 [left, mid] 中…

SWAT模型

原文链接&#xff1a;SWAT模型https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247610033&idx2&snc1da9be73550928ecc6a7a165dda3d9c&chksmfa826f56cdf5e640f891c6977cec1b2728ee2554ceacd224903df73d2dfedcd0c8b9ec047843&token91118244&langzh…

uniapp入门超详细教程:如何从零开始搭建项目

目录 一、介绍 二、环境搭建 2.1.需要下载的软件 2.1.1 HBuilderX 2.1.2 下载微信开发者工具 2.2 创建uniapp项目 2.2.1 新建项目 2.2.2 项目基本结构 2.2.3 在微信开发者工具上运行 2.2.4 发布微信小程序 三、pages.json 页面路由 四、组件 4.1 视图容器 4.1.1 v…

超分AI模型学习

概述 超分&#xff08;超分辨率&#xff1a;Super Resolution&#xff0c;SR&#xff09;&#xff1a;是计算机视觉和图像处理领域的一个热门话题。主要是将低分辨率图像恢复出高分辨率图像。可以采用的方法和手段很多&#xff0c;最近项目中有涉及&#xff08;红外成像的超分处…

Axivion Suite 7.8现已发布

现已实现100%覆盖MISRA规则&#xff0c;并加入了高级功能来提高代码分析能力。 我们很高兴地宣布Axivion Suite 7.8发布。全新版本的Axivion Suite对编译器、配置、分析、仪表板 (WebUI)和IDE插件的架构验证和静态代码分析功能均进行了升级。 100%覆盖所有可测试的MISRA规则 …

centos系统mysql集群复制双主双从

文章目录 MySQL 双主双从集群一、 准备环境二、 配置主服务器1. 配置 MySQL 主服务器 1 (192.168.1.1)2. 配置 MySQL 主服务器 2 (192.168.1.2) 三、配置从服务器1. 配置 MySQL 从服务器 1 (192.168.1.3)2. 配置 MySQL 从服务器 2 (192.168.1.4)3. 在主服务器 1 上配置复制到主…

使用php adodb5连接人大金仓数据库

打开php中的pgsql扩展 extensionpgsql使用adodb5连接kingbase数据库 <?php include("adodb5/adodb.inc.php"); $fox_dbtype pgsql; $fox_host 192.168.1.66; $fox_user system; $fox_pwd 123456; $fox_dbname testkingbase; $fox_port 54321;$dbNewADOCo…

【实战】Spring Cloud Stream3.0 整合RocketMq

文章目录 前言技术积累Spring Cloud Stream3.0新特性RocketMq简介 实战演示引入Maven依赖增加application配置消息生产者消息消费者 前言 相信很多同学用使用过rocketmq消息中间件&#xff0c;且大多情况下是使用原生的rocketmq-spring-boot-starter 进行集成然后创建一个rock…

Unity显示泰语且兼容泰语音标

前言&#xff1a;使用Unity开发的游戏需要支持泰语本地化&#xff0c;以及解决显示泰语时Unity的bug 目录 1、Text组件显示泰语2、TextMeshPro组件显示泰语 现在很多游戏都需要显示泰语&#xff0c;下面将介绍Unity如何显示泰语&#xff0c;&#xff08;仅介绍Unity字体方面的设…

Transformer自然语言处理实战pdf阅读

一.第一章 欢迎来到transformer的世界 1.解码器-编码器框架 在Transformer出现之前&#xff0c;NLP的最新技术是LSTM等循环架构。这些架 构通过在神经网络连接使用反馈循环&#xff0c;允许信息从一步传播到另一 步&#xff0c;使其成为对文本等序列数据进行建模的理想选择。如…

多表查询时条件写在where和join on的区别

文章目录 一、初始数据二、问题分析三、总结 先说结论&#xff0c; 1.如果想要拿到主表不受到关联表查询条件的数据的话&#xff0c;那么建议直接将查询条件放到on之后。 2.如果将关联表的条件查询放在where之后&#xff0c;可能会将主表中的数据进行排除。 所以如果想实现的查…

总结一些vue3小知识3

1.限制时间选择器只能选择后面的日期 说明&#xff1a;disabled-date属性是一个用来判断该日期是否被禁用的函数&#xff0c;接受一个 Date 对象作为参数。 应该返回一个 Boolean 值。 <el-date-picker class"w180" v-model"datas.form.timeDate[0]" …

6.6 使用dashboard商城搜索导入模板

本节重点介绍 : 模板商城中搜索模板导入模板修改模板 大盘模板商城地址 免费的 地址 https://grafana.com/grafana/dashboards 搜索模板技巧 详情 导入dashboard 两种导入模式 url导入id导入json文件导入 导入 node_exporter模板 https://grafana.com/grafana/dashboa…

萤石举办2024夏季新品发布会,全力推进“2+5+N”智能家居新生态

7月24日&#xff0c;“智动新生&#xff0c;尽在掌控”2024萤石夏季新品发布会在杭州成功举办。本次发布会上&#xff0c;“智慧生活守护者”萤石深入挖掘应用场景&#xff0c;重磅发布了包括智能健康手表、智能家居AI主机、生态控制器、智家APP等多款创新性的产品及应用&#…

短视频时代,云微客AI批量混剪技术有多厉害?

在数字媒体日益盛行的今天&#xff0c;视频剪辑已经成为了一种热门的技能&#xff0c;加上短视频创作在当下也变得越来越流行&#xff0c;因此云微客短视频AI批量剪辑就显得非常必要。近些年&#xff0c;随着人工智能技术的发展&#xff0c;在很大程度上简化了批量剪辑的过程&a…

linux之网络子系统-本机发包到本机 实现

一、前言 在linux之网络子系统-网络协议栈 发包收包详解-CSDN博客 文章中&#xff0c;详细介绍了跨主机之间的数据包发送的源码流程。除了跨主机&#xff0c;还有本机发包到本机是如何实现的&#xff1f;就是 saddr ip地址为 127.0.0.1 . 二、发送数据包到 127.0.0.1 首先&a…

Go语言编程 学习笔记整理 第2章 顺序编程 前半部分

前言&#xff1a;《Go语言编程》编著 许式伟 吕桂华 等 1.1 变量 var v1 int var v2 string var v3 [10]int // 数组 var v4 []int // 数组切片 var v5 struct { f int } var v6 *int // 指针 var v7 map[string]int // map&#xff0c;key为string类型&#xff0c;value为in…

神经网络理论(机器学习)

motivation 如果逻辑回归的特征有很多&#xff0c;会造出现一些列问题&#xff0c;比如&#xff1a; 线性假设的限制&#xff1a; 逻辑回归是基于线性假设的分类模型&#xff0c;即认为特征与输出之间的关系是线性的。如果特征非常多或者特征与输出之间的关系是非线性的&#…

职场新人必备神器:四款PDF转Word在线转换工具大比拼

关于PDF文件格式转换这件事&#xff0c;其实已经变成了职场人都要会的基础技能了&#xff0c;那么要如何才能够快速且完成的PDF转换为Word呢&#xff1f;今天就让我用自己的毕生所学给大家说说四款pdf转word在线转换免费的工具吧&#xff0c;下面一起来了解一下吧。 一、福昕PD…