按照之前对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不匹配,也是醉了。