永发信息网

如何理解C#加载DLL的顺序

答案:1  悬赏:60  手机版
解决时间 2021-01-25 11:50
如何理解C#加载DLL的顺序
最佳答案
[C#动态调用C++编写的DLL函数]
by jingzhongrong 2008-05-08 动态加载DLL需要使用Windows API函数:LoadLibrary、GetProcAddress以及FreeLibrary。我们可以使用DllImport在C#中使用这三个函数。

[DllImport("Kernel32")]
public static extern int GetProcAddress(int handle, String funcname);

[DllImport("Kernel32")]
public static extern int LoadLibrary(String funcname);

[DllImport("Kernel32")]
public static extern int FreeLibrary(int handle);

当我们在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
{
///
/// API LoadLibrary
///
[DllImport("Kernel32")]
public static extern int LoadLibrary(String funcname);

///
/// API GetProcAddress
///
[DllImport("Kernel32")]
public static extern int GetProcAddress(int handle, String funcname);

///
/// API FreeLibrary
///
[DllImport("Kernel32")]
public static extern int FreeLibrary(int handle);

///
///通过非托管函数名转换为对应的委托, by jingzhongrong
///
///通过LoadLibrary获得的DLL句柄
///非托管函数名
///对应的委托类型
///委托实例,可强制转换为适当的委托类型
public static Delegate GetFunctionAddress(int dllModule, string functionName, Type t)
{
int address = GetProcAddress(dllModule, functionName);
if (address == 0)
return null;
else
return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);
}

///
///将表示函数地址的IntPtr实例转换成对应的委托, by jingzhongrong
///
public static Delegate GetDelegateFromIntPtr(IntPtr address, Type t)
{
if (address == IntPtr.Zero)
return null;
else
return Marshal.GetDelegateForFunctionPointer(address, t);
}

///
///将表示函数地址的int转换成对应的委托,by jingzhongrong
///
public static Delegate GetDelegateFromIntPtr(int address, Type t)
{
if (address == 0)
return null;
else
return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);
}
}

通过这个类,我们这样调用DLL:

1、声明相应的委托(正确声明很重要,否则不能调用成功,后面有详细介绍)。

2、加载DLL:
int hModule = DLLWrapper.LoadLibrary(dllFilePath);
if (hModule == 0)
return false;

3、获取相应的委托实例:
FOO foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));
if (foo == null)
{
DLLWrapper.FreeLibrary(hModule);
return false;
}

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)]
struct StructVersionInfo
{
public int MajorVersion;
public int MinorVersion;
}
另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在C#中声明为class:
[StructLayout(LayoutKind.Sequential)]
class StructVersionInfo
{
public int MajorVersion;
public int MinorVersion;
}

对应C++中的声明:
typedef struct _VERSION_INFO
{
int MajorVersion;
int MinorVersion;
} VERSION_INFO, *PVERSION_INFO;

如果结构中使用到了字符串,最好应指定相应的字符集:
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]

部分常用的声明对应关系(在结构中):
C++:字符串数组
wchar_t Comments[120];
C#:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 120)]
public string Comments;

C++:结构成员
VERSION_INFO ver;
C#
publicStructVersionInfo ver;

C++:函数指针声明
PFOO pFoo; //具体声明见文章前面部分
C#:
publicIntPtr pFoo; //也可以为 public int pFoo;
//不同的声明方法可以使用上面DLLWrapper类的相应函数获取对应的委托实例

如果在结构中使用到了union,那么可以使用FieldOffset指定具体位置。

3、委托的声明:

当C++编写的DLL函数需要通过指针传出将一个结构:如以下声明:
void getVersionInfo(VERSION_INFO *ver);
对于在C#中声明为class的结构(当VERSION_INFO声明为class)
delegate voidgetVersionInfo(VERSION_INFO ver);
如果结构声明为struct,那么应该使用如下声明:
delegate voidgetVersionInfo(refVERSION_INFO ver);
注意:应该使用ref关键字。

如果DLL函数需要传入一个字符串,比如这样:
BOOL __stdcall jingzhongrong1(const wchar_t* lpFileName, int* FileNum);
那么使用委托来调用函数的时候应该在C#中如下声明委托:
delegate bool jingzhongrong1(
[MarshalAs(UnmanagedType.LPWStr)]String FileName,
ref int FileNum);
注意:应该使用[MarshalAs(UnmanagedType.LPWStr)]和String进行声明。

如果要在DLL函数中传出一个字符串,比如这样:
void __stdcall jingzhongrong2(
wchar_t* lpFileName, //要传出的字符串
int* Length);
那么我们如下声明委托:
//使用委托从非托管函数的参数中传出的字符串,
//应该这样声明,并在调用前为StringBuilder预备足够的空间
delegate void jingzhongrong2(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileName,
ref int Length,
);
在使用函数前,应先为StringBuilder声明足够的空间用于存放字符串:
StringBuilder fileName = new StringBuilder(FileNameLength);
我要举报
如以上问答信息为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
大家都在看
急急 求一个元旦晚会小游戏
城市便捷酒店停车场(出入口)(东二环路92号附
北溟鱼黑鲨鱼竿好不好用,这个鱼竿的质量怎么
有代表性的福建畲族舞蹈有()。
我想了解下,我老公是山东的我是河北保定的,
谭维维有什么歌好听
观光酒店停车场(漓江路)地址在什么地方,我要
黄海大战后,清政府的战略思想是A. 主动迎敌B
如何用ps制作假微信红包
【曲姓】曲姓读音字典上是一声但是应该读三声
我的世界服务器NPC魔棒如何使用?
小米增强版手机下载视频途径在哪里设置
百分之三是多少
我的电脑cpu脚针断了一根,现在能开机,有时
桂林医学院停车场(东城路与环城北二路交叉口
推荐资讯
下列常见的实践方法,不属于无性生殖的是CA.
【身份证明】身份证明的英语表达IdentifyCard
【In some countries ,—— are called pubil
晚上去西塘景区看夜景还需要门票吗
隋炀帝功大于过,还是过大于功
【有答案的考眼力图片】小学三年级语文寒假作
【直流电路】直流电路中三个基本物理量是什么
容声冰箱bcd-180e/ds多少钱
亚马逊欧洲站死了美国站也会死吗
吉隆花园停车场地址在什么地方,想过去办事
中石油加油站(善通公路店)地址好找么,我有些
急需《聊斋志异鸲鹆》的翻译
正方形一边上任一点到这个正方形两条对角线的
阴历怎么看 ?