You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从C++调用带参数的C#方法并规避反射性能开销

绕过C#反射直接调用方法的参数传递问题

问题描述

为避免C#反射中MethodInfo.Invoke()的性能开销,尝试通过C内存操作直接调用C#方法,目标是运行时完全绕过反射。目前已实现无参数调用,但传递结构体这类参数时无法正常工作。不希望让用户定义的C#方法接收IntPtr再手动转换,希望C侧的操作和内存管理能在后台完成。

具体问题:

  1. 如何从C++向通过反射获取的C#方法传递结构体这类参数?
  2. 传递参数时无编译错误,但运行时调用失败,可能的原因是什么?

.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);  
}

问题分析与解决方案

运行时调用失败的核心原因

你的代码存在两个致命问题:

  1. 调用约定不匹配:C#静态方法默认使用StdCall调用约定,而C++中定义的FunctionStorage默认是Cdecl,调用约定不匹配会直接导致栈损坏,引发运行时崩溃。
  2. 结构体传递方式错误: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));
}

关键说明

  1. 调用约定匹配:

    • C#静态方法默认使用__stdcall,因此C++的函数指针必须显式声明为__stdcall。
    • C++导出函数使用__cdecl,需在C#的DllImport中指定CallingConvention.Cdecl。
  2. 结构体处理:

    • C#结构体添加[StructLayout(LayoutKind.Sequential)]确保内存布局与C一致,字符串用[MarshalAs(UnmanagedType.LPWStr)]指定Unicode编码,匹配C的wchar_t*。
    • 使用fixed关键字固定托管结构体的地址,避免GC移动内存,直接传递给C++,无需分配非托管内存。
  3. 内存安全:

    • fixed块内会固定整个结构体及字符串指针指向的托管内存,确保调用期间不会被GC回收。
    • 无需手动管理非托管内存,减少内存泄漏风险。

内容的提问来源于stack exchange,提问作者Bilal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 15:56:09