C#動態(tài)調(diào)用C++編寫的DLL函數(shù)
動態(tài)加載
DLL
需要使用
Windows API
函數(shù):
LoadLibrary
、
GetProcAddress
以及
FreeLibrary
。我們可以使用
DllImport
在
C#
中使用這三個函數(shù)。
?
[DllImport("Kernel32")]
public
static
extern
int
GetProcAddress(inthandle, Stringfuncname);
?
[DllImport("Kernel32")]
public
static
extern
int
LoadLibrary(Stringfuncname);
?
[DllImport("Kernel32")]
public
static
extern
int
FreeLibrary(inthandle);
?
當我們在
C++
中動態(tài)調(diào)用
Dll
中的函數(shù)時,我們一般的方法是:
假設
DLL
中有一個導出函數(shù),函數(shù)原型如下:
BOOL
__stdcall foo(Object &object, LPVOID lpReserved);
?
1
、首先定義相應的函數(shù)指針:
typedef
BOOL (__stdcall *PFOO)(Object &object, LPVOID lpReserved);
?
2
、調(diào)用
LoadLibrary
加載
dll
:
HINSTANCE
hInst = ::LoadLibraryW(dllFileName);
?
3
、調(diào)用
GetProcAddress
函數(shù)獲取要調(diào)用函數(shù)的地址:
PFOO
foo = (PFOO)GetProcAddress(hInst,"foo");
if
(foo == NULL)
{
??? FreeLibrary(hInst);
??? return false;
}
?
4
、調(diào)用
foo
函數(shù):
BOOL
bRet = foo(object,(LPVOID)NULL);
?
5
、使用完后應釋放
DLL
:
FreeLibrary(hInst);
?
那么在
C#
中應該怎么做呢?方法基本上一樣,我們使用委托來代替
C++
的函數(shù)指針,通過
.NET Framework 2.0
新增的函數(shù)
GetDelegateForFunctionPointer
來得到一個委托的實例:
?
下面封裝了一個類,通過該類我們就可以在
C#
中動態(tài)調(diào)用
Dll
中的函數(shù)了:
?
public class
DLLWrapper
{
??? ///<summary>
??? /// API LoadLibrary
??? ///</summary>
??? [DllImport("Kernel32")]
??? publicstaticexternintLoadLibrary(Stringfuncname);
?
??? ///<summary>
??? /// API GetProcAddress
??? ///</summary>
??? [DllImport("Kernel32")]
??? publicstaticexternintGetProcAddress(inthandle, Stringfuncname);
?
??? ///<summary>
??? /// API FreeLibrary
??? ///</summary>
??? [DllImport("Kernel32")]
??? publicstaticexternintFreeLibrary(inthandle);
?
??? ///<summary>
??? ///
通過非托管函數(shù)名轉(zhuǎn)換為對應的委托
, by jingzhongrong
??? ///</summary>
??? ///<param name="dllModule">
通過
LoadLibrary
獲得的
DLL
句柄
</param>
??? ///<param name="functionName">
非托管函數(shù)名
</param>
??? ///<param name="t">
對應的委托類型
</param>
??? ///<returns>
委托實例,可強制轉(zhuǎn)換為適當?shù)奈蓄愋?/span>
</returns>
??? publicstaticDelegateGetFunctionAddress(intdllModule, stringfunctionName, Typet)
??? {
?????? intaddress = GetProcAddress(dllModule, functionName);
?????? if (address == 0)
?????????? returnnull;
?????? else
?????????? returnMarshal.GetDelegateForFunctionPointer(newIntPtr(address), t);
??? }
?
??? ///<summary>
??? ///
將表示函數(shù)地址的
IntPtr
實例轉(zhuǎn)換成對應的委托
, by jingzhongrong
??? ///</summary>
??? publicstaticDelegateGetDelegateFromIntPtr(IntPtraddress, Typet)
??? {
?????? if (address == IntPtr.Zero)
?????????? returnnull;
?????? else
?????????? returnMarshal.GetDelegateForFunctionPointer(address, t);
??? }
?
??? ///<summary>
??? ///
將表示函數(shù)地址的
int
轉(zhuǎn)換成對應的委托,by jingzhongrong
??? ///</summary>
??? publicstaticDelegateGetDelegateFromIntPtr(intaddress, Typet)
??? {
?????? if (address == 0)
?????????? returnnull;
?????? else
?????????? returnMarshal.GetDelegateForFunctionPointer(newIntPtr(address), t);
??? }
}
?
通過這個類,我們這樣調(diào)用
DLL
:
?
1
、聲明相應的委托(正確聲明很重要,否則不能調(diào)用成功,后面有詳細介紹)。
?
2
、加載
DLL
:
int
hModule
= DLLWrapper.LoadLibrary(dllFilePath);
if
(hModule == 0)
??? returnfalse;
?
3
、獲取相應的委托實例:
FOO
foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));
if
(foo == null)
{
??? DLLWrapper.FreeLibrary(hModule);
??? returnfalse;
}
?
4
、調(diào)用函數(shù):
foo(...);
?
5
、
.NET
并不能自動釋放動態(tài)加載的
DLL
,因此我們在使用完
DLL
后應該自己釋放
DLL
:
DLLWrapper
.FreeLibrary(hModule);
?
下面我們將就委托應如何聲明進行相應的討論,在實際操作過程中,我發(fā)現(xiàn)使用
DllImport
方法和動態(tài)調(diào)用方法兩者在
C#
中對
DLL
中函數(shù)原型的聲明是有些區(qū)別的,下面我介紹動態(tài)調(diào)用中委托的聲明:
?
1
、首先應該注意的是,
C++
中的類型和
C#
中類型的對應關系,比如
C++
中的
long
應該對應
C#
中的
Int32
而不是
long
,否則將導致調(diào)用結(jié)果出錯。
?
2
、結(jié)構(gòu)的聲明使用
StructLayout對結(jié)構(gòu)的相應布局進行設置,具體的請查看
MSDN:
?
使用
LayoutKind
指定結(jié)構(gòu)中成員的布局順序,一般可以使用
Sequential
:
??? [StructLayout(LayoutKind.Sequential)]
??? structStructVersionInfo
??? {
?????? publicintMajorVersion;
?????? publicintMinorVersion;
??? }
另外,如果單獨使用內(nèi)部類型沒有另外使用到字符串、結(jié)構(gòu)、類,可以將結(jié)構(gòu)在
C#
中聲明為
class
:
??? [StructLayout(LayoutKind.Sequential)]
??? classStructVersionInfo
??? {
?????? publicintMajorVersion;
?????? publicintMinorVersion;
??? }
?
對應
C++
中的聲明:
??? typedef
struct
_VERSION_INFO
??? {
??? ??? intMajorVersion;
??? ??? intMinorVersion;
??? } VERSION_INFO, *PVERSION_INFO;
?
如果結(jié)構(gòu)中使用到了字符串,最好應指定相應的字符集:
???
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
?
部分常用的聲明對應關系(在結(jié)構(gòu)中):
C++
:字符串數(shù)組
???
wchar_t
Comments[120];
C#
:
??? [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 120)]
??? publicstringComments;
?
C++
:結(jié)構(gòu)成員
??? VERSION_INFO
ver;
C#
???
public
StructVersionInfo
ver;
?
C++
:函數(shù)指針聲明
???
PFOO
pFoo;?//
具體聲明見文章前面部分
C#:
???
public
IntPtr
pFoo;?
//
也可以為
public int pFoo;
??????? //
不同的聲明方法可以使用上面
DLLWrapper
類的相應函數(shù)獲取對應的委托實例
?
如果在結(jié)構(gòu)中使用到了
union
,那么可以使用
FieldOffset
指定具體位置。
?
3
、委托的聲明:
?
當
C++
編寫的
DLL
函數(shù)需要通過指針傳出將一個結(jié)構(gòu):如以下聲明:
???
void
getVersionInfo(
VERSION_INFO
*ver);
對于在
C#
中聲明為
class
的結(jié)構(gòu)(當
VERSION_INFO
聲明為
class
)
???
delegate void
getVersionInfo
(
VERSION_INFO
ver);
如果結(jié)構(gòu)聲明為
struct
,那么應該使用如下聲明:
???
delegate void
getVersionInfo
(
ref
VERSION_INFO
ver);
注意:應該使用
ref
關鍵字。
?
?
如果
DLL
函數(shù)需要傳入一個字符串,比如這樣:
??? BOOL
__stdcall jingzhongrong1(constwchar_t* lpFileName, int* FileNum);
那么使用委托來調(diào)用函數(shù)的時候應該在
C#
中如下聲明委托:
??? delegatebooljingzhongrong1(
?????? [MarshalAs(UnmanagedType.LPWStr)]StringFileName,
?????? refintFileNum);
注意:應該使用
[MarshalAs(UnmanagedType.LPWStr)]
和
String
進行聲明。
?
?
如果要在
DLL
函數(shù)中傳出一個字符串,比如這樣:
??? void
__stdcall jingzhongrong2(
??? wchar_t* lpFileName,
//
要傳出的字符串
??? int*?Length);
那么我們?nèi)缦侣暶魑校?/span>
??? //
使用委托從非托管函數(shù)的參數(shù)中傳出的字符串,
??? //
應該這樣聲明,并在調(diào)用前為
StringBuilder
預備足夠的空間
??? delegatevoidjingzhongrong2(
?????? [MarshalAs(UnmanagedType.LPWStr)] StringBuilderlpFileName,
?????? refintLength,
??? );
在使用函數(shù)前,應先為
StringBuilder
聲明足夠的空間用于存放字符串:
???
StringBuilder
fileName = newStringBuilder(FileNameLength);
posted on 2008-09-02 11:56 死神 閱讀(1959) 評論(1) 編輯 收藏 所屬分類: C#學習