按照之前對C++函數指針的理解,C++函數指針爲內存地址,可以用int或者intptr_t保存地址信息,在需要調用時候再轉換成相應的函數指針。委託作爲C#的指針實現形式,那麼理論上可以用C#的IntPtr類型接C++返回的intptr_t地址後在轉換爲C#的委託對象。然後通過委託對象執行函數調用。理論上也可以把C#的函數轉換成IntPtr傳遞給C++,C++得到intptr_t後把地址轉換成相應的函數指針執行函數調用。
首先實現C++的dll庫代碼
提供的申明頭
#include <string>
using std::string;
//求和
extern "C" __declspec(dllexport) int Add(int &a,int &b);
//求和1
extern "C" __declspec(dllexport) int Add1(int a, int b);
//兩個浮點數相加
extern "C" __declspec(dllexport) double DoubleAdd(double a, double b);
//兩個浮點數相減
extern "C" __declspec(dllexport) double DoubleJian(double a, double b);
//得到操作兩個數的函數指針函數指針
extern "C" __declspec(dllexport) intptr_t GetTowNumberRes(int funName, intptr_t callBack);
//回調C#Handder的申明,一定要加_stdcall,不然會回調後C#程序退出
int _declspec(dllexport)_stdcall CallBackCSharp(char *);
實現
#include "MyDLL.h"
#include <iostream>
using namespace std;
//求和
int Add(int &a,int &b)
{
return a+b;
}
//求和1
int Add1(int a, int b)
{
return a + b;
}
//兩個浮點數相加
double DoubleAdd(double a,double b)
{
return a + b;
}
//兩個浮點數相減
double DoubleJian(double a, double b)
{
return a - b;
}
//得到操作兩個數的函數指針函數指針
intptr_t GetTowNumberRes(int funName, intptr_t callBack)
{
decltype(CallBackCSharp)* callHander = NULL;
if (callBack != 0)
{
callHander = (decltype(CallBackCSharp)*)callBack;
}
if (callHander != NULL)
{
int ret=callHander("C#你好,我是C++,我收到你請求返回指針請求,馬上給你取指針!");
}
//按類別返回相應函數指針
if (funName == 1)
{
return (intptr_t)DoubleAdd;
}
//按類別返回相應函數指針
else if (funName == 2)
{
return (intptr_t)DoubleJian;
}
else
{
return NULL;
}
}
C#調用方面
/// <summary>
/// 得到操作兩個浮點數的方法指針
/// </summary>
/// <param name="hModule"></param>
/// <param name="lpProcName"></param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("MyDLL.dll")]
private static extern IntPtr GetTowNumberRes(int funName, IntPtr callBack);
/// <summary>
/// 操作兩個double返回結果的委託
/// </summary>
/// <param name="a">數據a</param>
/// <param name="b">數據b</param>
/// <returns>返回值</returns>
public delegate double GetTowNumberResDelegate(double a, double b);
/// <summary>
/// 回調方法
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
public delegate int CallBackCSharpDelegate(string msg);
/// <summary>
/// 測試指針和委託
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnPointAndDelegate_Click(object sender, EventArgs e)
{
//返回加方法指針
int caseInt = 1;
//得到回調委託的指針
IntPtr callBackHander = Marshal.GetFunctionPointerForDelegate((Delegate)(CallBackCSharpDelegate)ShowInfo);
//得到操作兩個浮點數的指針
IntPtr hander = GetTowNumberRes(caseInt, callBackHander);
//轉換委託
GetTowNumberResDelegate callAdd = (GetTowNumberResDelegate)InvokeDllUtil.GetDelegateFromIntPtr(hander, typeof(GetTowNumberResDelegate));
double num1 = 1.2;
double num2 = 11.3;
//調用
double retResAdd= callAdd(num1, num2);
//得到減方法指針
caseInt = 2;
//得到操作兩個浮點數的指針
IntPtr handerJian = GetTowNumberRes(caseInt, callBackHander);
//轉換委託
GetTowNumberResDelegate callJian = (GetTowNumberResDelegate)InvokeDllUtil.GetDelegateFromIntPtr(handerJian, typeof(GetTowNumberResDelegate));
//調用
double retResJian = callJian(num1, num2);
MessageBox.Show("DoubleAdd通過方法指針返回:" + retResAdd+ ",DoubleJian通過方法指針返回:" + retResJian);
}
/// <summary>
/// 顯示信息的回調方法
/// </summary>
/// <param name="msg">信息</param>
/// <returns></returns>
public int ShowInfo(string msg)
{
labInfo.Text = msg;
return 1;
}
封裝的工具類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Forms;
namespace DllInvokTest
{
///<summary NoteObject="Class">
/// [功能描述:動態調用dll的工具類] <para/>
/// [創建者:zlz] <para/>
/// [創建時間:2020年5月12日] <para/>
///<說明>
/// [說明:動態調用dll的工具類]<para/>
///</說明>
///<修改記錄>
/// [修改時間:本次修改時間]<para/>
/// [修改內容:本次修改內容]<para/>
///</修改記錄>
///<修改記錄>
/// [修改時間:本次修改時間]<para/>
/// [修改內容:本次修改內容]<para/>
///</修改記錄>
///</summary>
public class InvokeDllUtil
{
/// <summary>
/// 加載程序集
/// </summary>
/// <param name="lpLibFileName"></param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern long GetLastError();
/// <summary>
/// 加載程序集
/// </summary>
/// <param name="lpLibFileName"></param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr LoadLibrary(string lpLibFileName);
/// <summary>
/// 得到方法地址
/// </summary>
/// <param name="hModule"></param>
/// <param name="lpProcName"></param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
/// <summary>
/// 釋放程序集
/// </summary>
/// <param name="hLibModule"></param>
/// <returns></returns>
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr FreeLibrary(IntPtr hLibModule);
///<summary>
///通過非託管函數名轉換爲對應的委託, by jingzhongrong
///</summary>
///<param name="dllModule">Get DLL handle by LoadLibrary</param>
///<param name="functionName">Unmanaged function name</param>
///<param name="t">ManageR type對應的委託類型</param>
///<returns>委託實例,可強制轉換爲適當的委託類型</returns>
public static Delegate GetFunctionAddress(string fileName, string funName, Type t)
{
IntPtr libHandle = IntPtr.Zero;
try
{
//獲取函數地址
libHandle = LoadLibrary(fileName);
if (libHandle == IntPtr.Zero)
{
long err = GetLastError();
throw new Exception("加載程序集異常:" + err);
}
IntPtr procAddres = GetProcAddress(libHandle, funName);
if (procAddres == IntPtr.Zero)
{
long err = GetLastError();
throw new Exception("加載程序集裏方法異常:" + err);
}
else
{
return Marshal.GetDelegateForFunctionPointer(procAddres, t);
}
}
catch
{
return null;
}
finally
{
if (libHandle != IntPtr.Zero)
{
//釋放資源
FreeLibrary(libHandle);
}
}
}
/// <summary>
/// 將表示函數地址的IntPtr實例轉換成對應的委託
/// </summary>
/// <param name="address">指針地址</param>
/// <param name="t">類型</param>
/// <returns></returns>
public static Delegate GetDelegateFromIntPtr(IntPtr address, Type t)
{
if (address == IntPtr.Zero)
{
return null;
}
else
{
return Marshal.GetDelegateForFunctionPointer(address, t);
}
}
/// <summary>
/// 將表示函數地址的int轉換成對應的委託
/// </summary>
/// <param name="address">地址</param>
/// <param name="t">類型</param>
/// <returns></returns>
public static Delegate GetDelegateFromIntPtr(int address, Type t)
{
if (address == 0)
{
return null;
}
else
{
return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);
}
}
/// <summary>
/// 動態調用Windows DLL
/// </summary>
/// <param name="fileName">Dll文件名</param>
/// <param name="funName">待調用的函數名</param>
/// <param name="objParams">函數參數</param>
/// <param name="returnType">返回值</param>
/// <returns>調用結果</returns>
public static object CommonDllInvoke(string fileName, string funName, object[] objParams, Type returnType)
{
IntPtr libHandle = IntPtr.Zero;
try
{
//獲取函數地址
libHandle = LoadLibrary(fileName);
if (libHandle == IntPtr.Zero)
{
long err=GetLastError();
throw new Exception("加載程序集異常:"+ err);
}
IntPtr procAddres = GetProcAddress(libHandle, funName);
if (procAddres == IntPtr.Zero)
{
long err = GetLastError();
throw new Exception("加載程序集裏方法異常:" + err);
}
//獲取參數類型
Type[] paramTypes = new Type[objParams.Length];
for (int i = 0; i < objParams.Length; ++i)
{
paramTypes[i] = objParams[i].GetType();
}
//構建調用方法模型
AssemblyName asembyName = new AssemblyName();
asembyName.Name = "WinDllInvoke_Assembly";
AssemblyBuilder asembyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asembyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = asembyBuilder.DefineDynamicModule("WinDllInvoke");
MethodBuilder methodBuilder = moduleBuilder.DefineGlobalMethod("InvokeFun", MethodAttributes.Public | MethodAttributes.Static, returnType, paramTypes);
//獲取一個 ILGenerator ,用於發送所需的 IL
ILGenerator IL = methodBuilder.GetILGenerator();
for (int j = 0; j < paramTypes.Length; ++j)
{
//將參數壓入堆棧
if (paramTypes[j].IsValueType)
{
//By Value
IL.Emit(OpCodes.Ldarg, j);
}
else
{
//By Addrsss
IL.Emit(OpCodes.Ldarga, j);
}
}
// 判斷處理器類型
if (IntPtr.Size == 4)
{
IL.Emit(OpCodes.Ldc_I4, procAddres.ToInt32());
}
else if (IntPtr.Size == 8)
{
IL.Emit(OpCodes.Ldc_I8, procAddres.ToInt64());
}
else
{
throw new PlatformNotSupportedException("不支持的處理器類型!");
}
IL.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, returnType, paramTypes);
// 返回值
IL.Emit(OpCodes.Ret);
moduleBuilder.CreateGlobalFunctions();
// 取得方法信息
MethodInfo methodInfo = moduleBuilder.GetMethod("InvokeFun");
object retObj= methodInfo.Invoke(null, objParams);
// 調用方法,並返回其值
return retObj;
}
catch
{
return null;
}
finally
{
if (libHandle != IntPtr.Zero)
{
//釋放資源
FreeLibrary(libHandle);
}
}
}
}
}
效果
基於這個指針概念,雙方約定方法申明,傳輸IntPtr相互調用沒問題。試了整整一天才試通,VS調試模式老是報P/Invok不匹配,也是醉了。