C#调用C++动态库接口函数和回调函数的方法
本文详细介绍如何在C#中通过P/Invoke技术调用C++编写的DLL文件,涵盖普通接口的参数传递(含字符串Marshalling)及回调函数的指针传递与执行机制,提供基于VS2017环境的完整代码示例。
准备工作与环境说明
在进行C#与C++的动态库交互前,需明确运行环境与编译配置。本方案基于Windows 10 64位系统及Visual Studio 2017开发环境。C++动态库(DLL)在编写时需确保导出函数使用 extern "C" 避免名称修饰,并指定正确的 calling convention(通常为 Cdecl),以便C#能够正确调用。
普通接口函数调用流程
普通函数的调用主要涉及将参数从托管环境(C#)传递到非托管环境(C++ DLL)。重点在于处理字符串类型的内存转换。
第1步:C++端编写与导出接口
目的:在DLL中暴露可供外部调用的函数入口。
- 头文件声明:使用
EXTERN_C和导出宏标识需要导出的函数。注意参数类型匹配,例如接收字符串时使用char*。
// 带返回值无形参示例
EXTERN_C TOOLLIBRARY_API char* Version(void);
// 无返回值带指针形参示例
EXTERN_C TOOLLIBRARY_API void SetApplicationDirPath(char *buff);
源代码实现:
char* Version(void)
{
return version_str;
}
void SetApplicationDirPath(char * buff)
{
std::string str = buff;
printf("传入的参数:%s\n", buff);
std::cout << "字符串:"<<buff <<std::endl;
}
第2步:C#端调用实现
目的:在C#项目中声明外部方法,并在调用时处理数据类型转换。
- DllImport声明:指定DLL名称、入口点(EntryPoint)、字符集(
CharSet.Ansi)和调用约定(Cdecl)。 - 参数处理:C#的字符串是非托管内存,直接传递可能导致错误。需使用
Marshal.StringToHGlobalAnsi将字符串复制到非托管内存,并将指针传递给C++;返回的非托管指针需使用Marshal.PtrToStringAnsi转换回C#字符串。
namespace CSharp_LoadLibrary
{
class Program
{
// 引用TOOLLIBRARY.dll中的Version函数
[DllImport("TOOLLIBRARY.dll", EntryPoint = "Version", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
extern static IntPtr Version();
// 引用SetApplicationDirPath函数,接收IntPtr以手动管理内存
[DllImport("TOOLLIBRARY.dll", EntryPoint = "SetApplicationDirPath", CallingConvention = CallingConvention.Cdecl)]
extern static void SetApplicationDirPath(IntPtr text);
static void Main(string[] args)
{
// 1. 测试传入参数:将C#字符串转换为非托管ANSI字符串指针并调用
SetApplicationDirPath(Marshal.StringToHGlobalAnsi("传入字符串测试.."));
// 2. 打印返回值:获取非托管指针并转换回C#字符串
Console.WriteLine(Marshal.PtrToStringAnsi(Version()));
}
}
}
回调函数调用流程
回调函数允许C++ DLL在执行特定事件时主动调用C#中定义的函数。这需要建立从非托管到托管的代码通道,通常通过 Delegate(委托)和 Function Pointer(函数指针)实现。
第3步:C++端注册回调函数
目的:C++端提供一个函数,用于接收C#传来的函数指针地址,并保存该指针供后续调用。
- 头文件声明:定义接收函数指针的原型。
// 设置回调函数指针
EXTERN_C TOOLLIBRARY_API void Set_DebugCallBackFunction(void(*func)(const char *p));
源代码实现:
// 全局变量或类成员存储函数指针
void (*CallBackFunction_p)(const char *p) = nullptr;
void Set_DebugCallBackFunction(void(*func)(const char *p))
{
// 保存回调函数指针
CallBackFunction_p = func;
printf("回调函数设置成功.\n");
// 模拟触发回调,向C#推送数据
if (CallBackFunction_p) {
CallBackFunction_p("这是回调函数传出来的测试数据.\n");
}
}
第4步:C#端定义委托并注册
目的:定义与C++函数签名匹配的委托类型,创建方法实例,并将其作为参数传递给C++ DLL。
- 委托定义:使用
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]特性标记委托,确保内存布局和调用约定一致。 - 实现回调逻辑:编写被调用的具体方法,内部处理接收到的数据(如将
IntPtr转为字符串)。 - 注册调用:将C#的方法实例传递给C++的注册函数。
using System;
using System.Runtime.InteropServices;
namespace CSharp_LoadLibrary
{
class Program
{
// 1. 声明C++端的注册接口,接收委托类型
[DllImport("TOOLLIBRARY.dll", EntryPoint = "Set_DebugCallBackFunction", CallingConvention = CallingConvention.Cdecl)]
extern static int Set_DebugCallBackFunction(CallbackDelegate callback);
// 2. 定义与C++函数签名一致的委托
// C++原型: void (*)(const char *) -> C#对应: delegate void CallbackDelegate(IntPtr Path)
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CallbackDelegate(IntPtr Path);
// 3. 实现具体的回调响应逻辑
static void CallBackFunction(IntPtr Path)
{
// 将非托管指针转换为C#字符串并输出
Console.WriteLine("C++传出来的回调:" + Marshal.PtrToStringAnsi(Path));
}
static void Main(string[] args)
{
// 4. 注册回调:将C#方法名作为参数传递给C++,建立连接
Set_DebugCallBackFunction(CallBackFunction);
}
}
}
注意事项与排错
在进行跨语言调用时,常见的问题包括内存泄漏、崩溃和数据截断,请注意以下几点:
- 调用约定一致性:C++ DLL导出函数若为
__stdcall,C#中也必须对应CallingConvention.StdCall,否则会导致栈不平衡引发崩溃。本例统一使用Cdecl。 - 字符编码匹配:C++中使用
char*通常对应ANSI编码,C#侧需设置CharSet.Ansi并使用Ansi相关的Marshal方法。若C++使用宽字符wchar_t*,则需改用Uni相关转换。 - 委托生命周期:在C#中注册的委托对象不能被垃圾回收立即销毁,建议将委托存储在静态字段或类成员变量中,防止在C++调用时委托已被回收。
以上就是C#调用C++动态库接口函数和回调函数的详细内容,更多关于C#调用C++接口函数和回调函数内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













