DllImportAttribute 常用知识介绍

引言

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


常用属性介绍

EntryPoint

指定要调用的DLL入口点的名称序号(默认入口点名称就是托管方法的名称)。序号以“#”符号为前缀,如#1。

如果省略此字段,则CLR将使用以DllImportAttribute标记的.NET方法的名称。

  • EntryPoint 在不希望外部托管方法具有与 DLL导出相同的名称的情况下,可以设置该属性来指示导出的 DLL 函数的入口点名称。

当定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在Windows 中还可以通过它们的序号值绑定到导出的 DLL 函数。

如果需要这样做,则诸如#1#129EntryPoint 值指示 DLL非托管函数的序号值而不是函数名。

示意代码:

using System;
using System.Runtime.InteropServices;
class Example
{    
    // Use DllImport to import the Win32 MessageBox function.    
    // Specify the method to import using the EntryPoint field and     
   // then change the name to MyNewMessageBoxMethod.    
   [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);    
   }
}

CharSet

指示如何向方法封送字符串参数。并控制名称重整。

通过一个 CharSet 枚举的成员使用此字段指定字符串参数的封送处理行为,并指定要调用的入口点名称。

用于C#Visual Basic 的默认枚举成员为 CharSet.Ansi,用于 C++ 的默认枚举成员为 CharSet.None,它与 CharSet.Ansi 等效。

Visual Basic 中可以使用 Declare 语句指定 CharSet字段。
ExactSpelling字段会影响 CharSet字段在确定要调用的入口点名称时的行为。

CharSet 对于字符集,并非所有版本的 Windows 都是同样创建的Windows 9x系列产品缺少重要的 Unicode 支持,而Windows NTWindows CE系列则一开始就使用 Unicode。在这些操作系统上运行的CLRUnicode 用于StringChar数据的内部表示。但也不必担心 — 当调用 Windows 9x API函数时,CLR会自动进行必要的转换,将其从 Unicode转换为 ANSI

如果DLL 函数不以任何方式处理文本,则可以忽略 DllImportAttributeCharSet 属性。然而,当CharString 数据是等式的一部分时,应该将CharSet 属性设置为 CharSet.Auto。这样可以使 CLR根据宿主 OS使用适当的字符集。
如果没有显式地设置 CharSet属性,则其默认值为 CharSet.Ansi这个默认值是有缺点的,因为对于在 Windows 2000Windows XPWindows NT®上进行的 interop 调用,它会消极地影响文本参数封送处理的性能。

应该显式地选择 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情况是:您显式地指定了一个导出函数,而该函数特定于这两种 Win32 OS 中的某一种。ReadDirectoryChangesW API 函数就是这样的一个例子,它只存在于基于 Windows NT 的操作系统中,并且只支持 Unicode;在这种情况下,您应该显式地使用 CharSet.Unicode。

有时,Windows API是否有字符集关系并不明显。一种决不会有错的确认方法是在Platform SDK 中检查该函数的C语言头文件。

示意代码:

using System;
using System.Runtime.InteropServices;
class Example
{    
    // Use DllImport to import the Win32 MessageBox function.    
    // Specify the method to import using the EntryPoint field and     
   // then change the name to MyNewMessageBoxMethod.    
   [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 相对应。

然而,如果该调用不起作用,则可以检查 Platform SDK 中的声明头文件,看看调用的 API 函数是否是一个不符合调用约定标准的异常 API

通常,本机函数(例如 Windows API函数或 C- 运行时 DLL 函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。

  1. 大多数Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。

  2. 相反,许多 C-运行时 DLL函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。

幸运的是,要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常,从默认值 CallingConvention.Winapi 开始是最好的选择。然后,在 C 运行时 DLL 函数和少数函数中,可能需要将约定更改为 CallingConvention.Cdecl

示意代码:

 using System;
 using System.Runtime.InteropServices;
 public class LibWrap
 {
    // C# doesn't support varargs so all arguments must be explicitly defined.
    // CallingConvention.Cdecl must be used since the stack is 
    // cleaned up by the caller.
    // int printf(const char *format [, argument]...)
    [DllImport("msvcrt.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    public static extern int printf(String format, int i, double d); 
    [DllImport("msvcrt.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    public static extern int printf(String format, int i, String s); 
 }
 public class App
 {
     public static void Main()
     {
         LibWrap.printf("\nPrint params: %i %f", 99, 99.99);
         LibWrap.printf("\nPrint params: %i %s", 99, "abcd");
     }
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值