本主題說明 DllImport 屬性的常見用法。第一節(jié)討論使用 DllImport 從托管應(yīng)用程序調(diào)用本機(jī)代碼的優(yōu)點(diǎn)。第二節(jié)集中討論封送處理和 DllImport 屬性的各個方面。
從托管應(yīng)用程序調(diào)用非托管代碼
當(dāng)在托管應(yīng)用程序中重用現(xiàn)有的非托管代碼時,DllImport 屬性非常有用。例如,托管應(yīng)用程序可能需要調(diào)用非托管 WIN32 API。
下面的代碼示例說明此通用方案,此示例將調(diào)用 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 的代碼行。此代碼行根據(jù)參數(shù)值通知編譯器,使之聲明位于 User32.dll 中的函數(shù)并將簽名中出現(xiàn)的所有字符串(如參數(shù)或返回值)視為 Unicode 字符串。如果缺少 EntryPoint
參數(shù),則默認(rèn)值為函數(shù)名。另外,由于 CharSet
參數(shù)指定 Unicode,因此公共語言運(yùn)行庫將首先查找稱為 MessageBoxW(有 W 是因?yàn)?Unicode 規(guī)范)的函數(shù)。如果運(yùn)行庫未找到此函數(shù),它將根據(jù)調(diào)用約定查找 MessageBox 以及相應(yīng)的修飾名。受支持的調(diào)用約定只有 __cdecl 和 __stdcall。
當(dāng)調(diào)用用戶定義的 DLL 中所包含的函數(shù)時,有必要將 extern "C"
添加在 DLL 函數(shù)聲明之前,如下所示:
// The function declaration in SampleDLL.h file extern "C" SAMPLEDLL_API int fnSampleDLL(void);
有關(guān)受支持的其他參數(shù)值的更多信息,請參見 DllImport。
將非結(jié)構(gòu)化參數(shù)由托管封送處理為非托管
除使用上述方法外,還可以使用另一種方法將托管參數(shù)(來自托管應(yīng)用程序)封送處理為非托管參數(shù)(在非托管 DLL 中)。
以下代碼示例說明封送處理技術(shù):
#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); }
完成實(shí)際的調(diào)用后,由于 CharSet
參數(shù)值的作用,所有參數(shù)字符串都自動轉(zhuǎn)換為 wchar_t*。同樣,所有 Int32 參數(shù)類型都轉(zhuǎn)換為非托管 int,而 UInt32 參數(shù)類型轉(zhuǎn)換為非托管 unsigned int。
下表提供關(guān)于轉(zhuǎn)換非托管和托管上下文的指導(dǎo):
非托管代碼 | C++ 的托管擴(kuò)展 |
---|---|
int | Int32 |
unsigned int | UInt32 |
short | Int16 |
char* | 用于 [in] 參數(shù)的 String* (CharSet = Ansi),用于 [out] 參數(shù)或返回值的 Text::StringBuilder*。 |
wchar_t* | 用于 [in] 參數(shù)的 String* (CharSet = Unicode),用于 [out] 參數(shù)或返回值的 Text::StringBuilder*。 |
函數(shù)指針(回調(diào)) 限制:函數(shù)指針必須具有 __stdcall 調(diào)用約定,因?yàn)檫@是 DllImport 支持的唯一類型。 |
委托類型 |
數(shù)組(如 wchar_t*[]) 限制:CharSet 參數(shù)僅應(yīng)用于函數(shù)參數(shù)的根類型。因此,無論 CharSet 的值是什么,String* __gc[] 都將被封送處理為 wchar_t* []。 |
相應(yīng)類型的托管數(shù)組(如 String*__gc[]) |
將結(jié)構(gòu)化類型由非托管封送處理為托管
除簡單類型外,運(yùn)行庫還提供了一種機(jī)制,可以將簡單結(jié)構(gòu)由托管上下文封送處理為非托管上下文。簡單結(jié)構(gòu)不包含任何內(nèi)部數(shù)據(jù)成員指針、結(jié)構(gòu)化類型的成員或其他元素。
例如,本主題顯示如何調(diào)用本機(jī) DLL 中具有以下簽名的函數(shù):
#include <stdio.h> struct S { char* str; int n; }; int __cdecl func( struct S* p ) { printf( "%s\n", p->str ); return p->n; }
若要創(chuàng)建此函數(shù)的托管包裝,請將 StructLayout 屬性應(yīng)用到調(diào)用類。此屬性確定封送處理結(jié)構(gòu)時結(jié)構(gòu)的組織方式。若要確保以傳統(tǒng)的 C 格式組織結(jié)構(gòu),請指定順序布局 (LayoutKind::Sequential
)。結(jié)果代碼如下:
#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 }
也可以在托管應(yīng)用程序中使用 __nogc 關(guān)鍵字,以確保不發(fā)生封送處理:
#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; }
注意參數(shù)是通過值傳遞的。若要在托管應(yīng)用程序中包裝此調(diào)用,請使用值而不是 __gc 類型。結(jié)果代碼如下:
#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 }