寻找Zihuatanejo

DllImport属性(多转整理)

DLL Import 属性

现在是更深入地进行探讨的时候了。在对托管代码进行 P/Invoke 调用时,DllImportAttribute 类型扮演着重要的角色。DllImportAttribute 的主要作用是给 CLR 指示哪个 DLL 导出您想要调用的函数。相关 DLL 的名称被作为一个构造函数参数传递给 DllImportAttribute。

如果您无法肯定哪个 DLL 定义了您要使用的 Windows API 函数,Platform SDK 文档将为您提供最好的帮助资源。在 Windows API 函数主题文字临近结尾的位置,SDK 文档指定了 C 应用程序要使用该函数必须链接的 .lib 文件。在几乎所有的情况下,该 .lib 文件具有与定义该函数的系统 DLL 文件相同的名称。例如,如果该函数需要 C 应用程序链接到 Kernel32.lib,则该函数就定义在 Kernel32.dll 中。您可以在 MessageBeep 中找到有关 MessageBeep 的 Platform SDK 文档主题。在该主题结尾处,您会注意到它指出库文件是 User32.lib;这表明 MessageBeep 是从 User32.dll 中导出的。

可选的 DllImportAttribute 属性

除了指出宿主 DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。

EntryPoint 在不希望外部托管方法具有与 DLL 导出相同的名称的情况下,可以设置该属性来指示导出的 DLL 函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在 Windows 中还可以通过它们的序号值绑定到导出的 DLL 函数。如果您需要这样做,则诸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非托管函数的序号值而不是函数名。

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

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

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

有时,Windows API 是否有字符集关系并不明显。一种决不会有错的确认方法是在 Platform SDK 中检查该函数的 C 语言头文件。(如果您无法肯定要看哪个头文件,则可以查看 Platform SDK 文档中列出的每个 API 函数的头文件。)如果您发现该 API 函数确实定义为一个映射到以 A 或 W 结尾的函数名的宏,则字符集与您尝试调用的函数有关系。Windows API 函数的一个例子是在 WinUser.h 中声明的 GetMessage API,您也许会惊讶地发现它有 A 和 W 两种版本。

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 返回的值传递给它。如果您回头看一下图 1 中的代码,您会看到我在 extern MessageBeep 方法的公共包装中就采用了这种方法。

CallingConvention 我将在此介绍的最后也可能是最不重要的一个 DllImportAttribute 属性是 CallingConvention。通过此属性,可以给 CLR 指示应该将哪种函数调用约定用于堆栈中的参数。CallingConvention.Winapi 的默认值是最好的选择,它在大多数情况下都可行。然而,如果该调用不起作用,则可以检查 Platform SDK 中的声明头文件,看看您调用的 API 函数是否是一个不符合调用约定标准的异常 API。

通常,本机函数(例如 Windows API 函数或 C- 运行时 DLL 函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。大多数 Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。相反,许多 C-运行时 DLL 函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。

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

 

C#通过p/invoke调用C++ DLL

1, PInvoke什么意思? Platform Invocation Services

2, 干什么用?  导入外部函数?什么是外部函数,就是不属于.Net托管的函数。

3,如何用?看下面的例子。用[DllImport(dllname)]来实现,但是首先要把System.Runtiime.InteropServices using进来。但是不using也行,就要敲全称,随你便了。

[DllImport("user32.dll")] 
         static extern int MessageBoxA(int hWnd,

                                          string msg,

                                          string caption,

                                          int type );

         private void button1_Click(object sender, System.EventArgs e)

         {

              MessageBoxA( 0, "Msg:hello", "Caption:Hello",0 );

         }

 

 

4,万一我的程序中已经有了一个函数叫MessageBoxA怎么办?这时候,可以使用EntryPoint来帮忙,下面的例子中,你把自己的函数定义为MyMsg.
[DllImport("user32.dll",EntryPoint="MessageBoxA")]

         static extern int MyMsg(int hWnd,

                                          string msg,

                                          string caption,

                                          int type );

         private void button1_Click(object sender, System.EventArgs e)

         {

              MyMsg( 0, "Msg:hello", "Caption:Hello",0 );

         }

 


5,charset如何使用?****A的是Ansi编码,****W的是unicode编码,如何使用charset,看你的函数调用而定。2K以后都用unicode了,前面的9x都是ansi编码,但是这是缺省的,微软给9x打布丁支持unicode不算。


API有两个版本: A(ASNI)版本和W(Unicode)版本. A版本调用时候会用ANSI来封送字符串,一般是win95/98上。W版本用Unicode来封送,在NT,2K和XP上。

.Net和win32交互的时候,默认是使用CharSet.Ansi来传送。

在 DllImportAttribute.ExactSpelling 字段为 true 时(它是 Visual Basic .NET 中的默认值),平台调用将只搜索您指定的名称。例如,如果指定 MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。

当 ExactSpelling 字段为 false(它是 C++ 托管扩展和 C# 中的默认值),平台调用将首先搜索未处理的别名 (MessageBox),如果没有找到未处理的别名,则将搜索已处理的名称 (MessageBoxA)。请注意,ANSI 名称匹配行为与 Unicode 名称匹配行为不同。      


 //CharSet.Ansi will call MessageBoxA

         //CharSet.Unicode will call MessageBoxW

 

         [DllImport("user32.dll",EntryPoint="MessageBox",CharSet=CharSet.Ansi)]

         static extern int MyMsg(int hWnd,

                                          string msg,

                                          string caption,

                                          int type );

         private void button1_Click(object sender, System.EventArgs e)

         {

              MyMsg( 0, "Msg:hello", "Caption:Hello",0 );

         }

 

6,Dll里面的callback函数如何实现?看下面这个例子:

delegate bool CallBackDef( int hWnd, int lParm );

 

         [DllImport("user32.dll")]

              static extern int GetWindowText( int hWnd,

                                                   StringBuilder text,

                                                   int count );

         [DllImport("user32.dll")]

              static extern int EnumWindows(CallBackDef callback, int lParam );

 

         static bool PrintWindow(int hWnd, int lParm )

         {

              StringBuilder text = new StringBuilder(255);

              GetWindowText( hWnd, text, 255 );

              Console.WriteLine( text.ToString() );

 

              return true;

         }

 

         private void button1_Click(object sender, System.EventArgs e)

         {

              CallBackDef callBack = new CallBackDef( PrintWindow );

              EnumWindows( callBack, 0 );

         }

 

7,MarshalAs如何用,什么时候用?
在MessageBox传递string去Dll的时,C#编译器知道Win32LPSTR等价与一个C#字符串。但是如果想覆盖默认.Net行为, 这时候就需要MarshallAs


[DllImport("user32.dll", CharSet=CharSet.Unicode )]

              static extern int MessageBox( int hWnd,

                                               [MarshalAs(UnmanagedType.LPWStr)]

                                               string msg,

                                               [MarshalAs(UnmanagedType.LPWStr)]

                                               string caption,

                                               int type);

 


8,我怎么知道要调用的函数在那个dll了?
这个问题我不会比你更清楚,特殊的函数应该在你特殊的dll中。Win32中常用的几个dll是user32.dll, kernel32.dll和GDI32.dll.用dumpbin -exports kernel32.dll可以看到这个dll所有的API函数。

9,相互之间传递struct怎么办?我是说传递很复杂的struct?
传递一个结构,这个要用到StructLayoutAttribute属性。比如:

PtInRect 具有以下非托管签名:
BOOL PtInRect(const RECT *lprc, POINT pt);
请注意,由于函数需要指向 RECT 类型的指针,必须通过引用来传递 Rect 结构。


using System.Runtime.InteropServices;


[StructLayout(LayoutKind.Sequential)]

public struct Point

{

     public int x;

     public int y;

}  

 

[StructLayout(LayoutKind.Explicit)]

public struct Rect

{

     [FieldOffset(0)] public int left;

     [FieldOffset(4)] public int top;

     [FieldOffset(8)] public int right;

     [FieldOffset(12)] public int bottom;

}  

 

class Win32API

{

     [DllImport("User32.dll")]

     public static extern bool PtInRect(ref Rect r, Point p);

}

 

10,哪里有不错的教程,我可以学到更详细的东西?
google一下多的是。MSDN里面也有一些不错的教程:(vs.net 2003)

 

P/Invoke是什么?

答:

在受控代码与非受控代码进行交互时会产生一个事务(transition) ,这通常发生在使用平台调用服务(Platform Invocation Services),即P/Invoke

如调用系统的 API 或与 COM 对象打交道,通过 System.Runtime.InteropServices 命名空间

虽然使用 Interop 非常方便,但据估计每次调用事务都要执行 10 到 40 条指令,算起来开销也不少,所以我们要尽量少调用事务

如果非用不可,建议本着一次调用执行多个动作,而不是多次调用每次只执行少量动作的原则

[引]C# DllImport 属性

在该示例中,程序接收来自用户的字符串并将该字符串显示在消息框中。程序使用从 User32.dll 库导入的 MessageBox 方法。

using System;
using System.Runtime.InteropServices;
class MainClass 
{
   [DllImport("User32.dll")]
   public static extern int MessageBox(int h, string m, string c, int type);

   static int Main() 
   {
      string myString; 
      Console.Write("Enter your message: ");
      myString = Console.ReadLine();
      return MessageBox(0, myString, "My Message Box", 0);
   }
}

extern 修饰符用于声明在外部实现的方法。

extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与 DllImport 属性一起使用。

在这种情况下,还必须将方法声明为 static。

本主题说明 DllImport 属性的常见用法。第一节讨论使用 DllImport 从托管应用程序调用本机代码的优点。第二节集中讨论封送处理和 DllImport 属性的各个方面。

从托管应用程序调用非托管代码

当在托管应用程序中重用现有的非托管代码时,DllImport 属性非常有用。例如,托管应用程序可能需要调用非托管 WIN32 API。

下面的代码示例说明此通用方案,此示例将调用 MessageBox(位于 User32.lib 中):

 

#using <mscorlib.dll> 
using namespace System::Runtime::InteropServices;  
// for DllImportAttribute 
 
namespace SysWin32 
{ 
   [DllImport(
"user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)] 
   
int MessageBox(void* hWnd, wchar_t* lpText, wchar_t* lpCaption,  
                  unsigned 
int uType); 
}
 
 
int main( ) 
{ 
   SysWin32::MessageBox( 
0, L"Hello world!", L"Greetings"0 ); 
}

 

主要注意包含 DllImport 的代码行。此代码行根据参数值通知编译器,使之声明位于 User32.dll 中的函数并将签名中出现的所有字符串(如参数或返回值)视为 Unicode 字符串。如果缺少 EntryPoint 参数,则默认值为函数名。另外,由于 CharSet 参数指定 Unicode,因此公共语言运行库将首先查找称为 MessageBoxW(有 W 是因为 Unicode 规范)的函数。如果运行库未找到此函数,它将根据调用约定查找 MessageBox 以及相应的修饰名。受支持的调用约定只有 __cdecl  __stdcall

当调用用户定义的 DLL 中所包含的函数时,有必要将 extern "C" 添加在 DLL 函数声明之前,如下所示:

 

// The function declaration in SampleDLL.h file 
extern "C" SAMPLEDLL_API int fnSampleDLL(void);

 

有关受支持的其他参数值的更多信息,请参见 DllImport

将非结构化参数由托管封送处理为非托管

除使用上述方法外,还可以使用另一种方法将托管参数(来自托管应用程序)封送处理为非托管参数(在非托管 DLL 中)。

以下代码示例说明封送处理技术:

 

#using <mscorlib.dll> 
using namespace System; // To bring System::String in 
using namespace System::Runtime::InteropServices;  
// for DllImportAttribute 
namespace SysWin32 
{ 
   [DllImport(
"user32.dll", EntryPoint = "MessageBox", CharSet = Unicode)] 
   Int32 MessageBox( Int32 hWnd, String
* lpText, String* lpCaption,  
                     UInt32 uType ); 
}
 
 
int main( ) 
{ 
   SysWin32::MessageBox(
0, S"Hello world!", S"Greetings"0); 
}

 

完成实际的调用后,由于 CharSet 参数值的作用,所有参数字符串都自动转换为 wchar_t*。同样,所有 Int32 参数类型都转换为非托管 int,而 UInt32 参数类型转换为非托管 unsigned int

下表提供关于转换非托管和托管上下文的指导:

非托管代码 C++ 的托管扩展
int Int32
unsigned int UInt32
short Int16
char* 用于 [in] 参数的 String* (CharSet = Ansi),用于 [out] 参数或返回值的Text::StringBuilder*
wchar_t* 用于 [in] 参数的 String* (CharSet = Unicode),用于 [out] 参数或返回值的Text::StringBuilder*
函数指针(回调) 
限制:函数指针必须具有 __stdcall 调用约定,因为这是 DllImport 支持的唯一类型。
委托类型
数组(如 wchar_t*[]) 
限制:CharSet 参数仅应用于函数参数的根类型。因此,无论 CharSet 的值是什么,String* __gc[] 都将被封送处理为 wchar_t* []
相应类型的托管数组(如 String*__gc[]

将结构化类型由非托管封送处理为托管

除简单类型外,运行库还提供了一种机制,可以将简单结构由托管上下文封送处理为非托管上下文。简单结构不包含任何内部数据成员指针、结构化类型的成员或其他元素。

例如,本主题显示如何调用本机 DLL 中具有以下签名的函数:

 

#include <stdio.h> 
struct S 
{ 
   
char* str; 
   
int n; 
}
; 
 
int __cdecl func( struct S* p ) 
{ 
   printf( 
"%s\n", p->str ); 
   
return p->n; 
}

 

若要创建此函数的托管包装,请将 StructLayout 属性应用到调用类。此属性确定封送处理结构时结构的组织方式。若要确保以传统的 C 格式组织结构,请指定顺序布局 (LayoutKind::Sequential)。结果代码如下:

 

#using <mscorlib.dll> 
using namespace System; 
using namespace System::Runtime::InteropServices; 
 
// CharSet = Ansi(Unicode) means that everything that is a string  
// in this structure should be marshaled as Ansi(Unicode)  
// strings 
[StructLayout( LayoutKind::Sequential, CharSet=Ansi )] 
__gc 
class MS // To be compatible with the type in the native  
// code, this structure should have the members laid out in 
// the same order as those in the native struct 
{ 
public: 
   String
* m_str; 
   Int32 m_n; 
}
; 
 
[DllImport(
"some.dll")] 
Int32 func( MS
* ptr ); 
int main( ) 
{ 
   MS
* p = new MS; 
   p
->m_str = S"Hello native!"; 
   p
->m_n = 7; 
   Console::WriteLine(func(p)); 
// Should print 7 
}

 

也可以在托管应用程序中使用 __nogc 关键字,以确保不发生封送处理:

 

#include <stdlib.h> 
#include 
<string.h> 
#
using <mscorlib.dll> 
using namespace System; 
using namespace System::Runtime::InteropServices; 
__nogc 
class UMS 
{ 
public: 
   
char* m_str; 
   
int m_n; 
}
; 
[DllImport(
"some.dll")] 
Int32 func( UMS
* ptr ); 
int main( ) 
{ 
   UMS
* p = new UMS; 
   p
->m_str = strdup( "Hello native!" ); 
   p
->m_n = 7; 
   Console::WriteLine(func(p)); 
// Should print 7 
   free( p->m_str ); 
   delete p; 
}

 

第二个方案是:

 

#include <stdio.h> 
struct S 
{ 
   wchar_t
* str; 
   
int n; 
}
; 
int __cdecl func( struct S p ) 
{ 
   printf( 
"%S\n", p.str ); 
   
return p.n; 
}

 

注意参数是通过值传递的。若要在托管应用程序中包装此调用,请使用值而不是 __gc 类型。结果代码如下:

 

#using <mscorlib.dll> 
using namespace System; 
using namespace System::Runtime::InteropServices; 
[StructLayout( LayoutKind::Sequential, CharSet
=Unicode )] 
__value 
class VS 
{ 
public: 
   String
* m_str; 
   Int32 m_n; 
}
; 
[DllImport( 
"some.dll" )] 
Int32 func( VS ptr ); 
int main( ) 
{ 
   VS v; 
   v.m_str 
= S"Hello native!"; 
   v.m_n 
= 7; 
   Console::WriteLine(func(v)); 
// should print 7 also 
}

posted on 2009-02-14 13:15  Zihuatanejo  阅读(764)  评论(0编辑  收藏  举报

导航