DllImport进阶:参数配置与高级主题探究

深入讨论DllImport属性的作用和配置方法

在基础篇中,我们已经简单介绍了DllImport的一些属性。现在我们将深入探讨这些属性的实际应用。

1. EntryPoint

EntryPoint属性用于指定要调用的非托管函数的名称。如果托管代码中的函数名与非托管代码中的函数名不同,可以使用这个属性。例如:

[DllImport("user32.dll", EntryPoint = "MessageBoxW")]
public static extern int ShowMessage(IntPtr hWnd, String text, String caption, uint type);

在这个例子中,我们将非托管函数MessageBoxW映射到托管函数ShowMessage。

2. CallingConvention

CallingConvention属性指定调用约定,它定义了函数如何接收参数和返回值。常见的调用约定包括:

  • CallingConvention.Cdecl:调用者清理堆栈,多用于C/C++库。
  • CallingConvention.StdCall:被调用者清理堆栈,Windows API常用。
  • CallingConvention.ThisCall:用于C++类方法。
  • CallingConvention.FastCall:用于快速调用,较少使用。

示例:

[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern bool Beep(uint dwFreq, uint dwDuration);

3. CharSet

CharSet属性用于指定字符串的字符集,影响字符串的处理和传递方式。主要选项有:

  • CharSet.Ansi:将字符串作为ANSI编码传递。
  • CharSet.Unicode:将字符串作为Unicode编码传递。
  • CharSet.Auto:根据平台自动选择ANSI或Unicode。

示例:

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

4. SetLastError

SetLastError属性指定是否在调用非托管函数后调用GetLastError。设置为true时,可以使用Marshal.GetLastWin32Error获取错误代码。

示例:

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);public void CloseResource(IntPtr handle)
{if (!CloseHandle(handle)){int error = Marshal.GetLastWin32Error();// 处理错误}
}

5. ExactSpelling

ExactSpelling属性指定是否精确匹配入口点名称。默认情况下,CharSet影响名称查找,设置为true时,关闭字符集查找。

示例:

[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);

6. PreserveSig

PreserveSig属性指定是否保留方法签名的HRESULT返回类型。默认值为true。当设置为false时,HRESULT会转换为异常。

示例:

[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoCreateGuid(out Guid guid);

7. BestFitMapping 和 ThrowOnUnmappableChar

BestFitMapping属性控制是否启用ANSI到Unicode的最佳映射。ThrowOnUnmappableChar指定是否在遇到无法映射的字符时抛出异常。

示例:

[DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern bool SetEnvironmentVariable(string lpName, string lpValue);

实践示例

下面是一个综合使用多个DllImport属性的示例:

using System;
using System.Runtime.InteropServices;class Program
{[DllImport("user32.dll", EntryPoint = "MessageBox", CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.StdCall)]public static extern int ShowMessageBox(IntPtr hWnd, String text, String caption, uint type);static void Main(){int result = ShowMessageBox(IntPtr.Zero, "Hello, World!", "Hello Dialog", 0);if (result == 0){int error = Marshal.GetLastWin32Error();Console.WriteLine($"Error: {error}");}}
}

在这个例子中,我们使用了EntryPoint、CharSet、SetLastError和CallingConvention属性来精确配置MessageBox函数的调用。

深入理解和正确配置DllImport属性可以帮助我们更高效地调用非托管代码,确保数据类型和调用约定的匹配,处理潜在的错误和异常,提升代码的稳定性和安全性。

探讨数据类型匹配的重要性

在C#中通过DllImport调用非托管代码时,数据类型的匹配是确保代码正确执行的关键因素之一。正确的数据类型匹配能够避免数据损坏、内存泄漏和程序崩溃等问题。

1. 数据类型匹配的重要性

  • 避免数据损坏:非托管代码和托管代码的数据类型必须一致,否则传递的数据可能会损坏。例如,将一个32位的整数传递给一个预期为64位整数的非托管函数会导致数据截断或损坏。
  • 防止程序崩溃:不匹配的数据类型可能会导致非托管代码访问非法内存地址,进而导致程序崩溃。
  • 确保数据完整性:正确的数据类型匹配可以确保数据在托管代码和非托管代码之间正确传递,保持数据的完整性。
  • 提高代码安全性:数据类型的不匹配可能会引入安全漏洞,导致潜在的缓冲区溢出等安全问题。

2. 基本数据类型的匹配

基本数据类型在托管代码和非托管代码之间的匹配非常重要。以下是常见数据类型的匹配示例:

  • 整数类型
    • C#中的int通常对应C/C++中的int或LONG类型:
[DllImport("Example.dll")] 
public static extern int Add(int a, int b);
  • 无符号整数类型
    • C#中的uint通常对应C/C++中的unsigned int或DWORD类型:
[DllImport("Example.dll")]
public static extern uint GetTickCount();
  • 长整数类型
    • C#中的long对应C中的long long或__int64类型:
[DllImport("Example.dll")] 
public static extern long Multiply(long a, long b);
  • 指针类型
    • C#中的IntPtr或UIntPtr对应C中的指针类型,如void*或HANDLE:
[DllImport("Example.dll")] 
public static extern IntPtr OpenHandle(uint access);
  • 布尔类型
    • C#中的bool对应C中的BOOL类型,需要注意的是,C/C++中的BOOL通常定义为int,而C#中的bool是1字节。
[DllImport("Example.dll")] [return: MarshalAs(UnmanagedType.Bool)] 
public static extern bool CloseHandle(IntPtr handle);

3. 复杂数据类型的匹配

对于结构体、数组和字符串等复杂数据类型的匹配,需要特别注意。

  • 结构体
    • 结构体需要在托管代码和非托管代码中保持一致,并使用StructLayout属性进行布局控制:
[StructLayout(LayoutKind.Sequential)] 
public struct Point { public int X; public int Y; } [DllImport("Example.dll")] 
public static extern void GetPoint(out Point p);
  • 数组
    • 数组的匹配需要使用MarshalAs属性指定数组的类型和大小:
[DllImport("Example.dll")]
public static extern void FillArray([MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] int[] array);
  • 字符串
    • 字符串的匹配需要注意字符集的选择(如CharSet.Ansi或CharSet.Unicode):
[DllImport("Example.dll", CharSet = CharSet.Unicode)] 
public static extern void PrintMessage(string message);

4. 数据类型匹配的常见问题及解决方法

  • 字符集不匹配:在传递字符串时,如果字符集不匹配,可能会导致字符串被截断或乱码。解决方法是在DllImport特性中明确指定字符集:
[DllImport("Example.dll", CharSet = CharSet.Unicode)] 
public static extern void PrintMessage(string message);
  • 指针类型不匹配:非托管代码中的指针类型应对应C#中的IntPtr或UIntPtr:
[DllImport("Example.dll")] 
public static extern IntPtr AllocateMemory(uint size);
  • 结构体布局不匹配:如果结构体在内存中的布局不同,可能会导致数据损坏。解决方法是使用StructLayout属性确保一致的内存布局:
[StructLayout(LayoutKind.Sequential)] 
public struct Point { public int X; public int Y; }
  • 数组边界问题:传递数组时,应确保数组的大小匹配,避免越界访问:
[DllImport("Example.dll")] 
public static extern void ProcessArray([MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] int[] array);

讨论内存管理的重要性

在调用非托管代码时,内存管理是一个不可忽视的重要环节。非托管代码不受.NET垃圾回收器的管理,因此需要开发人员手动分配和释放内存。这不仅涉及到如何正确使用内存,还包括如何避免内存泄漏和其他潜在问题。

1. 内存管理的重要性

  • 防止内存泄漏:手动分配的内存如果不正确释放,会导致内存泄漏,逐渐消耗系统资源。
  • 确保数据安全:未正确管理的内存可能会被覆盖或误用,导致数据损坏和程序崩溃。
  • 提高程序性能:高效的内存管理能够减少内存使用,提升程序性能。

2. 内存分配和释放

在非托管代码中,内存通常使用malloc、calloc等函数分配,并使用free函数释放。在托管代码中,我们可以使用Marshal类提供的方法来分配和释放非托管内存。

  • 分配内存Marshal.AllocHGlobal:分配指定字节数的非托管内存。Marshal.AllocCoTaskMem:分配任务内存,适用于COM互操作。
IntPtr ptr = Marshal.AllocHGlobal(100); // 分配100字节的内存
// 使用ptr进行操作
Marshal.FreeHGlobal(ptr); // 释放内存
  • 释放内存使用Marshal.FreeHGlobal或Marshal.FreeCoTaskMem释放之前分配的内存。
IntPtr ptr = Marshal.AllocCoTaskMem(100);
// 使用ptr进行操作
Marshal.FreeCoTaskMem(ptr); // 释放内存

3. 内存拷贝

在托管代码和非托管代码之间传递数据时,可能需要进行内存拷贝。Marshal类提供了一些方法用于内存拷贝:

  • Marshal.Copy:用于从托管数组复制到非托管内存,或从非托管内存复制到托管数组。
  • Marshal.StructureToPtr:将托管结构复制到非托管内存。
  • Marshal.PtrToStructure:将非托管内存的数据复制到托管结构。
int[] managedArray = new int[10];
IntPtr unmanagedArray = Marshal.AllocHGlobal(managedArray.Length * sizeof(int));Marshal.Copy(managedArray, 0, unmanagedArray, managedArray.Length);
// 使用unmanagedArray进行操作
Marshal.Copy(unmanagedArray, managedArray, 0, managedArray.Length);Marshal.FreeHGlobal(unmanagedArray);

4. 处理非托管资源

调用非托管代码时,可能会使用非托管资源(如文件句柄、窗口句柄等),这些资源也需要正确管理以避免资源泄漏。

  • 关闭句柄使用CloseHandle或类似的API来关闭非托管资源。
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);public void CloseResource(IntPtr handle)
{if (!CloseHandle(handle)){int error = Marshal.GetLastWin32Error();// 处理错误}
}

5. 管理生命周期

对于需要频繁分配和释放内存的操作,可以考虑封装内存管理逻辑,确保内存能够正确释放。

public class UnmanagedBuffer : IDisposable
{private IntPtr buffer;private bool disposed = false;public UnmanagedBuffer(int size){buffer = Marshal.AllocHGlobal(size);}~UnmanagedBuffer(){Dispose(false);}public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}protected virtual void Dispose(bool disposing){if (!disposed){if (buffer != IntPtr.Zero){Marshal.FreeHGlobal(buffer);buffer = IntPtr.Zero;}disposed = true;}}public IntPtr Buffer => buffer;
}

6. 内存管理最佳实践

  • 始终释放内存:确保所有分配的内存都在适当的时候释放,防止内存泄漏。
  • 使用智能指针或封装类:封装内存管理逻辑,减少手动管理的复杂性。
  • 定期检查内存使用:使用工具和代码分析,确保没有未释放的内存。

实践示例

以下是一个综合示例,展示了内存分配、内存拷贝和资源管理的完整流程:

C++部分代码:PointManager.h和PointManager.cpp两个文件

#pragma once#ifdef EXAMPLE_EXPORTS
#define EXAMPLE_API __declspec(dllexport)
#else
#define EXAMPLE_API __declspec(dllimport)
#endifstruct Point
{int X;int Y;
};extern "C" EXAMPLE_API Point* CreatePoint(int x, int y);
extern "C" EXAMPLE_API void GetPoint(Point * point, Point * pOut);
extern "C" EXAMPLE_API void DeletePoint(Point * point);
#include "pch.h"
#include "PointManager.h"// 创建一个新的 Point 对象并返回其指针
extern "C" __declspec(dllexport) Point* CreatePoint(int x, int y)
{Point* p = new Point();p->X = x;p->Y = y;return p;
}// 获取 Point 对象的值
extern "C" __declspec(dllexport) void GetPoint(Point * point, Point * pOut)
{if (point == nullptr || pOut == nullptr){SetLastError(ERROR_INVALID_PARAMETER);return;}pOut->X = point->X;pOut->Y = point->Y;
}// 删除 Point 对象
extern "C" __declspec(dllexport) void DeletePoint(Point * point)
{if (point != nullptr){delete point;}
}

C#部分代码:

using System;
using System.Runtime.InteropServices;class Program
{[StructLayout(LayoutKind.Sequential)]public struct Point{public int X;public int Y;}[DllImport("Example.dll", SetLastError = true)]public static extern IntPtr CreatePoint(int x, int y);[DllImport("Example.dll", SetLastError = true)]public static extern void GetPoint(IntPtr point, out Point p);[DllImport("Example.dll", SetLastError = true)]public static extern void DeletePoint(IntPtr point);static void Main(){IntPtr pointPtr = CreatePoint(10, 20);if (pointPtr == IntPtr.Zero){int error = Marshal.GetLastWin32Error();Console.WriteLine($"Error: {error}");return;}Point p;GetPoint(pointPtr, out p);Console.WriteLine($"Point: {p.X}, {p.Y}");DeletePoint(pointPtr);}
}

这个示例展示了如何在非托管代码中创建和管理内存资源,并在托管代码中正确分配和释放内存。

参考文档

使用非托管 DLL 函数 - .NET Framework | Microsoft Learn

标识 DLL 中的函数 - .NET Framework | Microsoft Learn


DllImportAttribute.EntryPoint 字段 (System.Runtime.InteropServices) | Microsoft Learn

原文链接:https://www.toutiao.com/article/7383720159233573427/?app=news_article&timestamp=1719443240&use_new_style=1&req_id=2024062707072029288E0C168B30524880&group_id=7383720159233573427&share_token=27F51CAD-7939-4769-90A3-0F26B5F997C4&tt_from=weixin&utm_source=weixin&utm_medium=toutiao_ios&utm_campaign=client_share&wxshare_count=1&source=m_redirect&wid=1719451374672

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

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

相关文章

计算机组成原理 | CPU子系统(3)MIPS32指令架构

MIPS32架构指令格式 MIPS32架构寻址方式 指令的编码与功能

文本生成sql模型(PipableAI/pip-sql-1.3b)

安装环境 pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install transformers 代码 question "What are the email address, town and county of the customers who are of the least common gender?"sc…

203. 移除链表元素【链表】【C++】

题目描述 题目描述 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,5] 示例 2&#x…

再谈kettle两种循环之--调用http分页接口循环获取数据

再谈kettle两种循环之 – 调用http分页接口循环获取数据 1.场景介绍: 由于数据量比较大,接口有返回限制,需要用到循环分页获取数据 2.案例适用范围: 循环job可参考,变量运用可参考,调用http分页接口循环获取数据可参考&#…

国产操作系统上多种压缩和解压命令详解 _ 统信 _ 麒麟 _ 中科方德

原文链接:国产操作系统上多种压缩和解压命令详解 | 统信 | 麒麟 | 中科方德 Hello,大家好啊!今天给大家带来一篇在国产操作系统上多种压缩和解压命令详解的文章。压缩和解压缩是我们在日常工作中经常需要进行的操作,尤其是在处理大…

【AIGC】用 AI 绘画 诠释印象派!关键词、安装包分享!

前言 印象派艺术运动是19世纪60年代法国的一场艺术革命,它不仅革新了绘画技法,更重新诠释了光与色彩、自然与美。印象派艺术家,如莫奈、雷诺阿和德加,通过捕捉自然光线的瞬息变化,用色彩和笔触表达对现实世界的独特感…

Theta方法:一种时间序列分解与预测的简化方法

Theta方法整合了两个基本概念:分解时间序列和利用基本预测技术来估计未来的价值。 每个数据科学爱好者都知道,时间序列是按一定时间间隔收集或记录的一系列数据点。例如,每日温度或经济指标的月值。把时间序列想象成不同成分的组合,趋势(数据…

取证工作:怎样解锁 LUKS2 加密磁盘?

对于 LUKS2 密码进行恢复,Elcomsoft Distributed Password Recovery (简称 EDPR) 软件可以构建高性能集群,以更快地破解密码。EDPR 软件提供零开销的可扩展性,并支持 GPU 加速,以加快恢复速度。EDPR 可帮助…

Redis-实战篇-编码解决商铺查询的缓存穿透问题(缓存空对象)

文章目录 1、缓存穿透2、常见的解决方案有两种:2.1、缓存空对象2.2、布隆过滤器 3、编码解决商铺查询的缓存穿透问题3.1、queryById3.2、RedisConstants.java 1、缓存穿透 缓存击穿是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效…

【b站-湖科大教书匠】3 数据链路层-计算机网络微课堂

课程地址:【计算机网络微课堂(有字幕无背景音乐版)】 https://www.bilibili.com/video/BV1c4411d7jb/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 3 数据链路层 3.1 数据链路层概述 3.1.1 数据链路层在网络体系结…

2024华为数通HCIP-datacom最新题库(变题更新⑤)

请注意,华为HCIP-Datacom考试831已变题 请注意,华为HCIP-Datacom考试831已变题 请注意,华为HCIP-Datacom考试831已变题 近期打算考HCIP的朋友注意了,如果你准备去考试,还是用的之前的题库,切记暂缓。 1、…

【项目管理体系】代码评审规范

1完整性检查 2一致性检查 3正确性检查 4可预测性检查 5健壮性检查 6结构性检查 7可追溯性检查 8可理解性检查 9可验证性检查 软件开发全套资料获取:(本文末个人名片直接获取) 软件产品,特别是行业解决方案软件产品不同于一般的商品…

JavaScript中常用数据类型做布尔值(Boolean)转换

一、前言 二、示例 1、String转Boolean 2、Number转Boolean 3、NaN、Null、undefined 转Boolean 4、Object转Boolean 5、Array转Boolean 6、Symbol转Boolean 三、总结 四、思考 一、前言 JavaScript中,经常需要对一些值进行boolean判断,根据判…

DDoS攻击的最新防御策略:从检测到缓解的全方位方案

在数字化浪潮的推动下,互联网已成为现代社会的血脉。然而,随着网络空间的不断膨胀,分布式拒绝服务(DDoS)攻击如同潜伏在暗处的猛兽,随时准备发动致命一击,威胁着网络的稳定与安全。面对这一严峻…

前端-echarts tooltip展示多项自定义数据

效果如图,鼠标滑动到某一个柱子的时候,出现这一项数据的多个自定义数据,外加自己的模板样式渲染。 希望能展示每一列中的多个自定义数据 代码部分 主要是在data中,value就是实际展示的主数据,其他字段名为自定义的数…

Linux常见操作问题

1、登录刚创建的用户,无法操作。 注:etc/passwd文件是Linux操作系统中存储用户账户信息的文本文件,包含了系统中所有用户的基本信息,比如用户名、用户ID、用户组ID、用户家目录路径。 注:etc: 这个目录存放所有的系统…

Java工具包——Lombok

目录 1. maven仓库手动导入依赖注解 1.1 maven仓库引入依赖 1.2 在类上使用Data注解 1.3 在属性上使用Getter与Setter注解 2. EditStarters插件注解 2.1 安装EditStarters插件 2.2 在pom.xml中进行操作 2.3 在java对象类中使用注解 3. lombok工具使用结果查看 3.1…

腾讯云CVM,CentOS8系统下部署Java-Web项目步骤详解

在CVM中部署项目首先要配置好JDK,Tomcat,Mysql(这里以Tomcat和Mysql为例)。部署JDK和Tomcat的步骤可以参考 CentOS7系统下部署tomcat,浏览器访问localhost:8080/_不积跬步,无以至千里;不积小流,无以成江河。-CSDN博客 我这里从Mysql的安装和设…

HDOJ5616 Jam‘s balance

目录 HDOJ5616 Jams balance题目描述背景输入输出 题解解法一解法二优化 打赏 HDOJ5616 Jam’s balance 题目描述 背景 有 N N N个已知质量的砝码,分别询问给出的 M M M个质量能否被称出 输入 第一行输入一个变量 T T T,表示有 T T T组数据&#xf…

数据预处理功能教程,上传文件生成知识库 | Chatopera

如何快速的生成高质量的知识库? 数据预处理功能教程 | Chatopera 云服务低代码定制聊天机器人 关于 Chatopera Chatopera 云服务重新定义聊天机器人,https://bot.chatopera.com 定制智能客服、知识库、AI 助手、智慧家居等智能应用,释放创新…