dll文件在windows上通常是指动态链接库文件,但是在.Net平台上dll为托管代码,虽然同样是为了共享代码,但不再是传统意义上的动态链接库了。
在.NET中,引入了一个程序集的概念,指经由编译器编译得到的,供CLR进一步编译执行的那个中间产物,在WINDOWS系统中,它一般表现为.dll,或者是.exe的格式。因此,在.Net中dll文件为程序集,也叫类库,是托管代码,.Net可以像动态链接库一样引用它,但是非托管代码,如C程序等无法直接调用该dll。
本文总结了.Net平台下调用.dll类库和动态链接库(本地dll,native code)文件的基本方法。
在visual studio中引用类库非常方便,不仅可以直接浏览类库内的类和函数,还可以智能补全引用的函数。
反射提供了封装程序集、模块和类型的对象(Type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。
需要引入System.Reflection命名空间,具体步骤如下:
Assembly dllx = Assembly.Load("file.dll");
object obj=dllx.CreateInstance("dllClass");
//调用没有参数的方法
object result=obj.GetType().GetMethod("dllmethod").Invoke(obj,null);
//调用有参数的方法
object result=obj.GetType().GetMethod("dllmethod").Invoke(obj,a,b);
object可以用var代替。使用object的效率低,在以上代码中建议用var。
在C#中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都直接或间接从 Object 继承。可以将任何类型的值赋给 object 类型的变量。将值类型的变量转换为对象的过程称为“装箱”。将对象类型的变量转换为值类型的过程称为“拆箱”。
而Var则根据赋值的类型自动设置变量的类型,如:
var intinput=5;
等于int intinput =5
,var intinput ="5.5"
等于string intinput ="5.5"
var的使用情况
var s = “abcd”
形式,而不能是:var s; s = “abcd”;
需要引用InteropServices命名空间:
using System.Runtime.InteropServices;
.NET调用P/Invoke是完成这一任务的最常用方法。另一种方法是使用 Managed Extensions to C++ 来包装函数(在此不作介绍)。
例如,我们要调用kernel32.dll中的beep函数,该函数在MSDN中的原型为:
BOOL Beep(DWORD dwFreq, DWORD dwDuration)
在.NET调用时需要编写这一原型,因此需要把数据类型转换为相应的.NET类型。
以C#为例,由于DWORD是4字节的整数,因此可以用int或uint作为C#对应类型。bool类型与Bool对应。因此可以用C#编写该函数原型如下:
public static extern bool Beep(int frequency, int duration);
使用extern指明该函数在别处。此原型将告诉运行时如何调用函数;现在我们在原型中添加DllImport属性告诉运行时在何处找到该函数。
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
需要在程序声明中使用System.Runtime.InteropServices命名空间
DllImport 允许您调用 Win32 中的任何代码。
在C#中,函数只支持__stdcall的调用方式。如果要调__cdecl的c/C++函数,要在DllImport里面指定 CallingConvention = CallConvention.Cdecl。
Win32 Types | Specification | CLR Type |
---|---|---|
char, INT8, SBYTE, CHAR |
8-bit signed integer | System.SByte |
short, short int, INT16, SHORT |
16-bit signed integer | System.Int16 |
int, long, long int, INT32, LONG32, BOOL, INT |
32-bit signed integer | System.Int32 |
__int64, INT64, LONGLONG |
64-bit signed integer | System.Int64 |
unsigned char, UINT8, UCHAR, BYTE |
8-bit unsigned integer | System.Byte |
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR, __wchar_t |
16-bit unsigned integer | System.UInt16 |
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT |
32-bit unsigned integer | System.UInt32 |
unsigned __int64, UINT64, DWORDLONG, ULONGLONG |
64-bit unsigned integer | System.UInt64 |
float, FLOAT |
Single-precision floating point | System.Single |
double, long double, DOUBLE |
Double-precision floating point | System.Double |
__stdcall
,这是C#的默认调用方式,不用显式声明。如果要调用的函数参数是指针或是地址变量,怎么办?
对于这种情况可以使用C#提供的非安全代码来进行解决,但是,毕竟是非托管代码,垃圾资源处理不好的话对应用程序是很不利的。所以还是使用C#提供的ref以及out修饰字比较好。
例如:
int __stdcall FunctionName(unsigned char ¶m1, unsigned char *param2)
在C#中对其进行调用的方法是:
[DllImport("file.dll")]
public static extern int FunctionName(ref byte param1, ref byte param2);
看到这,可能有人会问,&是取地址,*是传送指针,为何都只用ref就可以了呢?一种可能的解释是ref是一个具有重载特性的修饰符,会自动识别是取地址还是传送指针。
在实际的情况中,我们利用参数传递地址更多还是用在传送数组首地址上。如:
byte[] param1 = new param1(6);
在这里我们声明了一个数组,现在要将其的首地址传送过去,只要将param1数组的第一个元素用ref修饰。具体如下:
[DllImport("file.dll")]
static extern int FunctionName(ref byte param1[1], ref byte param2);
因为C#中使用DllImport是不能像动态load/unload assembly那样,所以只能借助API函数了。在kernel32.dll中,与动态库调用有关的函数包括[3]:
它们的原型分别是:
现在,我们可以用IntPtr hModule=LoadLibrary(“Count.dll”);来获得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);来获得函数的入口地址。
但是,知道函数的入口地址后,怎样调用这个函数呢?因为在C#中是没有函数指针的,没有像C++那样的函数指针调用方式来调用函数,所以我们得借助其它方法。经过研究,发现我们可以通过结合使用System.Reflection.Emit及System.Reflection.Assembly里的类和函数达到我们的目的。
详见《C#程序实现动态调用DLL的研究》一文。
.net CLR在运行时寻找正确的Assembly(类库dll也是Assembly),Net提供了搜索算法,可以根据.config文件添加自定义搜索路径。
搜索顺序如下:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="myAssembly"
publicKeyToken="32ab4ba45e0a69a1"
culture="neutral" />
<codeBase version="2.0.0.0"
href="om/myAssembly.dll"/>
dependentAssembly>
assemblyBinding>
runtime>
configuration>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="bin;bin2\subbin;bin3"/>
assemblyBinding>
runtime>
configuration>
OK,CLR就是根据上面的顺序从1到4进行搜索Assembly的。如果没有搜索到指定版本的类库DLL,则程序会抛出异常,提示:DLL文件无法找到。