from:https://blog.csdn.net/ferrycooper/article/details/63261771
很多的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,重新运行导致堆栈不对称如下