C++C#相互調函數指針

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章