在工控行业中会遇到C#调用C++底层函数的情况,比如机械的运动控制底层提供了C++的DLL,而应用系统是C#语言编写的。
下面尝试编写简单C++函数生成DLL,以及C#调用C++函数
1 简单C++函数编写以及生成DLL
- 1.1 创建C++项目
- 1.2 目录结
- 1.3 头文件 stdafx.h 源码就以下 3行
#include <stdio.h> #include "stdlib.h" #include <tchar.h>
- 1.4 函数源码
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。而被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。
__declspec(dllexport)的目的是为了将对应的函数放入到DLL动态库中。
extern "C" __declspec(dllexport)加起来的目的是为了使用DllImport调用非托管C++的DLL文件。因为使用DllImport只能调用由C语言函数做成的DLL。
#include "stdafx.h" extern "C" __declspec(dllexport) int Add(int x, int y)
{ return x + y; } extern "C" __declspec(dllexport) int Sub(int x, int y) { return x - y; } extern "C" __declspec(dllexport) int Multiply(int x, int y) { return x * y; } extern "C" __declspec(dllexport) int Divide(int x, int y) { return x / y; }
- 1.5 修改项目属性为生成DLL
2. C# 调用C++函数
2.1 调用方式1 import dll
2.1.1 定义委托import dll的函数
调用函数可能报错:“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配
主要原因是c++和c#类型不一致导致的,解决办法是在委托的Dll import语句中加上这一句:
CallingConvention = CallingConvention.Cdecl
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace Com.Gaaban.Framwork.Common.Cplus { public class DemoDelegate { [DllImport("CplusDll.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int Add(int x, int y); [DllImport("CplusDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int Sub(int x, int y); [DllImport("CplusDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int Multiply(int x, int y); [DllImport("CplusDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int Divide(int x, int y); } }
2.1.2 调用
int ret1 = DemoDelegate.Add(1, 2);
2.2 方式二
2.2.1 加载DLL及获取函数指针工具类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.InteropServices; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace Com.Gaaban.Framwork.Common.Cplus 9 { 10 public class DllInvoke 11 { 12 [DllImport("kernel32.dll", SetLastError = true)] 13 private extern static IntPtr LoadLibrary(String dllPath); 14 15 [DllImport("kernel32.dll", SetLastError = true)] 16 static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags); 17 18 [DllImport("kernel32.dll", SetLastError = true)] 19 private extern static IntPtr GetProcAddress(IntPtr lib, string funcName); 20 21 [DllImport("kernel32.dll", SetLastError = true)] 22 private extern static bool FreeLibrary(IntPtr lib); 23 24 private IntPtr hLib; 25 26 public DllInvoke(string dllPath) 27 { 28 hLib = LoadLibrary(dllPath); 29 if (hLib == IntPtr.Zero) 30 { 31 hLib = LoadLibraryEx(dllPath, IntPtr.Zero, 8); 32 } 33 if (hLib == IntPtr.Zero) 34 { 35 throw new Exception(string.Format("LoadLibrary {0} Error {1}!", dllPath, Marshal.GetLastWin32Error())); 36 } 37 } 38 39 /// <summary> 40 /// 获取C++方法对应的C#委托实例 41 /// </summary> 42 /// <param name="funcName">C++方法名</param> 43 /// <param name="type">C#委托</param> 44 /// <returns></returns> 45 public Delegate GetDelegate(string funcName, Type type) 46 { 47 IntPtr api = GetProcAddress(hLib, funcName); 48 Delegate del = (Delegate)Marshal.GetDelegateForFunctionPointer(api, type); 49 return del; 50 } 51 52 /// <summary> 53 /// 获取C++方法对应的C#委托实例 54 /// </summary> 55 /// <param name="address">C++函数地址</param> 56 /// <param name="type">C#委托</param> 57 /// <returns></returns> 58 public Delegate GetDelegateFromIntPtr(IntPtr address, Type t) 59 { 60 if (address == IntPtr.Zero) 61 return null; 62 else 63 return Marshal.GetDelegateForFunctionPointer(address, t); 64 } 65 66 /// <summary> 67 /// 获取C++方法对应的C#委托实例 68 /// </summary> 69 /// <param name="address">C++函数地址</param> 70 /// <param name="type">C#委托</param> 71 /// <returns></returns> 72 public Delegate GetDelegateFromInt(Int32 address, Type t) 73 { 74 if (address == 0) 75 return null; 76 else 77 return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t); 78 } 79 } 80 }
2.2.2 声明委托
调用函数可能报错:“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配
主要原因是c++和c#类型不一致导致的,解决办法是在声明委托前面加上这一句:
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)] delegate int Add(int mid, int errid);
2.2.3 调用函数
DllInvoke inv = new DllInvoke(@"F:\Code\Com.Gaaban.Framwork.UI.Test\Bin\C++\CplusDll.dll"); Add add = (Add)inv.GetDelegate("Add", typeof(Add)); int ret = add(1, 2);