C#委托實現C++ Dll中的回調函數


很多的Dll都是C和C++寫的,那么如果C#想要調用Dll中的函數怎么辦,尤其是Dll函數其中一個參數是函數指針的,即里面有回掉函數的用C#怎么實現?

C中的回掉函數在C#中有中特殊的處理方式叫委托,即要實現的回掉函數委托給另一個和它返回值類型以及函數參數類型、數量一樣的方法來實現。

 

一、新建項目Visual C++  Win32控制台應用,工程名為CcreateDll,解決方案名為Dlltest

 

確定—>下一步

 

 

應用程序類型選Dll—>完成

 

 

新建頭文件Ccreate.h,聲明導出函數,其中API_DECLSPEC int CallPFun(addP callback, inta, int b) 第一個參數為函數指針,內容如下:

 

#pragma once  
  
#ifndef Ccreate_H_  
#define Ccreatel_H_  
  
typedef  int(*addP)(int, int);  
  
#ifdef _EXPORTING   
#define API_DECLSPEC extern "C" _declspec(dllexport)   
#else   
#define API_DECLSPEC  extern "C" _declspec(dllimport)   
#endif  
  
API_DECLSPEC int Add(int plus1, int plus2);  
API_DECLSPEC int mulp(int plus1, int plus2);  
API_DECLSPEC int CallPFun(addP callback, int a, int b);  
  
#endif  

 

 

頭文件有了,在CcreateDll.cpp中include頭文件,並實現相關函數。Ccreate.cpp如下

// CcreateDll.cpp : 定義 DLL 應用程序的導出函數。  
//  
  
#include "stdafx.h"  
#include <iostream>   
#include "Ccreate.h"  
  
using namespace std;  
  
int Add(int plus1, int plus2)  
{  
    int add_result = plus1 + plus2;  
    return add_result;  
}  
int mulp(int plus1, int plus2)  
{  
    int add_result = plus1 * plus2;  
    return add_result;  
}  
  
int CallPFun(int(*callback)(int, int), int a, int b) {  
    return callback(a, b);  
}  

 

 

函數CallPFun實際就是傳入函數指針及其參數,內部直接調用函數指針。

 

Release模式下生成CcreateDll工程

 

 

生成成功后在解決方案目錄的Release文件夾下會看到生成的CcreateDll.dll,使用Dll查看工具可以看到三個導出函數。

 

 

二、新建C#控制台工程CsharpCallDll實現調用Dll並使用委托實現回掉。

 

 

CsharpCallDll工程Program.cs如下:

 

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Runtime.InteropServices;  
  
namespace CsharpCallDll  
{  
    public class Program  
    {  
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]  
        public delegate int DllcallBack(int num1, int num2);  
  
        [DllImport(@"../../../Release/CcreateDll.dll", EntryPoint = "Add", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]  
        extern static int Add(int a, int b);  
  
        [DllImport(@"../../../Release/CcreateDll.dll", EntryPoint = "mulp", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]  
        extern static int mulp(int a, int b);  
  
        [DllImport(@"../../../Release/CcreateDll.dll", EntryPoint = "CallPFun", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]  
        public extern static int CallPFun(DllcallBack pfun, int a, int b);  
        //[MarshalAs(UnmanagedType.FunctionPtr)]  
        static void Main(string[] args)  
        {  
            int a = 3;  
            int b = 4;  
            int result;  
            DllcallBack mycall;  
            mycall = new DllcallBack(Program.CsharpCall);  
            result = Add(a, b);  
            Console.WriteLine("Add 返回{0}", result);  
            result = mulp(a, b);  
            Console.WriteLine("mulp 返回{0}", result);  
            result = CallPFun(mycall, a, b);  
            Console.WriteLine("dll回掉 返回{0}", result);  
            Console.ReadLine();  
        }  
  
        public static int CsharpCall(int a, int b)  
        {  
            return a * a + b * b;  
        }  
    }  
}  

 

 

通過DllImport導入相應的Dll並聲明Dll中的導出函數,CcreateDll.dll中導出函數CallPFun有三個參數,原型為

 

int CallPFun(int(*callback)(int, int), int a, int b) {  
    return callback(a, b);  
   }  
   

 

參數1為一個帶兩個int參數的返回值為int型的函數指針,這里聲明一個委托

public delegate int DllcallBack(int num1, intnum2);

該委托可以指向任何帶兩個int型參數且返回值為int型的方法,這里的CsharpCall方法可以看作是回掉函數的實現。

 

public static int CsharpCall(int a, int b)  
   {  
            return a * a + b * b;  
   }  
   

 

通過        DllcallBack mycall;

           mycall = new DllcallBack(Program.CsharpCall);

  把實際要完成的工作交給CsharpCall去完成。

    運行CsharpCallDll,結果如下:

  

 

是不是實現了C#委托實現回掉

 

最后還有如果聲明委托時在public delegate int DllcallBack(int num1, int num2);上面沒有[UnmanagedFunctionPointer(CallingConvention.Cdecl)]這一句,那么程序將不能實現回調,函數只能運行一次,第二次運行時將會出現System.AccessViolationException異常,如下

 

 

還有Dll調用約定,CallingConvention.有五種調用方式

CallingConvention= CallingConvention.StdCall

CallingConvention= CallingConvention.Cdecl

CallingConvention= CallingConvention.FastCall

CallingConvention= CallingConvention.ThisCall

CallingConvention= CallingConvention.Winapi

到底使用哪種方式,網上有說"Bydefault, C and C++ use cdecl - but marshalling uses stdcall to match theWindows API."即默認情況下,C和C++使用的Cdecl調用,但編組使用StdCall調用匹配的Windows API,對於FastCall、ThisCall、Winapi這三種調用方式尚不清楚。

這里將CallingConvention= CallingConvention.Cdecl改成CallingConvention = CallingConvention.StdCall,重新運行導致堆棧不對稱如下

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM