2
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);

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

I’m trying to avoid the performance overhead of using MethodInfo.Invoke() in C# Reflection by calling methods directly from C++ using memory manipulation. My goal is to completely bypass Reflection at runtime.

I successfully managed to call a method obtained via Reflection in C++ without passing parameters, but when I tried to pass parameters, I couldn’t make it work. I suspect the user-defined method needs to accept an IntPtr and handle data conversion internally, but this approach is not user-friendly. Ideally, everything related to C++ and memory management should be handled in the background.

Here are my specific questions:

  1. How can I pass parameters (e.g., structs) from C++ to a C# method obtained via Reflection?

  2. When I attempted to pass parameters, I didn’t encounter a compile-time error, but at runtime, the call failed. What could be the likely cause of this issue?

Any guidance or examples on how to implement this would be greatly appreciated.

My .NET version is 8.0.110

5
  • I don't know the answer to your question, but one obvious problem is that the method FunctionCreatedByUser(StructCreatedByUser data) expects the data struct to be passed by value but you're actually passing it by reference. You probably also need to apply [StructLayout(LayoutKind.Sequential)]. Commented Dec 8, 2024 at 17:07
  • It seems that, in any case, the user would need to make extra adjustments to adapt to this system. I hope there are alternative solutions available Commented Dec 8, 2024 at 17:18
  • Use C++/cli (managed C++) to create an adapter : C++ cli in ten minutes. Technology boundaries are not free : You should expect some overhead. E.g. C# manages life cycle/memory differently, C# code may throw and you would need to translate that to C++ exceptions etc. Better do it right. And if you can't pay the penalties rewrite the C# functionality in C++ Commented Dec 8, 2024 at 18:03
  • Of course, I understand that there will be additional costs. However, if these costs only amount to a few seconds (5-6 seconds) for 1 million calls, it’s something that can be overlooked. Since I am doing all of this in Unity, it is not possible for me to rewrite the functions in C++. Commented Dec 8, 2024 at 18:17
  • 1
    stackoverflow.com/questions/31511996/… Commented Dec 8, 2024 at 19:32

2 Answers 2

2

So many issues:

  • Your C++ code expects the function to have a single void* parameter, which means you'd need to pass the struct by reference, as it's too wide for a native int.
  • You also have an obvious memory leak because you are not freeing your HGlobal.
  • GetFunctionPointer() is dangerous, and not intended to be used directly in most cases, as the runtime often needs to set up a trampoline shim depending on the design on the function, the calling convention, and where it's called from. You'd need to use a delegate with Marshal.GetFunctionPointerForDelegate instead.

Honestly I'm very unclear why you're not just letting the marshaller sort this all out for you. Just declare a delegate type with the actual ref StructCreatedByUser and pass that through.

Don't forget to hang on to the delegate using GC.KeepAlive or storing it in a field until you're done with the callback. Also make sure to specify the calling convention on the delegate type.

[DllImport("CppLibrary")]
private static extern void SetPointer(FunctionStorage func);

[DllImport("CppLibrary")]
private static extern void CallCppFunctionWithParam(ref StructCreatedByUser p1);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FunctionStorage(ref StructCreatedByUser p1);

private static FunctionStorage _func = FunctionCreatedByUser;  // need to keep this alive

public static void FunctionCreatedByUser(ref StructCreatedByUser data)
{
    Console.WriteLine(data.x + "  " + data.anything + "   " + data.name );
}

static void Main()
{
    StructCreatedByUser data = new StructCreatedByUser { x = 10, anything = 20.5f, name = "wedf" };
    SetPointer(_func);
    CallCppFunctionWithParam(ref data);
}

If you particularly need to generate the delegate via reflection then you can simply do

_func = typeof(Program)
    .GetMethod(nameof(Program.FunctionCreatedByUser))!
    .CreateDelegate<FunctionStorage>();
Sign up to request clarification or add additional context in comments.

20 Comments

@Bilal, can you please elaborate on why you need to pass a ref object o instead of a specific type like StructCreatedByUser? In the case of object the built-in marshalled just can't know how to work with the data you pass (obviously not every managed object can be passed to C++).
Like I said, don't do DynamicInvoke. Just create a delegate dynamically, and call it statically.
Hard to say without a fuller understanding of your usecase, but you could build a list of possible functions to be called, and create Expressions to call them. Then compile those expressions to create delegates for them, that will run very fast. See learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/…
No you create a Expression.Lambda<Func<object>> which does all of the getting the parameters and passing them. All of the dynamic code should be in there, then you create a single delegate type off that.
Omg. It's really working
|
1

After discussing with CharlieFace in the comments, I learned that what I wanted to achieve can be done without using C++. I want to thank everyone who contributed.

If you found this thread and came here, I recommend reading the comments. They will likely solve your C++ problem as well.

The code that solved my issue is as follows:

using System.Linq.Expressions;
using System.Reflection;

public struct MyStruct
{
    public string name;
}

public class Program
{
    public void FunctionCreatedByUser(int x, float anything, string name, MyStruct myStruct)
    {
        Console.WriteLine($"{x}  {anything}  {name}" + "   " + myStruct.name);
    }

    static void Main()
    {
        Program program = new Program();

        var parameters = new object[] { 10, 20.5f, "TestName", new MyStruct() { name = "asdagdjakfadgkjdhflajg" } };

        MethodInfo methodInfo = typeof(Program).GetMethod("FunctionCreatedByUser");

        var lambdaParam = Expression.Parameter(typeof(object[]), "parameters");

        var methodParams = methodInfo.GetParameters();
        var convertedParams = new Expression[methodParams.Length];
        for (int i = 0; i < methodParams.Length; i++)
        {
            var paramAccess = Expression.ArrayIndex(lambdaParam, Expression.Constant(i));
            convertedParams[i] = Expression.Convert(paramAccess, methodParams[i].ParameterType);
        }

        var methodCall = Expression.Call(
            Expression.Constant(program),  // Instance of the class
            methodInfo,                    // Method to call
            convertedParams                // Parameters for the method
        );

        var lambda = Expression.Lambda<Action<object[]>>(methodCall, lambdaParam);

        Action<object[]> compiledDelegate = lambda.Compile();
        compiledDelegate(parameters);
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.