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