本主題說明 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
}
請參見
屬性演練