有状态的CLR委托如何编组到仅采用函数指针的本机C函数中

问题描述 投票:1回答:1

(替代标题:如何在C或C ++中实现等效于CLR委托的方法]

考虑此C函数:

int Test(int(*fn1)(double a));

如果要从C程序调用此函数,则无法将任意状态对象与函数指针一起传递-我只能有效地使用全局状态。这是一个常见问题,这就是为什么许多C API提供类似于[]

int Test(int(*fn1)(double a, void *state), void *state);

但是,令我惊讶的是,我注意到从C#程序中调用函数的第一个版本时这不是问题。

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int CallbackType(double something);

[DllImport("TestLib.dll", CallingConvention = CallingConvention.Cdecl)]
extern static int Test(CallbackType fn);

[当回调函数被调用时(即在C#代码中),this指针及其所有成员都被保留(这自动意味着可以使闭包和多播委托等附加功能易于使用)。

我不理解封送拆收器如何将2个指针的信息压缩为1。我做了很多测试,并意识到在使用TestCallback的不同实例(即C#中的不同调用目标)调用C函数时,每次到达C的函数指针都是一个不同的地址。更准确地说,似乎TestCallback实例与唯一的C函数指针地址之间存在直接1:1映射,并且该地址似乎是持久的-但是我无法找到该地址在TestCallback内部存储的位置]实例。

我的结论是,在程序运行时实例化TestCallback时,CLR必须向RAM中发出可执行的本机代码块。该代码块使用硬编码的状态对象指针来调用调度程序功能(状态对象可能是为其发出黑色代码的特定TestCallback实例)。

但是,到目前为止,我还没有发现任何可以证实或反对的东西-对此主题没有深入的信息,或者被肤浅的教程所掩盖。

如果是这样,那么在程序存储器和数据存储器严格分开的体系结构上如何使CPU无法从数据存储器加载运行时生成的代码,那怎么可能?它在要求提前编译的平台上如何工作?怎样在较低级别的语言(如C或C ++)中实现类似的功能?


我用于测试的一些其他代码:

C header file ===================

extern __declspec(dllexport) int Test(
    int(*fn1)(double a), int *address1,
    int(*fn2)(double a), int *address2,
    int(*fn3)(double a), int *address3
);

C file ===================

int Test(
    int(*fn1)(double a), int *address1,
    int(*fn2)(double a), int *address2,
    int(*fn3)(double a), int *address3
)
{
    *address1 = (int)fn1;
    *address2 = (int)fn2;
    *address3 = (int)fn3;
    int result = fn1(5538867.0);
    result += 9;
    return result;
}

C# file ===================

class Program
{
    static void Main(string[] args)
    {
        var sc1 = new SomeClass(5538867);
        var sc2 = new SomeClass(-999999);
        var callback1 = new CallbackType(sc1.TheCallback);
        var callback2 = new CallbackType(sc2.TheCallback);
        int called_address1 = 0, called_address2 = 0, called_address3 = 0;

        var result = Test(
            callback1, ref called_address1,
            callback2, ref called_address2,
            callback2, ref called_address3
            );
        // should be 9 or 8
        Console.WriteLine(result);
        Console.WriteLine(called_address1.ToString("x8"));
        Console.WriteLine(called_address2.ToString("x8"));
        Console.WriteLine(called_address3.ToString("x8"));

        result = Test(
            callback1, ref called_address1,
            callback2, ref called_address2,
            callback2, ref called_address3
            );

        Console.WriteLine(result);
        Console.WriteLine(called_address1.ToString("x8"));
        Console.WriteLine(called_address2.ToString("x8"));
        Console.WriteLine(called_address3.ToString("x8"));

        GC.KeepAlive(callback1);
        GC.KeepAlive(callback2);

        Console.ReadKey();
    }

    class SomeClass
    {
        public SomeClass(int i)
        {
            this.i = i;
            this.something_else = "sjdklfjksdf";
        }

        private readonly int i;
        private readonly string something_else;

        public int TheCallback(double something)
        {
            return (int)something - this.i + this.something_else.Length - 11;
        }
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate int CallbackType(double something);

    [DllImport("TestLib.dll", CallingConvention = CallingConvention.Cdecl)]
    extern static int Test(
        CallbackType callback1, ref int called_address1,
        CallbackType callback2, ref int called_address2,
        CallbackType callback3, ref int called_address3
        );
}

((替代标题:如何在C或C ++中实现等效于CLR委托的方法。)考虑以下C函数:int Test(int(* fn1)(double a));如果要从C程序调用此函数,我会...

c# c delegates function-pointers
1个回答
0
投票

此后我找到了部分答案。

© www.soinside.com 2019 - 2024. All rights reserved.