如何在C#中实现C++风格的函数指针?,不使用代理

How to implement C++ style function pointers in C#?, Without using delegates

本文关键字:指针 代理 函数 实现 风格 C++      更新时间:2023-10-16

我正在学习C#中的指针,很好奇是否可以在C#中使用C++风格的函数指针。是的,我知道C#对函数指针(称为委托)有自己的等价概念。但我只想知道,如果在C#中使用指针,而不使用委托,是否可以实现同样的效果。

如果在C#中使用指针是完全合法的(使用不安全选项),并且指针语义与C/C++几乎相似,那么在我看来,也应该能够使用C/C++风格的函数指针。请指导我。有可能吗?如果是,如何?,如果没有,为什么?

请注意C#和C/C++中指针使用的相似性,如下面的示例所示

/* Using pointers in C# (Very similar to C/C++) */
using System;
namespace UnsafeCodeApplication
{
   class TestPointer
   {
      public unsafe static void Main()
      {
         int[]  list = {10, 100, 200};
         fixed(int *ptr = list)
         /* let us have array address in pointer */
         for ( int i = 0; i < 3; i++)
         {
            Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
            Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
         }
         Console.ReadKey();
      }
   }
}

注意到C#对函数指针没有任何内置支持的其他答案是正确的,即使在不安全模式下也是如此。

考虑一下实现这个特性需要什么是很有趣的。碰巧的是,早在2010年,我就在一个未发布的C#原型版本中实现了这个功能?大约在那时。也许是2011年。

我们在审查原型时决定,语法不够令人愉快,用例也不够令人信服,无法证明继续使用该功能是合理的。

首先,该功能相对于代表的显著优势是什么?委托是类型安全的,并且很好地捕获了绑定到其接收器的函数的引用的语义。然而,在一些"系统"场景中——与您首先使用原始内存指针的场景相同——委托太重了。它们是垃圾收集的,增加了收集压力,与指针大小相比,它们是一个大对象,每次调用都有成本,等等。或者,您可能正在构建自己的自定义布局vtable,以与一些特别讨厌的非托管代码进行互操作,并且您希望调用刚刚放在vtable中的函数指针。等等。

CLR有必要的指令:calli,一个指针间接调用。但是C#中没有任何语法结构会导致C#编译器发出此指令。

那么问题出在哪里呢?只需添加一些语法,就可以发出它,对吧?

问题是:什么语法?为了确保CLR的物理堆栈保持正确对齐,编译器必须知道通过指针调用的方法的签名。当然,我们已经有了一种机制来说明方法的签名是什么:这被称为委托,我们已经拒绝使用这样的东西。此外,请记住,我们在这里讨论的是实际要操作的物理堆栈,而不是CLR的抽象求值堆栈。调用的函数的签名必须包括诸如函数指针是cdecl还是syscall之类的内容。

我们挣扎了一段时间才想出一个看起来不可怕的东西,无论我们尝试哪种方式,它看起来都更可怕。实际上,我不记得我们最终为原型实现了什么符号;我想我可能把它挡住了。我可能会把它放在我的笔记里,但不幸的是,我现在没有时间看。

这个功能仍然不时被提起。C#团队目前的管理层有在低级应用程序中使用托管语言的历史,所以如果您有一个强大的用例,现在可能是再次推介它的好时机。

虽然CLR支持,但C#语言将您限制为安全、不安全的代码。函数指针属于不安全-不安全类别,它们很容易使堆栈不平衡。一个具有长期后果的恶劣事故,使代码在事故发生后很长一段时间内行为不端。这个网站的名字是有充分理由的,这种bug是SOE factorial。甚至C++编译器也添加了一个运行时检查,以验证没有发生堆栈失衡。

你仍然有选择。您可以使用Reflection.Emit或C++/CLI来使用原始函数指针。或者通过使用Delegate.DynamicInvoke()来实现道德上的等效。后者总是安全的,CLR会执行运行时检查以验证是否传递了足够的参数。

这肯定是你应该考虑追求的解决方案,尽管你为什么要问还不清楚。委托在CLR中使用大量代码进行了非常微观的优化,以使其快速运行。该代码的作用相当于Reflection.Emit,但在机器代码中。你无法击败它。

不,C#中没有什么比委托更接近C++中的函数指针了。C#指针可以在以下类型上操作:

  • sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal或bool
  • 任何枚举类型
  • 任何指针类型
  • 仅包含非托管类型字段的任何用户定义的结构类型

来源:https://msdn.microsoft.com/en-us/library/y31yhkeb.aspx

并不总是可以像在本机代码中那样将指向函数的指针提供给用户。C#代码编译为CLR,而不是本机代码。

在.NET 1.1中有Econo JIT模式,在该模式中,函数的本机代码可以在调用函数后删除。函数每次被调用时都可能有不同的地址。

C#没有指向函数的指针,事实上,C#有指向值类型的指针的唯一原因是与C/C++和Win API进行互操作。代表们为您提供了保障。

C#与C++的编码模式非常不同。不要让语义上的相似性混淆你。两种截然不同的语言需要截然不同的思维方式。

C#9允许函数指针:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers

该语言将允许使用delegate*语法声明函数指针。完整的语法在中有详细描述下一节,但它与Func使用的语法相似和Action类型声明。这些类型使用函数指针类型表示为ECMA-335中概述。这意味着调用delegate*将使用calli,其中委托的调用将在Invoke上使用callvirt方法尽管两者的调用在语法上是相同的构造。

方法组现在将被允许作为地址的参数表示这种表达式的类型将是delegate*具有目标方法的等效签名和托管呼叫约定。

下面我们来看看如何使用此功能的简单示例。在项目的属性中,选择";构建";tab和check";允许不安全代码";。

    using System;
    namespace ConsoleApp4
    {
        class Program
        {
            public static void LogMyMethodCall() => Console.WriteLine("MyMethod has been called");
            public unsafe class Example
            {
                public void MyMethod(Action<int> a, delegate*<void> f)
                {
                    a(1);
                    f();
                }
            }
            public unsafe static void Main(string[] args)
            {
                var e = new Example();
                e.MyMethod(x => Console.WriteLine($"{x} has been passed to MyMethod"), &LogMyMethodCall);
                Console.ReadLine();
            }
        }
    }    
   

看看这个,void*p是指向函数(十六进制值,Ram地址)的指针

class Program
{
    static void Main(string[] args)
    {
        //
        OurCallback();
    }
    public static IntPtr GetFunctionPointer()
    {
        A obj1 = new A();
        RuntimeMethodHandle method = ((Action)obj1.Method1).Method.MethodHandle;
        IntPtr p = method.GetFunctionPointer();
        return p;
    }
    public unsafe static void OurCallback()
    {
      var k =  GetFunctionPointer();
      void* p = k.ToPointer();
    }
    struct A
    {
        public void Method1()
        {
            Console.WriteLine("nTest!");
        }
    }
}

更多版本如果你的外部库使用回调,这个版本更适合

   [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public unsafe delegate void CallbackFunc(void* hPreviewCallback, void* 
    pParam);
    private unsafe static void HandleDecData(void* hPreviewCallback, void* 
    pParam)
    {
        // Here you can handle callback
    }