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)
#endif

struct 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.mfbz.cn/a/772128.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TreeSize Free - 硬盘空间管理工具

TreeSize FreeTreeSize Free 是一款免费的强大灵活的硬盘空间管理工具。可以帮你找出硬盘上最大的目录以及它占用的空间。支持空间大小显示、分配空间和占用空间、文件数、3D工具条和分配图、最近使用数据、文件作者、NTFS压缩率等信息,并支持搜索文件。该软件类似浏…

掌握亚马逊自养号:测评策略的核心要点与实战经验

在当今电商领域的激烈角逐中,亚马逊测评对于卖家而言,已从单纯的销量助推器与好评累积工具,进化为品牌塑造与市场洞察的关键环节。然而,许多卖家仍局限于传统认知,未能充分挖掘自养号测评的多元化价值与深远影响。本文…

Modbus协议转Profinet协议网关模块连智能仪表与PLC通讯

一、现场需求:PLC作为控制器,仪表设备做为执行设备,执行设备能够实时响应PLC传来的指令,并且向PLC回馈数据,从而达到PLC对仪表设备进行控制和监测,实现对生产过程的精准控制。 二、解决方案:通过…

2024年7月5日 十二生肖 今日运势

小运播报:2024年7月5日,星期五,农历五月三十 (甲辰年庚午月庚午日),法定工作日。 红榜生肖:狗、羊、虎 需要注意:鸡、牛、鼠 喜神方位:西北方 财神方位:正…

java考试题20道

选择题 编译Java源代码文件的命令是javac javac命令是将Java源代码文件进行编译得到字节码文件(.class文件) java命令是在JVM上运行得到的字节码文件 下面是一个示例: javac test.java -------> test.class java test ------> 运行test.class文件下列那…

QT_GUI

1、QT安装 一个跨平台的应用程序和用户界面框架,用于开发图形用户界面(GUI)应用程序以及命令行工具。QT有商业版额免费开源版,一般使用免费开源版即可,下面安装的是QT5,因为出来较早,使用较多&…

以品质为初心,以创新为驱动,光明乳业闪耀第十五届中国奶业大会

2024年7月3日,以“数智赋能引领产业发展增长点,科技创新驱动奶业新质生产力”为主题的中国奶业协会第十五届奶业大会奶业20强(D20)论坛暨2024中国奶业展览会隆重召开,光明乳业党委书记、董事长黄黎明受邀出席会议&…

代谢组数据分析(十三):评估影响代谢物的重要临床指标

欢迎大家关注全网生信学习者系列: WX公zhong号:生信学习者Xiao hong书:生信学习者知hu:生信学习者CDSN:生信学习者2介绍 相关性分析是通过计算两个变量之间的相关系数来评估它们之间线性关系的强度和方向。最常用的是皮尔逊相关系数(Pearson correlation coefficient),…

python库(3):Cerberus库

1 Cerberus简介 Cerberus 是一个Python数据验证库,设计用于验证数据结构的有效性和一致性。它提供了一种简单而强大的方式来定义和应用验证规则,特别适用于处理用户输入的验证、配置文件的检查以及API的参数验证等场景。下面将详细介绍 Cerberus 的特点…

vite项目配置svg图标(vite-plugin-svg-icons)

1.插件地址 网址 , 可以去里面查看中文文档,里面有详情的教程 2.使用, 如果你安装的有element-plus ,可以使用这样的方式来修改大小和颜色 <el-icon size"18" color"red"><SvgIcon name"xing"></SvgIcon></el-icon> …

汇聚荣拼多多评价好不好?

汇聚荣拼多多评价好不好?在探讨电商平台的口碑时&#xff0c;用户评价是衡量其服务质量和商品质量的重要指标。拼多多作为国内领先的电商平台之一&#xff0c;其用户评价自然成为消费者选择购物平台时的参考依据。针对“汇聚荣拼多多评价好不好?”这一问题&#xff0c;可以从…

Elasticsearch初识与 index+mapping+document 基操

前言 在21年多少有使用过es 当时是在艺术赛道的一个教育公司&#xff0c;大概流程就是 将mysql中的各种课程数据通过logstash汇总到es 然后提供rest接口出去。由于在职时间较短(很不幸赶上了教育双减)&#xff0c;所以对es的了解其实仅仅是些皮毛&#xff0c;当然elk在我的任职…

稀疏数组搜索

题目链接 稀疏数组搜索 题目描述 注意点 字符串数组中散布着一些空字符串words的长度在[1, 1000000]之间字符串数组是排好序的数组中的字符串不重复 解答思路 因为数组中的字符串是排好序的&#xff0c;所以首先想到的是二分查找&#xff0c;先将数组中长度与s相同的字符串…

全网都在疯传的最新蓝海风口项目!

最近全网都在疯传这种视频&#xff0c;想必兄弟们都见到过了&#xff01; 大家看这个号&#xff0c;1天的时间&#xff0c;2个作品&#xff0c;第2个直接就爆了&#xff0c;昨天看点赞还是3.8w&#xff0c;今天已经10w了&#xff0c;这是妥妥的风口啊&#xff01; 大家有没有想…

io_contextttttttttttt

创建上下文——io_context_t 它是一个上下文结构&#xff0c;在内部它包含一个完成队列&#xff0c;在线程之间是可以共享的。 提交请求——iocb io回调数据结构&#xff0c;和io_submit配合使用。 处理结果 通过io_event处理结果&#xff0c; struct io_event {void *data…

TIS人人都会用的数据集成产品

文章目录 1.TIS是什么&#xff1f;1.1 简介1.2 官网及项目地址1.3 架构 2.功能特性2.1 基于Web UI的开箱即用2.2 支持分布式任务分发2.3 全新的基于微内核的运行环境2.4 功能覆盖DataX大部分&#xff08;Reader/Writer&#xff09;Plugin2.5 重构DataX的Classloader2.6 支持RDB…

科比老大职业生涯数据预测(基于随机森林模型)

1.实验背景 科比布莱恩特&#xff0c;作为NBA历史上最伟大的篮球运动员之一&#xff0c;他的职业生涯充满了无数精彩瞬间。 科比于1996年以13顺位的选秀身份进入联盟&#xff0c;一生都效力于洛杉矶湖人队。于2016年宣布退役&#xff0c;职业生涯获奖无数&#xff0c;5次NBA总…

99. 岛屿数量

题目描述&#xff1a;给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周都是水域。你可以假设矩阵外均被水包围。 输入描述&#xff1a…

EXTI寄存器,AFIO的简洁,EXTI配置的流程

一&#xff0c;AFIO简介 AFIO是Alternate Function Input/Output 的缩写&#xff0c;表示复用功能IO&#xff0c;主要用于实现IO端口的复用功能以及外部中断的控制 STM32外设有很多I/O以及内置外设&#xff08;如12C&#xff0c;ADC,ISP,USART等&#xff09;。为节省引出管脚的…

命令行运行git reflog(reference log)报错的解决办法

文章目录 1. 检查 Git 是否已安装2. 检查 PATH 环境变量3. 重新安装 Git 在Git中&#xff0c; reflog的英文全称是 “ reference log”。意思是 引用日志&#xff08;参考日志&#xff09;。它记录了本地仓库中HEAD和分支引用所指向的提交的变更历史。这包括了你所有的提交&…