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…

02--Spring中AOP

目录 16.1 AspectJ框架【AOP框架】 16.2 使用AspectJ步骤 16.3 Spring中AOP概述 16.4 Spring中AOP相关术语 第十七章 AspectJ详解【重点】 17.1 AspectJ中切入点表达式 17.2 AspectJ中JoinPoint对象 17.3 AspectJ中通知 17.4 定义切面优先级 17.5 基于XML方式配置AOP …

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…

qiankun 结合 vue3, 小白快速上手体验

一、主应用改造 首先需要维护一份微应用列表,里面包含了微应用的名称、入口和生效规则,若需要给子应用传递内容,可以在 props 传入对应的内容 // app.js const apps [{name: micro-vue-app3,entry: //localhost:3013,container: #micro-vu…

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

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

SQLServer2022新特性 GENERATE_SERIES函数

SQLServer2022新特性 GENERATE_SERIES函数,在给定间隔内生成一系列数字。 序列值之间的间隔和步骤由用户定义。 参考官方地址 https://learn.microsoft.com/en-us/sql/t-sql/functions/generate-series-transact-sql?viewsql-server-ver16 1、本文内容 语法参数…

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

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

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

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

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

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

【linux/shell】shell中使用for循环读取数据

目录 一.for循环从列表中读取数据的几种形式 二.for循环从配置文件读取数据 三.for循环用通配符读取目录 四.for循环带有数字变量 一.for循环从列表中读取数据的几种形式 #!/bin/bash listl"aa bb cc" list2aa bb ccfor i in $list3 doecho $i done 使用这种形…

取证工作:怎样解锁 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、缓存穿透 缓存击穿是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效…

Spring Boot中使用Feign进行HTTP请求

Spring Boot中使用Feign进行HTTP请求 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们来探讨一下如何在Spring Boot中使用Feign进行HTTP请求。 一、Fei…

【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、…

Python的with语句与上下文管理器:深入解析与实战应用

Python的with语句与上下文管理器:深入解析与实战应用 在Python编程中,with语句是一个强大的特性,它提供了一种简洁的方式来管理资源,如文件、网络连接等。通过使用with语句,我们可以确保在代码块执行完毕后&#xff0…

Python中的enumerate函数:索引与值的完美搭档

Python中的enumerate函数:索引与值的完美搭档 在Python编程中,遍历列表、元组或其他可迭代对象时,我们经常会需要同时访问每个元素的索引和值。这时,enumerate()函数就显得尤为重要,它为我们提供了一个简洁而高效的方…

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

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