最近的项目中,因为一些原因,需要C#调用非托管(这里为C++)的动态库。网上喜闻乐见的方式是采用静态(DllImport)方式进行调用。偶然在园子里看到可以用动态(LoadLibrary,GetProcAddress,FreeLibrary)方式调用非托管动态库,于是就想着比较一下静态和动态方式的性能(主要想用运行时间来体现)。
以下为源码:
1.主程序源码:
1 using System; 2 using System.Diagnostics; 3 using System.Text; 4 using System.Threading; 5 6 namespace DllImportDemo 7 { 8 internal class Program 9 { 10 11 private static void Main() 12 { 13 14 const int callCount = 100000; 15 Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; 16 Thread.CurrentThread.Priority = ThreadPriority.Highest; 17 var watch = new Stopwatch(); 18 watch.Start(); 19 while (watch.ElapsedMilliseconds < 2000) 20 { 21 22 } 23 watch.Stop(); 24 watch.Restart(); 25 Console.WriteLine("DllImportStatic will be called."); 26 for (var i = 0; i < callCount; i++) 27 { 28 DllImportStatic(); 29 } 30 watch.Stop(); 31 Console.WriteLine("It costs {0}ms when DllImportStatic is called {1} times.", 32 watch.ElapsedMilliseconds, callCount); 33 34 watch.Restart(); 35 Console.WriteLine("DllImportDynamic will be called."); 36 for (var i = 0; i < callCount; i++) 37 { 38 DllImportDynamic(); 39 } 40 watch.Stop(); 41 Console.WriteLine("It costs {0}ms when DllImportDynamic is called {1} times.", 42 watch.ElapsedMilliseconds, callCount); 43 Console.Read(); 44 } 45 46 private static void DllImportStatic() 47 { 48 IniHelper.ReadIniData("info", "v.productname", string.Empty, "./extends.properties"); 49 } 50 51 private static void DllImportDynamic() 52 { 53 ReadIniData("info", "v.productname", string.Empty, "./extends.properties"); 54 } 55 56 private delegate long GetPrivateProfileString(string section, string key, 57 string def, StringBuilder retVal, int size, string filePath); 58 59 private static string ReadIniData(string section, string key, string noText, string iniFilePath) 60 { 61 var invoker = new DllInvoker("kernel32.dll"); 62 63 var profileString = (GetPrivateProfileString)invoker.Invoke("GetPrivateProfileStringA", 64 typeof(GetPrivateProfileString)); 65 66 var valueItem = new StringBuilder(1024); 67 68 profileString(section, key, noText, valueItem, 1024, iniFilePath); 69 70 return valueItem.ToString(); 71 } 72 } 73 74 75 }
2.动态调用托管动态库源码(部分代码参考网络):
1 using System; 2 using System.Runtime.InteropServices; 3 4 namespace DllImportDemo 5 { 6 class DllInvoker 7 { 8 9 [DllImport("kernel32.dll")] 10 private extern static IntPtr LoadLibrary(String path); 11 12 [DllImport("kernel32.dll")] 13 private extern static IntPtr GetProcAddress(IntPtr lib, String funcName); 14 15 [DllImport("kernel32.dll")] 16 private extern static bool FreeLibrary(IntPtr lib); 17 18 private readonly IntPtr _hLib; 19 20 public DllInvoker(string dllPath) 21 { 22 _hLib = LoadLibrary(dllPath); 23 } 24 25 ~DllInvoker() 26 { 27 FreeLibrary(_hLib); 28 } 29 30 /// <summary> 31 /// 将要执行的函数转换为委托 32 /// </summary> 33 /// <param name="apiName"></param> 34 /// <param name="type">委托类型</param> 35 /// <returns></returns> 36 public Delegate Invoke(string apiName, Type type) 37 { 38 var api = GetProcAddress(_hLib, apiName); 39 return Marshal.GetDelegateForFunctionPointer(api, type); 40 } 41 } 42 }
3.静态调用动态库源码(部分代码参考网络)
1 using System; 2 using System.IO; 3 using System.Runtime.InteropServices; 4 using System.Text; 5 6 namespace DllImportDemo 7 { 8 public class IniHelper 9 { 10 #region API函数声明 11 12 /// <summary> 13 /// ini文件读取函数 14 /// </summary> 15 /// <param name="section">ini文件节点名称</param> 16 /// <param name="key">节点下面项的key值</param> 17 /// <param name="def">项key对应值的默认值,如果通过项key查找到结果,返回该结果; 18 /// 如果未找到,返回指定的默认值</param> 19 /// <param name="retVal">字符串缓冲区,即返回结果的存储区</param> 20 /// <param name="size">字符串缓冲区初始大小</param> 21 /// <param name="filePath">ini文件路径</param> 22 /// <returns>返回取得字符串缓冲区的长度</returns> 23 [DllImport("kernel32")] 24 private static extern long GetPrivateProfileString(string section, string key, 25 string def, StringBuilder retVal, int size, string filePath); 26 27 #endregion 28 29 #region 读Ini文件 30 31 public static string ReadIniData(string section, string key, string noText, string iniFilePath) 32 { 33 if (File.Exists(iniFilePath)) 34 { 35 var temp = new StringBuilder(1024); 36 37 GetPrivateProfileString(section, key, noText, temp, 1024, iniFilePath); 38 39 return temp.ToString(); 40 } 41 return String.Empty; 42 } 43 44 #endregion 45 } 46 }
4.运行结果:
疑问:
从表面来看,读取ini文件的静态方式,只采用了一次[DllImport],动态调用时采用了两次[DllImport](FreeLibrary在析构函数中调用),按道理静态方式执行速度会快一些,但结果却相反,不知道是理解的问题还是其它什么原因?
[DllImport("kernel32")] private static extern long GetPrivateProfileString(string section, string key,string def, StringBuilder retVal, int size, string filePath);