1. 程式人生 > >C#委托實現C++ Dll中的回調函數

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

iostream 頭文件 ops set erro oid def ice 情況

很多的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,重新運行導致堆棧不對稱如下

技術分享

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