C#調用C++ 鏈接庫的方式分為靜態調用和動態調用這兩種方式。靜態調用之前的文章里面都有介紹,使用.net 提供的DllImport 導入相關的C++ 庫即可。請看之前的文章,https://www.cnblogs.com/zhangweizhong/p/8119340.html 。
今天介紹動態調用的方法。很多時候,Dll庫的目錄可能是變化的,或是有些場景,需要根據具體的情況,來動態加載這些Dll庫。這樣使用靜態調用的方式就很不方便,C#中我們經常通過配置動態的調用托管Dll,那么是不是也可以這樣動態調用C++動態鏈接呢?
只要通過LoadLibrary, GetProcess, FreeLibrary這幾個函數是可以動態調用動態鏈接的(它們包含在kernel32.dll中)。
原理
LoadLibrary ( string lpFileName):載入指定的動態鏈接庫,並將它映射到當前進程使用的地址空間。載入成功后即可訪問庫內保存的資源 , 除了LoadLibrary 方法,還有一個類似的 LoadLibraryEx 方法。
GetProcAddress (int hModule, string lpProcName):GetProcAddress函數檢索指定的動態鏈接庫(DLL)中的輸出庫函數地址。 如果函數調用成功,返回值是DLL中的輸出函數地址。 如果函數調用失敗,返回值是NULL。調用函數GetLastError ,得到具體的錯誤信息。
FreeLibrary ( int hModule) :釋放指定的動態鏈接庫,它們早先是用LoadLibrary API函數裝載的。
GetLastError() : 獲取錯誤信息
實現
1. 將kernel32中的幾個方法封裝成本地調用類 DLLWrapper
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Irisking.Basic.Util
{
/// <summary>
/// DLLWrapper
/// </summary>
internal class DLLWrapper
{
[DllImport("kernel32.dll")]
private static extern uint GetLastError();
/// <summary>
/// API LoadLibraryEx
/// </summary>
/// <param name="lpFileName"></param>
/// <param name="hReservedNull"></param>
/// <param name="dwFlags"></param>
/// <returns></returns>
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryEx", SetLastError = true)]
private static extern int LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);
/// <summary>
/// API GetProcAddress
/// </summary>
/// <param name="handle"></param>
/// <param name="funcname"></param>
/// <returns></returns>
[DllImport("Kernel32", EntryPoint = "GetProcAddress", SetLastError = true)]
public static extern int GetProcAddress(int handle, string funcname);
/// <summary>
/// API FreeLibrary
/// </summary>
/// <param name="handle"></param>
/// <returns></returns>
[DllImport("Kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
private static extern int FreeLibrary(int handle);
///<summary>
/// 通過非托管函數名轉換為對應的委托 , by jingzhongrong
///</summary>
///<param name="dllModule"> 通過 LoadLibrary 獲得的 DLL 句柄 </param>
///<param name="functionName"> 非托管函數名 </param>
///<param name="t"> 對應的委托類型 </param>
///<returns> 委托實例,可強制轉換為適當的委托類型 </returns>
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);
}
///<summary>
/// 將表示函數地址的 intPtr 實例轉換成對應的委托
///</summary>
public static Delegate GetDelegateFromIntPtr(IntPtr address, Type t)
{
if (address == IntPtr.Zero)
return null;
else
return Marshal.GetDelegateForFunctionPointer(address, t);
}
///<summary>
/// 將表示函數地址的 int 轉換成對應的委托
///</summary>
public static Delegate GetDelegateFromIntPtr(int address, Type t)
{
if (address == 0)
return null;
else
return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);
}
/// <summary>
/// 加載sdk
/// </summary>
/// <param name="lpFileName"></param>
/// <returns></returns>
public static int LoadSDK(string lpFileName)
{
if (File.Exists(lpFileName))
{
var hReservedNull = IntPtr.Zero;
var dwFlags = LoadLibraryFlags.LOAD_WITH_ALTERED_SEARCH_PATH;
var result = LoadLibraryEx(lpFileName, hReservedNull, dwFlags);
var errCode = GetLastError();
LogHelper.Info($"LoadSDK Result:{result}, ErrorCode: {errCode}");
return result;
}
return 0;
}
/// <summary>
/// 釋放sdk
/// </summary>
/// <param name="handle"></param>
/// <returns></returns>
public static int ReleaseSDK(int handle)
{
try
{
if (handle > 0)
{
LogHelper.Info($"FreeLibrary handle:{handle}");
var result = FreeLibrary(handle);
var errCode = GetLastError();
LogHelper.Info($"FreeLibrary Result:{result}, ErrorCode: {errCode}");
return 0;
}
return -1;
}
catch (Exception ex)
{
LogHelper.Error(ex);
return -1;
}
}
}
/// <summary>
/// LoadLibraryFlags
/// </summary>
public enum LoadLibraryFlags : uint
{
/// <summary>
/// DONT_RESOLVE_DLL_REFERENCES
/// </summary>
DONT_RESOLVE_DLL_REFERENCES = 0x00000001,
/// <summary>
/// LOAD_IGNORE_CODE_AUTHZ_LEVEL
/// </summary>
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,
/// <summary>
/// LOAD_LIBRARY_AS_DATAFILE
/// </summary>
LOAD_LIBRARY_AS_DATAFILE = 0x00000002,
/// <summary>
/// LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
/// </summary>
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,
/// <summary>
/// LOAD_LIBRARY_AS_IMAGE_RESOURCE
/// </summary>
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,
/// <summary>
/// LOAD_LIBRARY_SEARCH_APPLICATION_DIR
/// </summary>
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200,
/// <summary>
/// LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
/// </summary>
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000,
/// <summary>
/// LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
/// </summary>
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100,
/// <summary>
/// LOAD_LIBRARY_SEARCH_SYSTEM32
/// </summary>
LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800,
/// <summary>
/// LOAD_LIBRARY_SEARCH_USER_DIRS
/// </summary>
LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400,
/// <summary>
/// LOAD_WITH_ALTERED_SEARCH_PATH
/// </summary>
LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008
}
}
2. 使用DLLWrapper類動態讀取C++Dll,獲得函數指針,並且將指針封裝成C#中的委托。原因很簡單,C#中已經不能使用指針了,如下:
定義委托
UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)] public delegate int Delegate_IKUSBSDK_GetVersion([In] [Out] [MarshalAs(UnmanagedType.LPArray)] byte[] version);
3. 調用函數
//1. 加載sdk var sdkModule = DLLWrapper.LoadSDK(_route.DeviceA_PATH); // 2. 通過handle 找到相關的函數 Delegate_IKUSBSDK_GetVersion getVersion = (Delegate_IKUSBSDK_GetVersion)DLLWrapper.GetFunctionAddress(sdkModule, "IKUSBSDK_GetVersion", typeof(Delegate_IKUSBSDK_GetVersion)); var result = getVersion(version);
最后
通過如上例子,我們可以在C#中動態或者靜態的調用C++寫的代碼了。

