前面讲述了两种采用平台调用的技术,分别是动态平台调用 part 1,动态平台调用 part 2
上面两篇文章介绍了两种不同的进行动态平台调用的方法。这两种方法也是在.NET Framework 1.0和1.1下进行动态平台调用的主要方法。从.NET Framework 2.0起,引入了一个能够用于进行动态平台调用的新技术,那就是Marshal.GetDelegateForFunctionPointer。使用这种方法动态加载非托管DLL并调用其中的非托管函数,其主要步骤如下:
(1) 为非托管函数定义一个委托;
(2) 使用Win32 API函数LoadLibrary加载需要调用的非托管DLL;
(3) 使用Win32 API函数GetProcAddress获得非托管函数的地址;
(4) 使用Marshal.GetDelegateForFunctionPointer将上面获得的函数地址封送到第一步定义的委托;
(5) 使用代理调用函数,获得结果;
(6) 使用Win32 API函数FreeLibrary释放之前加载的非托管DLL。
下面的代码演示了采用上面所介绍的方法,对非托管函数进行动态平台调用的过程。
class DynamicPInvokeViaDelegate
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate int MultiplyDelegate(int factorA, int factorB);
public static void Test()
{
string entryPoint = "_Multiply@8";
string currentDirectory =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string dllPath = Path.Combine(currentDirectory,
@"nativelibfordynamicpinvoke\NativeLibForDynamicPInvoke.dll");
//动态加载需要平台调用的非托管DLL
IntPtr dllAddr = Win32API.LoadLibrary(dllPath);
if (dllAddr == IntPtr.Zero)
{
throw new DllNotFoundException(string.Format("Can not load {0}, please check.",
dllPath));
}
//获得需要调用的函数的地址
IntPtr procAddr = Win32API.GetProcAddress(dllAddr, entryPoint);
if (procAddr == IntPtr.Zero)
{
throw new EntryPointNotFoundException(
string.Format("Can not find entry point \"{0}\" in dll \"{1}\", please check.",
entryPoint, dllPath));
}
//使用代理来封送函数指针
MultiplyDelegate multiplyDelegate =
(MultiplyDelegate)Marshal.GetDelegateForFunctionPointer(
procAddr, typeof(MultiplyDelegate));
//调用非托管函数
int factorA = 100, factorB = 8;
int result = multiplyDelegate(factorA, factorB);
//释放加载的DLL
bool isFree = Win32API.FreeLibrary(dllAddr);
if (isFree)
{
Console.WriteLine("Successfully free {0}.",
Path.GetFileName(dllPath));
}
else
{
throw new Exception(string.Format("Can not free {0}.",
Path.GetFileName(dllPath)));
}
//打印结果
Console.WriteLine(string.Format("{0} * {1} = {2} ", factorA, factorB, result));
Console.WriteLine("Press any key to exit.");
Console.Read();
}
}
在上面的代码中,需要特别注意粗体部分。在为非托管函数定义委托时,委托的参数类型和返回值类型必须同将要调用的非托管函数完全匹配。而委托的名称可以不同。在定义委托时,可以给其加上UnmanagedFunctionPointer属性,也可以不加,采用默认值。通过设置UnmanagedFunctionPointer的CallingConvention属性,CharSet、SetLastError等字段,可以控制作为非托管函数指针传入或传出非托管代码的委托签名的封送行为。由于UnmanagedFunctionPointer和Marshal.GetDelegateForFunctionPointer都是.NET Framework 2.0以上版本才有的特性,因此这种动态平台调用的方法只适合.NET Framework 2.0以上版本。
此外,在使用GetProcAddress函数获得要调用的非托管函数的地址时,传递的函数名必须是经过重整后的名称。由于本示例所采用的非托管DLL在导出函数时,使用了extern "C",并采用了__stdcall这个调用约定,因此最终导出的函数名就会被重整成_Multiply@8。这样就要求在使用GetProcAddress方法获得非托管函数的地址时,必须传递同非托管DLL导出的实际函数名完全相同的名称。
运行Test方法进行测试,同样也能获得完全相同的结果:
100 * 8 = 800 Press any key to exit. |