如何在 C# 中实现 C++ 风格的函数指针?,不使用委托
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++ 中指针用法的相似性,如下例所示
/* 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++ 中的函数指针的东西了。 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# 没有对函数指针的任何内置支持的其他答案是正确的,即使在不安全模式下也是如此。
考虑实现此功能需要做些什么很有趣。碰巧的是,我 已经 在未发布的 C# 原型版本中实现了此功能,早在... 2010 年?那时左右。也许是 2011 年。
我们在审查原型后决定,语法不够令人愉快,用例也不足以证明继续使用该功能是合理的。
首先,与委托相比,该功能的显着优势是什么?委托是类型安全的,并且很好地捕获了对绑定到其接收者的函数的引用的语义。然而,在某些 "systems" 场景中——与您首先使用原始内存指针的场景相同——委托实在太重量级了。它们被垃圾收集,它们增加了收集压力,与指针的大小相比它们是一个大对象,它们在每次调用时都有成本,等等。或者,您可能正在构建自己的自定义布局 vtables 以与一些特别讨厌的非托管代码进行互操作,并且您希望调用您刚刚放在 vtable 中的函数指针。等等。
CLR 有必要的指令:calli
,指针间接调用。但是 C# 中没有任何语法结构会导致 C# 编译器发出此指令。
所以问题是什么?只需添加一些语法即可发出此消息,对吗?
问题是:什么语法?为了确保 CLR 的物理堆栈保持正确对齐,编译器必须知道通过指针 调用的方法的签名。当然,我们已经有了一种机制来说明方法的签名是什么:称为 delegate,我们已经拒绝使用这样的东西。此外,请记住,我们在这里谈论的是实际要操作的物理堆栈,而不是 CLR 的抽象计算堆栈。被调用函数的签名必须包括函数指针是 cdecl 还是 syscall 等内容。
我们努力想出一些看起来并不可怕的东西,但无论我们尝试哪种方式,它看起来都更可怕。我实际上不记得我们最终为原型实现了什么符号;我想我可能已经阻止了它。我可能在我的笔记中的某个地方有它,但不幸的是我现在没有时间看。
该功能仍时不时地出现。 C# 团队的当前管理层有在低级应用程序中使用托管语言的历史,因此如果您有强大的用例,现在可能是再次推销它的好时机。
虽然受 CLR 支持,但 C# 语言将您限制为安全的不安全代码。函数指针属于不安全的不安全类别,它们很容易使堆栈失衡。一个具有长期后果的严重事故,使代码在事故发生很久之后行为不端。这个站点得名是有原因的,这种错误是 SOE 阶乘的。甚至 C++ 编译器也添加了运行时检查以验证没有发生堆栈不平衡。
你还有选择。您可以通过 Reflection.Emit 或 C++/CLI 使用原始函数指针。或者通过使用 Delegate.DynamicInvoke() 达到道德上的等价物。后者总是安全的,CLR 执行运行时检查以验证是否传递了足够的参数。
您应该考虑采用哪种解决方案,尽管不清楚您为什么要问。委托在 CLR 中使用大量代码进行了非常微观的优化,以提高它们的速度。该代码相当于 Reflection.Emit 但在机器代码中。你打不过它。
看看这个,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
}
C# 9 允许函数指针:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers
The language will allow for the declaration of function pointers using the delegate*
syntax. The full syntax is described in detail in
the next section but it is meant to resemble the syntax used by Func
and Action type declarations.
These types are represented using the function pointer type as
outlined in ECMA-335. This means invocation of a delegate*
will use
calli
where invocation of a delegate will use callvirt
on the Invoke
method. Syntactically though invocation is identical for both
constructs.
Method groups will now be allowed as arguments to an address-of
expression. The type of such an expression will be a delegate*
which
has the equivalent signature of the target method and a managed
calling convention.
看看下面关于如何使用此功能的简单示例。在项目的属性 select“构建”选项卡中选中“允许不安全代码”。
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();
}
}
}
我正在学习 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# 中没有比委托更接近 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# 没有对函数指针的任何内置支持的其他答案是正确的,即使在不安全模式下也是如此。
考虑实现此功能需要做些什么很有趣。碰巧的是,我 已经 在未发布的 C# 原型版本中实现了此功能,早在... 2010 年?那时左右。也许是 2011 年。
我们在审查原型后决定,语法不够令人愉快,用例也不足以证明继续使用该功能是合理的。
首先,与委托相比,该功能的显着优势是什么?委托是类型安全的,并且很好地捕获了对绑定到其接收者的函数的引用的语义。然而,在某些 "systems" 场景中——与您首先使用原始内存指针的场景相同——委托实在太重量级了。它们被垃圾收集,它们增加了收集压力,与指针的大小相比它们是一个大对象,它们在每次调用时都有成本,等等。或者,您可能正在构建自己的自定义布局 vtables 以与一些特别讨厌的非托管代码进行互操作,并且您希望调用您刚刚放在 vtable 中的函数指针。等等。
CLR 有必要的指令:calli
,指针间接调用。但是 C# 中没有任何语法结构会导致 C# 编译器发出此指令。
所以问题是什么?只需添加一些语法即可发出此消息,对吗?
问题是:什么语法?为了确保 CLR 的物理堆栈保持正确对齐,编译器必须知道通过指针 调用的方法的签名。当然,我们已经有了一种机制来说明方法的签名是什么:称为 delegate,我们已经拒绝使用这样的东西。此外,请记住,我们在这里谈论的是实际要操作的物理堆栈,而不是 CLR 的抽象计算堆栈。被调用函数的签名必须包括函数指针是 cdecl 还是 syscall 等内容。
我们努力想出一些看起来并不可怕的东西,但无论我们尝试哪种方式,它看起来都更可怕。我实际上不记得我们最终为原型实现了什么符号;我想我可能已经阻止了它。我可能在我的笔记中的某个地方有它,但不幸的是我现在没有时间看。
该功能仍时不时地出现。 C# 团队的当前管理层有在低级应用程序中使用托管语言的历史,因此如果您有强大的用例,现在可能是再次推销它的好时机。
虽然受 CLR 支持,但 C# 语言将您限制为安全的不安全代码。函数指针属于不安全的不安全类别,它们很容易使堆栈失衡。一个具有长期后果的严重事故,使代码在事故发生很久之后行为不端。这个站点得名是有原因的,这种错误是 SOE 阶乘的。甚至 C++ 编译器也添加了运行时检查以验证没有发生堆栈不平衡。
你还有选择。您可以通过 Reflection.Emit 或 C++/CLI 使用原始函数指针。或者通过使用 Delegate.DynamicInvoke() 达到道德上的等价物。后者总是安全的,CLR 执行运行时检查以验证是否传递了足够的参数。
您应该考虑采用哪种解决方案,尽管不清楚您为什么要问。委托在 CLR 中使用大量代码进行了非常微观的优化,以提高它们的速度。该代码相当于 Reflection.Emit 但在机器代码中。你打不过它。
看看这个,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
}
C# 9 允许函数指针: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers
The language will allow for the declaration of function pointers using the
delegate*
syntax. The full syntax is described in detail in the next section but it is meant to resemble the syntax used by Func and Action type declarations. These types are represented using the function pointer type as outlined in ECMA-335. This means invocation of adelegate*
will usecalli
where invocation of a delegate will usecallvirt
on the Invoke method. Syntactically though invocation is identical for both constructs.
Method groups will now be allowed as arguments to an address-of expression. The type of such an expression will be a
delegate*
which has the equivalent signature of the target method and a managed calling convention.
看看下面关于如何使用此功能的简单示例。在项目的属性 select“构建”选项卡中选中“允许不安全代码”。
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();
}
}
}