Notifications
Article
DllImportAttribute 常用知识介绍
Updated a month ago
685
1
不管是是端游还是手游,我们在使用Unity引擎进行代码编写的时候,会引用或多或少的三方库,其他不乏 使用C\C++等语言编写的Native库。本文主要介绍 C#中与 Native Code 打交道时,必不可少的一个知识点。在开发历程中不论是自己编写底层库还是使用开源库都离不开对该 特性的应用。

引言

  • DllImportAttribute属性提供调用非托管函数的规范。在对托管代码进行P/Invoke调用时,DllImportAttribute类型扮演着重要的角色。
  • DllImportAttribute的主要作用就是给CLR指示哪个DLL导出您想要的调用的函数。相关DLL的名称被作为一个构造函数参数传递给DllImportAttribute

常用属性介绍

EntryPoint

指定要调用的 DLL 入口点的名称序号(默认入口点名称就是托管方法的名称)。序号以 “#” 符号为前缀,如 #1。
如果省略此字段,则CLR将使用以DllImportAttribute标记的.NET方法的名称。
在不希望外部托管方法具有与 DLL导出相同的名称的情况下,可以设置该属性来指示导出的 DLL 函数的入口点名称。
来看一段代码示意:
using System; using System.Runtime.InteropServices; class Example { [DllImport("user32.dll", EntryPoint = "MessageBox")] public static extern int MyNewMessageBoxMethod(IntPtr hWnd, String text, String caption, uint type); static void Main() { // Call the MessageBox function using platform invoke. MyNewMessageBoxMethod(new IntPtr(0), "Hello World!", "Hello Dialog", 0); } }
注意: 如果不指定 EntryPoint ,那么需要把 示例代码中 的 MyNewMessageBoxMethod 函数名改为 MessageBox

CharSet

指示如何向方法封送字符串参数。并控制名称重整。
通过一个 CharSet 枚举的成员使用此字段指定字符串参数的封送处理行为,并指定要调用的入口点名称。
用于 C# 的默认枚举成员为 CharSet.Ansi,用于 C++ 的默认枚举成员为 CharSet.None,它与 CharSet.Ansi 等效。
如果 DLL 函数不以任何方式处理文本,则可以忽略 DllImportAttribute CharSet 属性。然而,当 Char String 数据是等式的一部分时,应该将 CharSet 属性设置为 CharSet.Auto。这样可以使 CLR根据 宿主 OS 使用适当的字符集。如果没有显式地设置 CharSet属性,则其默认值为 CharSet.Ansi。这个默认值是有缺点的,因为对于在 Windows 2000Windows XP Windows NT®上进行的 interop 调用,它会消极地影响文本参数封送处理的性能。
简单说,在手游开发中该字段的影响面不大。
示例代码:
using System; using System.Runtime.InteropServices; class Example { [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "MessageBox")] public static extern int MyNewMessageBoxMethod(IntPtr hWnd, String text, String caption, uint type); static void Main() { // Call the MessageBox function using platform invoke. MyNewMessageBoxMethod(new IntPtr(0), "Hello World!", "Hello Dialog", 0); } }

SetLastError

用来指示是否被调用方调用 SetLastError 从特性化的方法返回之前的 Win32 API 函数。true 指示被调用方将调用 SetLastError;否则为 false
默认值为 false。运行时封送拆收器将调用 GetLastError并缓存返回的值,以防其被其他API调用覆盖。SetLastError 错误处理非常重要,但在编程时经常被遗忘。当您进行 P/Invoke调用时,也会面临其他的挑战 — 处理托管代码中 Windows API错误处理和异常之间的区别。
经常用到的一个解决方案是:
如果正在使用 P/Invoke调用Windows API 函数,而对于该函数,使用 GetLastError 来查找扩展的错误信息,则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设置为 true。这适用于大多数外部方法。这会导致CLR在每次调用外部方法之后缓存由 API 函数设置的错误。然后,在包装方法中,可以通过调用类库的System.Runtime.InteropServices.Marshal类型中定义的 Marshal.GetLastWin32Error方法来获取缓存的错误值。并且检查这些期望来自 API函数的错误值,并为这些值引发一个可感知的异常。对于其他所有失败情况(包括根本就没意料到的失败情况),则引发在System.ComponentModel命名空间中定义的 Win32Exception,并将 Marshal.GetLastWin32Error 返回的值传递给它。
示例代码:
using System.Runtime.InteropServices; public class Win32 { [DllImport("user32.dll",SetLastError=true)] public static extern int MessageBoxA(int hWnd,String text,String caption,uint type); }

CallingConvention

指定传递方法参数时使用的调用约定值。默认值为 CallingConvention.WinapiWinapi默认StdCall约定),它在大多数情况下都可行。该值与 Windows CE 平台上的 __cdecl 相对应。通常,Native 函数(例如 Windows API函数或 C- 运行时 DLL 函数)的调用约定描述了如何 将参数 推入线程堆栈 或 从线程堆栈中清除。
  • 大多数 Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。
  • 相反,许多 C-RunTime DLL 函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。

最后

其中最常用的 EntryPoint \ CharSet ,其他的可以根据自己的项目情况再进一步的了解学习。附一张完整的注释图例
Tags:script
郡墙
Programmer - Programmer
11
Comments
凹凸
a month ago
..
👍
0