做外贸网站赚钱吗,南京正规小程序开发公司,wordpress主题699元,网站后台制作深入讨论DllImport属性的作用和配置方法
在基础篇中#xff0c;我们已经简单介绍了DllImport的一些属性。现在我们将深入探讨这些属性的实际应用。
1. EntryPoint EntryPoint属性用于指定要调用的非托管函数的名称。如果托管代码中的函数名与非托管代码中的函数名不同#…深入讨论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/?appnews_articletimestamp1719443240use_new_style1req_id2024062707072029288E0C168B30524880group_id7383720159233573427share_token27F51CAD-7939-4769-90A3-0F26B5F997C4tt_fromweixinutm_sourceweixinutm_mediumtoutiao_iosutm_campaignclient_sharewxshare_count1sourcem_redirectwid1719451374672