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