如何从C++调用带参数的C#方法并规避反射性能开销
绕过C#反射直接调用方法的参数传递问题
问题描述
为避免C#反射中MethodInfo.Invoke()的性能开销,尝试通过C内存操作直接调用C#方法,目标是运行时完全绕过反射。目前已实现无参数调用,但传递结构体这类参数时无法正常工作。不希望让用户定义的C#方法接收IntPtr再手动转换,希望C侧的操作和内存管理能在后台完成。
具体问题:
- 如何从C++向通过反射获取的C#方法传递结构体这类参数?
- 传递参数时无编译错误,但运行时调用失败,可能的原因是什么?
.NET版本:8.0.110
原始代码
C#代码
using System; using System.Runtime.InteropServices; using System.Reflection; public struct StructCreatedByUser { public int x; public float anything; public string name; } class Program { [DllImport("CppLibrary")] private static extern void SetPointer(IntPtr ptr); [DllImport("CppLibrary")] private static extern void CallCppFunctionWithParam(IntPtr param); public static void FunctionCreatedByUser(StructCreatedByUser data){ Console.WriteLine(data.x + " " + data.anything + " " + data.name ); } static void Main() { StructCreatedByUser data = new StructCreatedByUser { x = 10, anything = 20.5f, name = "wedf" }; IntPtr param = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(StructCreatedByUser))); Marshal.StructureToPtr(data, param, false); IntPtr methodPtr = typeof(Program).GetMethod("FunctionCreatedByUser").MethodHandle.GetFunctionPointer(); SetPointer(methodPtr); CallCppFunctionWithParam(param); } }
C++代码
#include <iostream> #include <functional> #include <string> typedef void (*FunctionStorage)(void*); FunctionStorage func; extern "C" void SetPointer(void* methodPtr) { func = reinterpret_cast<FunctionStorage>(methodPtr); } extern "C" void CallCppFunctionWithParam(void* param) { func(param); }
问题分析与解决方案
运行时调用失败的核心原因
你的代码存在两个致命问题:
- 调用约定不匹配:C#静态方法默认使用
StdCall调用约定,而C++中定义的FunctionStorage默认是Cdecl,调用约定不匹配会直接导致栈损坏,引发运行时崩溃。 - 结构体传递方式错误:C#方法期望接收值类型的结构体实例,但你传递的是非托管内存的指针;同时结构体中的
string是.NET托管类型,直接用Marshal.StructureToPtr转换后,内存中仅包含指向托管堆的指针,无法被C#方法正确解析。
正确实现方案
以下是修正后的代码,确保调用约定一致、结构体内存布局匹配,且无需修改用户定义的C#方法签名:
修正后的C#代码
using System; using System.Runtime.InteropServices; using System.Reflection; // 显式指定结构体内存布局与字符串编码,匹配C++侧定义 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct StructCreatedByUser { public int x; public float anything; [MarshalAs(UnmanagedType.LPWStr)] public string name; } class Program { // 指定C++导出函数的调用约定为Cdecl [DllImport("CppLibrary", CallingConvention = CallingConvention.Cdecl)] private static extern void SetPointer(IntPtr ptr); [DllImport("CppLibrary", CallingConvention = CallingConvention.Cdecl)] private static extern void CallCppFunctionWithParam(IntPtr param); // 保持原方法签名不变,无需修改用户代码 public static void FunctionCreatedByUser(StructCreatedByUser data) { Console.WriteLine($"{data.x} {data.anything} {data.name}"); } static void Main() { StructCreatedByUser data = new StructCreatedByUser { x = 10, anything = 20.5f, name = "wedf" }; // 获取C#方法的函数指针 IntPtr methodPtr = typeof(Program).GetMethod("FunctionCreatedByUser") .MethodHandle.GetFunctionPointer(); SetPointer(methodPtr); // 固定托管结构体地址,避免GC移动内存,直接传递给C++ unsafe { fixed (StructCreatedByUser* pData = &data) { CallCppFunctionWithParam((IntPtr)pData); } } } }
修正后的C++代码
#include <iostream> // 匹配C#静态方法的StdCall调用约定 typedef void (__stdcall *FunctionStorage)(StructCreatedByUser); // 定义与C#侧完全一致的结构体 struct StructCreatedByUser { int x; float anything; wchar_t* name; // 对应C#的Unicode字符串 }; FunctionStorage func; // 导出函数使用Cdecl调用约定,匹配C#的DllImport设置 extern "C" __declspec(dllexport) void __cdecl SetPointer(void* methodPtr) { func = reinterpret_cast<FunctionStorage>(methodPtr); } extern "C" __declspec(dllexport) void __cdecl CallCppFunctionWithParam(void* param) { // 将void*转换为结构体指针,按值传递给C#方法 func(*reinterpret_cast<StructCreatedByUser*>(param)); }
关键说明
调用约定匹配:
- C#静态方法默认使用
__stdcall,因此C++的函数指针必须显式声明为__stdcall。 - C++导出函数使用
__cdecl,需在C#的DllImport中指定CallingConvention.Cdecl。
- C#静态方法默认使用
结构体处理:
- C#结构体添加
[StructLayout(LayoutKind.Sequential)]确保内存布局与C一致,字符串用[MarshalAs(UnmanagedType.LPWStr)]指定Unicode编码,匹配C的wchar_t*。 - 使用
fixed关键字固定托管结构体的地址,避免GC移动内存,直接传递给C++,无需分配非托管内存。
- C#结构体添加
内存安全:
fixed块内会固定整个结构体及字符串指针指向的托管内存,确保调用期间不会被GC回收。- 无需手动管理非托管内存,减少内存泄漏风险。
内容的提问来源于stack exchange,提问作者Bilal
相关产品推荐
相关产品推荐

