How to implement C++ style function pointers in C#?, Without using delegates
我正在学习C语言中的指针,很好奇是否可以在C++中使用C++样式函数指针。是的,我知道C对于函数指针(称为委托)有自己的等价概念。但我只想知道,在不使用委托的情况下,使用C_中的指针是否可以实现相同的效果。
如果使用C语言中的指针是完全合法的(使用不安全选项),指针语义几乎类似于C/C++,那么在我看来,也应该能够使用C/C++样式函数指针。请给我引路。有可能吗?如果是,怎么办?,如果不是,那为什么?
请注意以下示例中C语言和C/C++中指针用法的相似性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* 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_没有内置的函数指针支持,即使在不安全的模式下,也是正确的。
考虑如何实现这个特性是很有趣的。碰巧的是,我在一个未发布的C原型版本中实现了这个特性,回到……2010?那时左右。大概2011岁吧。
我们在回顾原型时决定,语法不够令人满意,并且使用案例不足以证明继续使用该特性的合理性。
首先,与代理相比,该功能的显著优势是什么?委托是类型安全的,可以很好地捕获对绑定到其接收器的函数的引用的语义。然而,在一些"系统"场景中——与您将首先使用原始内存指针的场景相同——委托太重了。它们是垃圾收集的,它们增加了收集压力,与指针的大小相比,它们是一个大对象,每次调用都有成本,等等。或者,您可能正在构建自己的自定义布局vtables,以便与一些特别讨厌的非托管代码进行互操作,并且您希望调用刚刚在vtable中放下的函数指针。等等。
clr有必要的指令:
有什么问题吗?只需添加一些导致发出此消息的语法,对吗?
问题是:什么语法?为了确保clr的物理堆栈保持正确对齐,编译器必须知道通过指针调用的方法的签名。当然,我们已经有了一种机制来说明方法的签名是什么:这称为委托,而且我们已经拒绝了使用这种方法。此外,请记住,我们在这里讨论的是实际将要被操作的物理堆栈,而不是clr的抽象评估堆栈。例如,被调用函数的签名必须包括诸如函数指针是cdecl还是syscall之类的内容。
我们挣扎了一段时间才想出一个看起来不可怕的东西,不管我们怎么努力,它看起来都更可怕。实际上,我不记得我们最终为原型实现了什么符号;我想我可能已经把它屏蔽了。我可能把它放在笔记里的某个地方,但不幸的是我现在没有时间看。
这项功能还不时地被提起。C团队的当前管理层有在低级应用程序中使用托管语言的历史,因此如果您有一个强大的用例,那么现在可能是再次推销它的好时机。
虽然受CLR支持,但C语言将您限制为安全不安全的代码。函数指针属于不安全的不安全类别,它们很容易使堆栈失衡。一种严重的灾难,具有长期的后果,使代码在灾难发生后很长一段时间都表现不好。这个站点的名字是有原因的,这种bug是SOE factorial。甚至C++编译器也添加运行时检查来验证没有发生堆栈不平衡。
你还有选择。可以使用Simult.EIT或C++/CLI使用原始函数指针。或者使用delegate.dynamicInvoke()实现道德等效。后者总是安全的,CLR执行运行时检查以验证是否传递了足够的参数。
这无疑是你应该考虑追求的解决方案,尽管你问的原因还不清楚。委托是非常微优化的,在clr中有大量的代码使其快速。该代码执行反射.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++非常不同的编码范例。不要让语义上的相似性迷惑了你。两种不同的语言需要不同的思维方式。
看看这个,void*p是指向函数的指针(十六进制值,RAM地址)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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(" Test!"); } } } |
更多版本如果外部库使用回调,则此版本更好
1 2 3 4 5 6 7 8 9 10 11 | [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 } |