今天对通过C#工程来调用C++的dll做了一些尝试,记录一些收获与遇到问题。
C++ dll 代码:
/**my_dll.cpp**/
#include "UserApi.h"
#define PB_API __declspec(dllexport)
extern "C"
{
typedef PB_API int(*SumFunc)(int a, int b);
typedef PB_API int(*MinusFunc)(int a, int b);
typedef PB_API int(*MultiFunc)(int a, int b);
typedef PB_API int(*DivideFunc)(int a, int b);
typedef PB_API struct
{
SumFunc sum_func;
MinusFunc minus_func;
MultiFunc multi_func;
DivideFunc divide_func;
}FuncCallback;
static FuncCallback* s_func_callback;
PB_API void register_spi(FuncCallback* func_callback)
{
s_func_callback = func_callback;
}
PB_API int call_sum(int a, int b)
{
return s_func_callback->sum_func(a, b);
}
PB_API int call_minus(int a, int b)
{
return s_func_callback->minus_func(a, b);
}
PB_API int call_multi(int a, int b)
{
return s_func_callback->multi_func(a, b);
}
PB_API int call_divide(int a, int b)
{
return s_func_callback->divide_func(a, b);
}
PB_API int sum(int a, int b)
{
return a + b;
}
PB_API int sum_1(int* a, int* b)
{
return *a + *b;
}
PB_API int sum_2(SumFunc sum_func, int a, int b)
{
return sum_func(a, b);
}
PB_API void* create_user_api()
{
return new UserApi();
}
PB_API int sum_with_api(UserApi* user_api)
{
return user_api->m_a + user_api->m_b;
}
PB_API int divide_with_api(UserApi* user_api)
{
return user_api->m_a / user_api->m_b;
}
}
/**UserApi.h**/
class UserApi
{
public:
UserApi();
~UserApi();
public:
int m_a;
int m_b;
};
/**UserApi.cpp**/
#include "UserApi.h"
UserApi::UserApi() :m_a(100), m_b(9)
{
}
UserApi::~UserApi()
{
}
C# 代码:
/**Program.cs**/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace DllTest
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public delegate int MyFunc(int a, int b);
struct FuncCallback
{
public MyFunc sum_func;
public MyFunc minus_func;
public MyFunc multi_func;
public MyFunc divide_func;
}
class Program
{
private const string DLL_NAME = "MyDll.dll";
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sum(int a, int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sum_1(ref int a, ref int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sum_2(MyFunc my_sum, int a, int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void register_spi(IntPtr func_callback);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int call_sum(int a, int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int call_minus(int a, int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int call_multi(int a, int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int call_divide(int a, int b);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr create_user_api();
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sum_with_api(IntPtr user_api);
[DllImport(DLL_NAME, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int divide_with_api(IntPtr user_api);
static FuncCallback m_func_callback;
static IntPtr m_func_callback_point;
static IntPtr m_user_api;
public static int my_sum(int a, int b)
{
return a + b;
}
public static int my_minus(int a, int b)
{
return a - b;
}
public static int my_multi(int a, int b)
{
return a * b;
}
public static int my_divide(int a, int b)
{
return a / b;
}
static void Main(string[] args)
{
m_func_callback = new FuncCallback();
m_func_callback.sum_func = new MyFunc(my_sum);
m_func_callback.minus_func = new MyFunc(my_minus);
m_func_callback.multi_func = new MyFunc(my_multi);
m_func_callback.divide_func = new MyFunc(my_divide);
int size = Marshal.SizeOf(m_func_callback);
m_func_callback_point = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(m_func_callback, m_func_callback_point, true);
register_spi(m_func_callback_point);
Console.WriteLine("sum: " + sum(3, 5).ToString());
int a = 10, b = 20;
Console.WriteLine("sum_1: " + sum_1(ref a, ref b).ToString());
Console.WriteLine("sum_2: " + sum_2(m_func_callback.sum_func, 9, 2).ToString());
Console.WriteLine("call_sum: " + call_sum(1, 2).ToString());
Console.WriteLine("call_minus: " + call_minus(1, 2).ToString());
Console.WriteLine("call_multi: " + call_multi(1, 2).ToString());
Console.WriteLine("call_divide: " + call_divide(1, 2).ToString());
m_user_api = create_user_api();
Console.WriteLine("sum_with_api: {0}", sum_with_api(m_user_api));
Console.WriteLine("divide_with_api: {0}", divide_with_api(m_user_api));
Console.Read();
}
}
}
运行结果:
sum: 8
sum_1: 30
sum_2: 11
call_sum: 3
call_minus: -1
call_multi: 2
call_divide: 0
sum_with_api: 109
divide_with_api: 11
这里只是测试了一下简单的函数,后面有需要其他有代表性的再行补充。
说一下遇到的问题以及需要注意的地方:
1、调用约定,C++中函数默认采用__cdecl的调用约定,而C#中默认则是__stdcall,这里需要统一,可以在C++中使用__stdcall声明函数,或者在C#中导出Dll的时候,使用CallingConvention = CallingConvention.Cdecl参数,指定导出函数的调用约定。
2、函数指针,在各种API中,使用回调函数是一种比较常见的通信方式,C++这里的函数指针在C#中对应的是委托。
3、指针类型参数,这里测试了两种指针类型的参数,int* 与一个结构体指针,对于int* 类型参数,在C#的导出函数中,对应为ref int类型,对于结构类型的指针,在C#导出函数中,对应为IntPtr类型。
4、指针类型返回值,可以通过C++函数返回指向C++对象的指针,C#中使用IntPtr保存,再将该指针作为参数传递给接受该指针类型参数的函数,可以正常调用。
GitHub:https://github.com/xunmeng2002/User_C-_Dll_in_C-