函数指针Function Pointers
总结Summary
此建议提供的语言构造提供当前无法有效访问或根本不能在 c # 中访问的 IL 操作码: ldftn
和 calli
。This proposal provides language constructs that expose IL opcodes that cannot currently be accessed efficiently, or at all, in C# today: ldftn
and calli
. 这些 IL 操作码在高性能代码中非常重要,开发人员需要一种高效的访问方式。These IL opcodes can be important in high performance code and developers need an efficient way to access them.
动机Motivation
下面的问题中描述了此功能的动机和背景, (方式是功能) 的可能实现:The motivations and background for this feature are described in the following issue (as is a potential implementation of the feature):
https://github.com/dotnet/csharplang/issues/191
这是编译器内部函数的替代设计建议This is an alternate design proposal to compiler intrinsics
详细设计Detailed Design
函数指针Function pointers
该语言将允许使用语法声明函数指针 delegate*
。The language will allow for the declaration of function pointers using the delegate*
syntax. 在下一节中详细介绍了完整的语法,但它应类似于 Func
和类型声明所使用的语法 Action
。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.
unsafe class Example {
void Example(Action<int> a, delegate*<int, void> f) {
a(42);
f(42);
}
}
这些类型使用 ECMA-335 中所述的函数指针类型来表示。These types are represented using the function pointer type as outlined in ECMA-335. 这意味着对的调用将 delegate*
使用对 calli
delegate
方法使用的调用 callvirt
Invoke
。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.
方法指针的 ECMA-335 定义包含调用约定,作为类型签名的一部分 (节 7.1) 。The ECMA-335 definition of method pointers includes the calling convention as part of the type signature (section 7.1).
默认调用约定将为 managed
。The default calling convention will be managed
. 通过 unmanaged
将关键字叫到 delegate*
将使用运行时平台默认的语法,可以指定非托管调用约定。Unmanaged calling conventions can by specified by putting an unmanaged
keyword afer the delegate*
syntax, which will use the runtime platform default. 然后 unmanaged
,可以通过在命名空间中指定以命名空间开头的任何类型,在括号中指定特定的非托管约定 CallConv
System.Runtime.CompilerServices
,而不是 CallConv
前缀。Specific unmanaged conventions can then be specified in brackets to the unmanaged
keyword by specifying any type starting with CallConv
in the System.Runtime.CompilerServices
namespace, leaving off the CallConv
prefix. 这些类型必须来自程序的核心库,并且一组有效的组合依赖于平台。These types must come from the program's core library, and the set of valid combinations is platform-dependent.
//This method has a managed calling convention. This is the same as leaving the managed keyword off.
delegate* managed<int, int>;
// This method will be invoked using whatever the default unmanaged calling convention on the runtime
// platform is. This is platform and architecture dependent and is determined by the CLR at runtime.
delegate* unmanaged<int, int>;
// This method will be invoked using the cdecl calling convention
// Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl
delegate* unmanaged[Cdecl] <int, int>;
// This method will be invoked using the stdcall calling convention, and suppresses GC transition
// Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall
// SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition
delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;
类型之间的转换 delegate*
是根据其签名(包括调用约定)完成的。Conversions between delegate*
types is done based on their signature including the calling convention.
unsafe class Example {
void Conversions() {
delegate*<int, int, int> p1 = ...;
delegate* managed<int, int, int> p2 = ...;
delegate* unmanaged<int, int, int> p3 = ...;
p1 = p2; // okay p1 and p2 have compatible signatures
Console.WriteLine(p2 == p1); // True
p2 = p3; // error: calling conventions are incompatible
}
}
delegate*
类型是指针类型,这意味着它具有标准指针类型的所有功能和限制:A delegate*
type is a pointer type which means it has all of the capabilities and restrictions of a standard pointer type:
- 仅在上下文中有效
unsafe
。Only valid in anunsafe
context. delegate*
只能从上下文调用包含参数或返回类型的方法unsafe
。Methods which contain adelegate*
parameter or return type can only be called from anunsafe
context.- 不能转换为
object
。Cannot be converted toobject
. - 不能用作泛型参数。Cannot be used as a generic argument.
- 可隐式转换
delegate*
为void*
。Can implicitly convertdelegate*
tovoid*
. - 可以从显式转换
void*
为delegate*
。Can explicitly convert fromvoid*
todelegate*
.
限制:Restrictions:
- 自定义特性不能应用于
delegate*
或它的任何元素。Custom attributes cannot be applied to adelegate*
or any of its elements. delegate*
不能将参数标记为params
Adelegate*
parameter cannot be marked asparams
delegate*
类型具有正常指针类型的所有限制。Adelegate*
type has all of the restrictions of a normal pointer type.- 指针算法不能直接对函数指针类型执行。Pointer arithmetic cannot be performed directly on function pointer types.
函数指针语法Function pointer syntax
完整的函数指针语法由以下语法表示:The full function pointer syntax is represented by the following grammar:
pointer_type
: ...
| funcptr_type
;
funcptr_type
: 'delegate' '*' calling_convention_specifier? '<' funcptr_parameter_list funcptr_return_type '>'
;
calling_convention_specifier
: 'managed'
| 'unmanaged' ('[' unmanaged_calling_convention ']')?
;
unmanaged_calling_convention
: 'Cdecl'
| 'Stdcall'
| 'Thiscall'
| 'Fastcall'
| identifier (',' identifier)*
;
funptr_parameter_list
: (funcptr_parameter ',')*
;
funcptr_parameter
: funcptr_parameter_modifier? type
;
funcptr_return_type
: funcptr_return_modifier? return_type
;
funcptr_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
funcptr_return_modifier
: 'ref'
| 'ref readonly'
;
如果未 calling_convention_specifier
提供,则默认值为 managed
。If no calling_convention_specifier
is provided, the default is managed
. 在 calling_convention_specifier
identifier
unmanaged_calling_convention
调用约定的元数据表示形式中介绍了的精确元数据编码以及在中有效的。The precise metadata encoding of the calling_convention_specifier
and what identifier
s are valid in the unmanaged_calling_convention
is covered in Metadata Representation of Calling Conventions.
delegate int Func1(string s);
delegate Func1 Func2(Func1 f);
// Function pointer equivalent without calling convention
delegate*<string, int>;
delegate*<delegate*<string, int>, delegate*<string, int>>;
// Function pointer equivalent with calling convention
delegate* managed<string, int>;
delegate*<delegate* managed<string, int>, delegate*<string, int>>;
函数指针转换Function pointer conversions
在不安全的上下文中,将扩展 (隐式转换) 的可用隐式转换集,使其包含以下隐式指针转换:In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:
- 现有转换Existing conversions
- 如果满足以下所有条件,则从 funcptr _ 类型
F0
到另一 funcptr _ 类型F1
:From funcptr_typeF0
to another funcptr_typeF1
, provided all of the following are true:F0
和F1
具有相同数量的参数,中的每个参数与D0n
F0
ref
out
in
中的相应参数具有相同的、或修饰符D1n
F1
。F0
andF1
have the same number of parameters, and each parameterD0n
inF0
has the sameref
,out
, orin
modifiers as the corresponding parameterD1n
inF1
.- 对于每个 value 参数 (没有
ref
、out
或in
修饰符) 的参数,存在从中的参数类型F0
到中的相应参数类型的标识转换、隐式引用转换或隐式指针转换F1
。For each value parameter (a parameter with noref
,out
, orin
modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type inF0
to the corresponding parameter type inF1
. - 对于每个
ref
、out
或in
参数,中的参数类型与F0
中相应的参数类型相同F1
。For eachref
,out
, orin
parameter, the parameter type inF0
is the same as the corresponding parameter type inF1
. - 如果返回类型是通过值 (no
ref
或ref readonly
) 、标识、隐式引用或隐式指针转换从的返回类型F1
到的返回类型F0
。If the return type is by value (noref
orref readonly
), an identity, implicit reference, or implicit pointer conversion exists from the return type ofF1
to the return type ofF0
. - 如果返回类型是按引用 (
ref
或ref readonly
) ,则的返回类型和修饰符与的ref
F1
返回类型和ref
修饰符相同F0
。If the return type is by reference (ref
orref readonly
), the return type andref
modifiers ofF1
are the same as the return type andref
modifiers ofF0
. - 的调用约定与的
F0
调用约定相同F1
。The calling convention ofF0
is the same as the calling convention ofF1
.
允许目标方法的地址Allow address-of to target methods
现在将允许方法组作为表达式的参数。Method groups will now be allowed as arguments to an address-of expression. 此类表达式的类型将为, delegate*
它具有目标方法的等效签名和托管调用约定:The type of such an expression will be a delegate*
which has the equivalent signature of the target method and a managed calling convention:
unsafe class Util {
public static void Log() { }
void Use() {
delegate*<void> ptr1 = &Util.Log;
// Error: type "delegate*<void>" not compatible with "delegate*<int>";
delegate*<int> ptr2 = &Util.Log;
}
}
在不安全的上下文中, M
F
如果满足以下所有条件,则方法与函数指针类型兼容:In an unsafe context, a method M
is compatible with a function pointer type F
if all of the following are true:
M
和F
具有相同数量的参数,中的每个参数与M
ref
out
in
中的相应参数具有相同的、或修饰符F
。M
andF
have the same number of parameters, and each parameter inM
has the sameref
,out
, orin
modifiers as the corresponding parameter inF
.- 对于每个 value 参数 (没有
ref
、out
或in
修饰符) 的参数,存在从中的参数类型M
到中的相应参数类型的标识转换、隐式引用转换或隐式指针转换F
。For each value parameter (a parameter with noref
,out
, orin
modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type inM
to the corresponding parameter type inF
. - 对于每个
ref
、out
或in
参数,中的参数类型与M
中相应的参数类型相同F
。For eachref
,out
, orin
parameter, the parameter type inM
is the same as the corresponding parameter type inF
. - 如果返回类型是通过值 (no
ref
或ref readonly
) 、标识、隐式引用或隐式指针转换从的返回类型F
到的返回类型M
。If the return type is by value (noref
orref readonly
), an identity, implicit reference, or implicit pointer conversion exists from the return type ofF
to the return type ofM
. - 如果返回类型是按引用 (
ref
或ref readonly
) ,则的返回类型和修饰符与的ref
F
返回类型和ref
修饰符相同M
。If the return type is by reference (ref
orref readonly
), the return type andref
modifiers ofF
are the same as the return type andref
modifiers ofM
. - 的调用约定与的
M
调用约定相同F
。The calling convention ofM
is the same as the calling convention ofF
. 这包括调用约定位,以及非托管标识符中指定的任何调用约定标志。This includes both the calling convention bit, as well as any calling convention flags specified in the unmanaged identifier. M
是静态方法。M
is a static method.
在不安全的上下文中,如果包含的表达式的目标为方法组,而该表达式的目标为方法组,则该表达式的目标为方法组, E
F
前提是至少有 E
一个方法适用于通过使用的参数类型和修饰符构造的参数列表 F
,如下所述。In an unsafe context, an implicit conversion exists from an address-of expression whose target is a method group E
to a compatible function pointer type F
if E
contains at least one method that is applicable in its normal form to an argument list constructed by use of the parameter types and modifiers of F
, as described in the following.
- 选择一个方法,该方法
M
对应于窗体的方法调用E(A)
,其中包含以下修改:A single methodM
is selected corresponding to a method invocation of the formE(A)
with the following modifications:- 参数列表
A
是一个表达式列表,每个表达式都分类为一个变量,并使用类型和修饰符 (ref
、或的out
in
相应 funcptr _ 参数 _ 列表 的)F
。The arguments listA
is a list of expressions, each classified as a variable and with the type and modifier (ref
,out
, orin
) of the corresponding funcptr_parameter_list ofF
. - 候选方法只是那些适用于其普通窗体的方法,而不是以其展开形式适用的方法。The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form.
- 候选方法仅为静态方法。The candidate methods are only those methods that are static.
- 参数列表
- 如果重载决策的算法产生错误,则会发生编译时错误。If the algorithm of overload resolution produces an error, then a compile-time error occurs. 否则,该算法将生成单个最佳方法,
M
该方法的参数数目与F
相同,转换被视为存在。Otherwise, the algorithm produces a single best methodM
having the same number of parameters asF
and the conversion is considered to exist. - 所选的方法
M
必须与) 与函数指针类型一起定义 (兼容F
。The selected methodM
must be compatible (as defined above) with the function pointer typeF
. 否则,将发生编译时错误。Otherwise, a compile-time error occurs. - 转换的结果是类型的函数指针
F
。The result of the conversion is a function pointer of typeF
.
这意味着开发人员可以依赖重载决策规则与地址运算符结合使用:This means developers can depend on overload resolution rules to work in conjunction with the address-of operator:
unsafe class Util {
public static void Log() { }
public static void Log(string p1) { }
public static void Log(int i) { };
void Use() {
delegate*<void> a1 = &Log; // Log()
delegate*<int, void> a2 = &Log; // Log(int i)
// Error: ambiguous conversion from method group Log to "void*"
void* v = &Log;
}
将使用指令实现的地址为的运算符 ldftn
。The address-of operator will be implemented using the ldftn
instruction.
此功能的限制:Restrictions of this feature:
- 仅适用于标记为的方法
static
。Only applies to methods marked asstatic
. - 非
static
本地函数不能在中使用&
。Non-static
local functions cannot be used in&
. 这些方法的实现细节特意不由语言指定。The implementation details of these methods are deliberately not specified by the language. 这包括它们是静态的还是实例的,或者是否与它们一起发出的签名完全相同。This includes whether they are static vs. instance or exactly what signature they are emitted with.
函数指针类型上的运算符Operators on Function Pointer Types
将按如下所示修改不安全代码中的部分:The section in unsafe code on operators is modified as such:
在不安全的上下文中,有多个构造可用于在 _ 不 _funcptr type_s 的所有 _pointer type_s 上运行 _ :In an unsafe context, several constructs are available for operating on all _pointer_type_s that are not _funcptr_type_s:
*
运算符可用于 (指针间接寻址) 执行指针间接寻址。The*
operator may be used to perform pointer indirection (Pointer indirection).->
运算符可用于通过指针访问结构的成员 (指针成员访问) 。The->
operator may be used to access a member of a struct through a pointer (Pointer member access).[]
运算符可用于索引指针 (指针元素访问) 。The[]
operator may be used to index a pointer (Pointer element access).&
运算符可用于获取 () 地址的变量的地址。The&
operator may be used to obtain the address of a variable (The address-of operator).++
和--
运算符可用于递增和递减指针 (指针增量和减量) 。The++
and--
operators may be used to increment and decrement pointers (Pointer increment and decrement).+
和-
运算符可用于 (指针算法) 执行指针算法。The+
and-
operators may be used to perform pointer arithmetic (Pointer arithmetic).==
!=
可以使用、、、、<
>
<=
和=>
运算符来比较指针) (指针比较。The==
,!=
,<
,>
,<=
, and=>
operators may be used to compare pointers (Pointer comparison).stackalloc
运算符可用于从调用堆栈 (固定大小缓冲区) 分配内存。Thestackalloc
operator may be used to allocate memory from the call stack (Fixed size buffers).fixed
语句可用于临时修复变量,因此可以 (fixed 语句) 获取其地址。Thefixed
statement may be used to temporarily fix a variable so its address can be obtained (The fixed statement).在不安全的上下文中,有多个构造可用于在所有 _funcptr type_s 上操作 _ :In an unsafe context, several constructs are available for operating on all _funcptr_type_s:
&
运算符可用于获取静态方法的地址 (允许地址的目标方法) The&
operator may be used to obtain the address of static methods (Allow address-of to target methods)==
!=
可以使用、、、、<
>
<=
和=>
运算符来比较指针) (指针比较。The==
,!=
,<
,>
,<=
, and=>
operators may be used to compare pointers (Pointer comparison).
此外,我们修改中的所有部分 Pointers in expressions
以禁止函数指针类型(和除外) Pointer comparison
The sizeof operator
。Additionally, we modify all the sections in Pointers in expressions
to forbid function pointer types, except Pointer comparison
and The sizeof operator
.
更好的函数成员Better function member
更好的函数成员规范将更改为包含以下行:The better function member specification will be changed to include the following line:
delegate*
比void*
Adelegate*
is more specific thanvoid*
这意味着,可以在和上重载, void*
delegate*
但仍揭示使用地址运算符。This means that it is possible to overload on void*
and a delegate*
and still sensibly use the address-of operator.
类型推断Type Inference
在不安全代码中,对类型推理算法进行了以下更改:In unsafe code, the following changes are made to the type inference algorithms:
输入类型Input types
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#input-types
添加了以下内容:The following is added:
如果
E
是方法组的地址并且T
是函数指针类型,则的所有参数类型T
均为类型为的输入类型E
T
。IfE
is an address-of method group andT
is a function pointer type then all the parameter types ofT
are input types ofE
with typeT
.
输出类型Output types
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#output-types
添加了以下内容:The following is added:
如果
E
是方法组地址并且T
是函数指针类型,则的返回类型T
是类型为的输出类型E
T
。IfE
is an address-of method group andT
is a function pointer type then the return type ofT
is an output type ofE
with typeT
.
输出类型推断Output type inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#output-type-inferences
在项目符号2和3之间添加以下项目符号:The following bullet is added between bullets 2 and 3:
- 如果
E
是方法组的地址,并且T
是具有参数类型和返回类型的函数指针类型T1...Tk
Tb
,且类型为的重载解析E
产生了T1..Tk
返回类型的单个方法U
,则将从到进行 下限推理U
Tb
。IfE
is an address-of method group andT
is a function pointer type with parameter typesT1...Tk
and return typeTb
, and overload resolution ofE
with the typesT1..Tk
yields a single method with return typeU
, then a lower-bound inference is made fromU
toTb
.
精确推断Exact inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#exact-inferences
以下子项目符号将作为方案添加到项目符号2:The following sub-bullet is added as a case to bullet 2:
V
是一个函数指针类型delegate*<V2..Vk, V1>
U
,并且是一个函数指针类型delegate*<U2..Uk, U1>
,的调用约定与V
相同U
,并且的 refness 与Vi
相同Ui
。V
is a function pointer typedelegate*<V2..Vk, V1>
andU
is a function pointer typedelegate*<U2..Uk, U1>
, and the calling convention ofV
is identical toU
, and the refness ofVi
is identical toUi
.
下限推理Lower-bound inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#lower-bound-inferences
以下事例添加到了项目符号3:The following case is added to bullet 3:
V
是一个函数指针类型,delegate*<V2..Vk, V1>
并且存在与相同的函数指针类型delegate*<U2..Uk, U1>
,的U
delegate*<U2..Uk, U1>
调用约定与相同,并且的V
refness 与U
Vi
相同Ui
。V
is a function pointer typedelegate*<V2..Vk, V1>
and there is a function pointer typedelegate*<U2..Uk, U1>
such thatU
is identical todelegate*<U2..Uk, U1>
, and the calling convention ofV
is identical toU
, and the refness ofVi
is identical toUi
.
将从到的推理的第一个项目符号 Ui
Vi
修改为:The first bullet of inference from Ui
to Vi
is modified to:
- 如果不是
U
函数指针类型并且Ui
未知是引用类型,或者是U
函数指针类型并且未知的是函数Ui
指针类型或引用类型,则进行 精确推理IfU
is not a function pointer type andUi
is not known to be a reference type, or ifU
is a function pointer type andUi
is not known to be a function pointer type or a reference type, then an exact inference is made
然后,将从到的推理的第三个项目符号后面添加 Ui
到 Vi
:Then, added after the 3rd bullet of inference from Ui
to Vi
:
- 否则,如果
V
为,delegate*<V2..Vk, V1>
则推理依赖于的第 i 个参数delegate*<V2..Vk, V1>
:Otherwise, ifV
isdelegate*<V2..Vk, V1>
then inference depends on the i-th parameter ofdelegate*<V2..Vk, V1>
:
- 如果 V1:If V1:
- 如果返回值为,则进行 下限推理 。If the return is by value, then a lower-bound inference is made.
- 如果按引用返回,则进行 精确推理 。If the return is by reference, then an exact inference is made.
- 如果 V2 .。。VkIf V2..Vk:
- 如果参数的值为,则进行 上限推理 。If the parameter is by value, then an upper-bound inference is made.
- 如果参数是按引用进行的,则进行 精确推理 。If the parameter is by reference, then an exact inference is made.
上限推理Upper-bound inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#upper-bound-inferences
将以下事例添加到项目符号2:The following case is added to bullet 2:
U
是一个函数指针类型,delegate*<U2..Uk, U1>
并且V
是一个与相同的函数指针类型delegate*<V2..Vk, V1>
,的调用约定与U
相同V
,并且的 refness 与Ui
相同Vi
。U
is a function pointer typedelegate*<U2..Uk, U1>
andV
is a function pointer type which is identical todelegate*<V2..Vk, V1>
, and the calling convention ofU
is identical toV
, and the refness ofUi
is identical toVi
.
将从到的推理的第一个项目符号 Ui
Vi
修改为:The first bullet of inference from Ui
to Vi
is modified to:
- 如果不是
U
函数指针类型并且Ui
未知是引用类型,或者是U
函数指针类型并且未知的是函数Ui
指针类型或引用类型,则进行 精确推理IfU
is not a function pointer type andUi
is not known to be a reference type, or ifU
is a function pointer type andUi
is not known to be a function pointer type or a reference type, then an exact inference is made
然后,将从到的推理的第三个项目符号后面添加 Ui
到 Vi
:Then added after the 3rd bullet of inference from Ui
to Vi
:
- 否则,如果
U
为,delegate*<U2..Uk, U1>
则推理依赖于的第 i 个参数delegate*<U2..Uk, U1>
:Otherwise, ifU
isdelegate*<U2..Uk, U1>
then inference depends on the i-th parameter ofdelegate*<U2..Uk, U1>
:
- 如果 U1:If U1:
- 如果返回值为,则进行 上限推理 。If the return is by value, then an upper-bound inference is made.
- 如果按引用返回,则进行 精确推理 。If the return is by reference, then an exact inference is made.
- 如果英式If U2..Uk:
- 如果参数的值为,则进行 下限推理 。If the parameter is by value, then a lower-bound inference is made.
- 如果参数是按引用进行的,则进行 精确推理 。If the parameter is by reference, then an exact inference is made.
in
、 out
、 ref readonly
参数和返回类型的元数据表示形式Metadata representation of in
, out
, and ref readonly
parameters and return types
函数指针签名没有参数标志位置,因此,我们必须使用 modreqs 对参数和返回类型是 in
、 out
还是进行编码 ref readonly
。Function pointer signatures have no parameter flags location, so we must encode whether parameters and the return type are in
, out
, or ref readonly
by using modreqs.
in
将 System.Runtime.InteropServices.InAttribute
作为 modreq
参数或返回类型上的 ref 说明符的重复使用,以表示以下内容:We reuse System.Runtime.InteropServices.InAttribute
, applied as a modreq
to the ref specifier on a parameter or return type, to mean the following:
- 如果应用于参数 ref 说明符,此参数将被视为
in
。If applied to a parameter ref specifier, this parameter is treated asin
. - 如果应用于返回类型 ref 说明符,返回类型将被视为
ref readonly
。If applied to the return type ref specifier, the return type is treated asref readonly
.
out
我们使用将 System.Runtime.InteropServices.OutAttribute
作为 modreq
参数类型上的 ref 说明符的,以表示该参数是一个 out
参数。We use System.Runtime.InteropServices.OutAttribute
, applied as a modreq
to the ref specifier on a parameter type, to mean that the parameter is an out
parameter.
错误Errors
- 将作为 modreq 应用于返回类型是错误的
OutAttribute
。It is an error to applyOutAttribute
as a modreq to a return type. - 将
InAttribute
和OutAttribute
作为 modreq 应用到参数类型是错误的。It is an error to apply bothInAttribute
andOutAttribute
as a modreq to a parameter type. - 如果通过 modopt 指定任一方法,则它们将被忽略。If either are specified via modopt, they are ignored.
调用约定的元数据表示形式Metadata Representation of Calling Conventions
调用约定在元数据的方法签名中通过签名中标志的组合进行编码 CallKind
,而在签名的开头有零个或多个 modopt
。Calling conventions are encoded in a method signature in metadata by a combination of the CallKind
flag in the signature and zero or more modopt
s at the start of the signature. ECMA-335 当前声明标志中的以下元素 CallKind
:ECMA-335 currently declares the following elements in the CallKind
flag:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
其中,c # 中的函数指针将支持除之外的所有函数 varargs
。Of these, function pointers in C# will support all but varargs
.
此外,运行时 (和最终 335) 将更新为 CallKind
在新平台上包含新的。In addition, the runtime (and eventually 335) will be updated to include a new CallKind
on new platforms. 目前没有正式的名称,但本文档将用作 unmanaged ext
新的可扩展调用约定格式的占位符作为替代。This does not have a formal name currently, but this document will use unmanaged ext
as a placeholder to stand for the new extensible calling convention format. 不包含 modopt
, unmanaged ext
是平台默认调用约定, unmanaged
不带方括号。With no modopt
s, unmanaged ext
is the platform default calling convention, unmanaged
without the square brackets.
将映射 calling_convention_specifier
到 CallKind
Mapping the calling_convention_specifier
to a CallKind
calling_convention_specifier
省略或指定为的将 managed
映射到 default
CallKind
。A calling_convention_specifier
that is omitted, or specified as managed
, maps to the default
CallKind
. 这是 CallKind
未特性化的任何方法的默认值 UnmanagedCallersOnly
。This is default CallKind
of any method not attributed with UnmanagedCallersOnly
.
C # 识别从 ECMA 335 映射到特定现有非托管的4个特殊标识符 CallKind
。C# recognizes 4 special identifiers that map to specific existing unmanaged CallKind
s from ECMA 335. 若要进行此映射,必须自行指定这些标识符,无需任何其他标识符,并且此要求将编码到中的规范中 unmanaged_calling_convention
。In order for this mapping to occur, these identifiers must be specified on their own, with no other identifiers, and this requirement is encoded into the spec for unmanaged_calling_convention
s. 这些标识符 Cdecl
Thiscall
Stdcall
Fastcall
unmanaged cdecl
unmanaged thiscall
unmanaged stdcall
unmanaged fastcall
分别分别对应于、、和。These identifiers are Cdecl
, Thiscall
, Stdcall
, and Fastcall
, which correspond to unmanaged cdecl
, unmanaged thiscall
, unmanaged stdcall
, and unmanaged fastcall
, respectively. 如果指定了多个 identifer
,或单个不 identifier
属于专门识别的标识符,我们将对标识符执行特殊的名称查找,规则如下:If more than one identifer
is specified, or the single identifier
is not of the specially recognized identifiers, we perform special name lookup on the identifier with the following rules:
- 在前面追加
identifier
字符串CallConv
We prepend theidentifier
with the stringCallConv
- 我们仅查看在命名空间中定义的类型
System.Runtime.CompilerServices
。We look only at types defined in theSystem.Runtime.CompilerServices
namespace. - 我们仅查看在应用程序的核心库中定义的类型,它是用于定义
System.Object
和没有依赖项的库。We look only at types defined in the core library of the application, which is the library that definesSystem.Object
and has no dependencies. - 我们仅探讨公共类型。We look only at public types.
如果在中指定的所有上 identifier
进行查找成功 unmanaged_calling_convention
,我们会将编码 CallKind
为 unmanaged ext
,并对 modopt
函数指针签名开头的一组中的每个已解析类型进行编码。If lookup succeeds on all of the identifier
s specified in an unmanaged_calling_convention
, we encode the CallKind
as unmanaged ext
, and encode each of the resolved types in the set of modopt
s at the beginning of the function pointer signature. 请注意,这些规则意味着用户不能 identifier
使用作为的前缀 CallConv
,因为这会导致查找 CallConvCallConvVectorCall
。As a note, these rules mean that users cannot prefix these identifier
s with CallConv
, as that will result in looking up CallConvCallConvVectorCall
.
解释元数据时,首先查看 CallKind
。When interpreting metadata, we first look at the CallKind
. 如果不是 unmanaged ext
,我们将忽略 modopt
返回类型上的所有,以确定调用约定,并且仅使用 CallKind
。If it is anything other than unmanaged ext
, we ignore all modopt
s on the return type for the purposes of determining the calling convention, and use only the CallKind
. 如果 CallKind
为 unmanaged ext
,我们将查看函数指针类型开头的 modopts,并采用满足以下要求的所有类型的并集:If the CallKind
is unmanaged ext
, we look at the modopts at the start of the function pointer type, taking the union of all types that meet the following requirements:
- 是在核心库中定义的,它是不引用其他库和定义的库
System.Object
。The is defined in the core library, which is the library that references no other libraries and definesSystem.Object
. - 类型是在命名空间中定义的
System.Runtime.CompilerServices
。The type is defined in theSystem.Runtime.CompilerServices
namespace. - 类型以前缀开头
CallConv
。The type starts with the prefixCallConv
. - 类型是公共的。The type is public.
这表示 identifier
unmanaged_calling_convention
当在源中定义函数指针类型时对中的执行查找时必须找到的类型。These represent the types that must be found when performing lookup on the identifier
s in an unmanaged_calling_convention
when defining a function pointer type in source.
CallKind
unmanaged ext
如果目标运行时不支持该功能,则尝试将函数指针与的结合使用是错误的。It is an error to attempt to use a function pointer with a CallKind
of unmanaged ext
if the target runtime does not support the feature. 这将通过查找是否存在常量来确定 System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind
。This will be determined by looking for the presence of the System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind
constant. 如果此常量存在,则将运行时视为支持该功能。If this constant is present, the runtime is considered to support the feature.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
是 CLR 用来指示应使用特定调用约定调用方法的属性。System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
is an attribute used by the CLR to indicate that a method should be called with a specific calling convention. 因此,我们引入了以下对使用该属性的支持:Because of this, we introduce the following support for working with the attribute:
- 直接从 c # 中调用使用此属性批注的方法是错误的。It is an error to directly call a method annotated with this attribute from C#. 用户必须获取指向方法的函数指针,然后调用该指针。Users must obtain a function pointer to the method and then invoke that pointer.
- 将该特性应用于一般静态方法或普通静态本地函数之外的任何内容是错误的。It is an error to apply the attribute to anything other than an ordinary static method or ordinary static local function. C # 编译器会将从元数据导入的任何非静态或静态非普通方法标记为该语言不支持的属性。The C# compiler will mark any non-static or static non-ordinary methods imported from metadata with this attribute as unsupported by the language.
- 对于用特性标记的方法,如果该方法的参数或返回类型不是,则是错误的
unmanaged_type
。It is an error for a method marked with the attribute to have a parameter or return type that is not anunmanaged_type
. - 如果用特性标记的方法具有类型参数,则是错误的,即使这些类型参数被约束为也是如此
unmanaged
。It is an error for a method marked with the attribute to have type parameters, even if those type parameters are constrained tounmanaged
. - 泛型类型中的方法使用属性进行标记是错误的。It is an error for a method in a generic type to be marked with the attribute.
- 将用特性标记的方法转换为委托类型是错误的。It is an error to convert a method marked with the attribute to a delegate type.
- 为指定的任何类型不
UnmanagedCallersOnly.CallConvs
符合modopt
在元数据中调用约定的要求是错误的。It is an error to specify any types forUnmanagedCallersOnly.CallConvs
that do not meet the requirements for calling conventionmodopt
s in metadata.
确定使用有效特性标记的方法的调用约定时 UnmanagedCallersOnly
,编译器将对属性中指定的类型执行以下检查, CallConvs
以确定 CallKind
modopt
应该用于确定调用约定的有效和。When determining the calling convention of a method marked with a valid UnmanagedCallersOnly
attribute, the compiler performs the following checks on the types specified in the CallConvs
property to determine the effective CallKind
and modopt
s that should be used to determine the calling convention:
- 如果未指定类型,则将
CallKind
视为,在unmanaged ext
modopt
函数指针类型的开头没有调用约定 s。If no types are specified, theCallKind
is treated asunmanaged ext
, with no calling conventionmodopt
s at the start of the function pointer type. - 如果指定了一种类型,并且该类型命名为、、或,则将
CallConvCdecl
CallConvThiscall
CallConvStdcall
CallConvFastcall
CallKind
分别处理为unmanaged cdecl
、unmanaged thiscall
、unmanaged stdcall
或,并且unmanaged fastcall
modopt
函数指针类型的开头没有调用约定 s。If there is one type specified, and that type is namedCallConvCdecl
,CallConvThiscall
,CallConvStdcall
, orCallConvFastcall
, theCallKind
is treated asunmanaged cdecl
,unmanaged thiscall
,unmanaged stdcall
, orunmanaged fastcall
, respectively, with no calling conventionmodopt
s at the start of the function pointer type. - 如果指定了多个类型,或未将单个类型命名为上面专门调用的类型之一,则
CallKind
会将视为unmanaged ext
,并将指定的类型的联合视为modopt
函数指针类型的开头。If multiple types are specified or the single type is not named one of the specially called out types above, theCallKind
is treated asunmanaged ext
, with the union of the types specified treated asmodopt
s at the start of the function pointer type.
然后,编译器会查看此有效 CallKind
和 modopt
集合,并使用正常的元数据规则来确定函数指针类型的最终调用约定。The compiler then looks at this effective CallKind
and modopt
collection and uses normal metadata rules to determine the final calling convention of the function pointer type.
未解决的问题Open Questions
检测运行时支持 unmanaged ext
Detecting runtime support for unmanaged ext
https://github.com/dotnet/runtime/issues/38135 跟踪添加此标志。https://github.com/dotnet/runtime/issues/38135 tracks adding this flag. 根据评审的反馈,我们将使用问题中指定的属性,或使用的状态 UnmanagedCallersOnlyAttribute
作为确定运行时是否支持的标志 unmanaged ext
。Depending on the feedback from review, we will either use the property specified in the issue, or use the presence of UnmanagedCallersOnlyAttribute
as the flag that determines whether the runtimes supports unmanaged ext
.
注意事项Considerations
允许实例方法Allow instance methods
可以通过使用 EXPLICITTHIS
instance
c # 代码) 中 (指定的 CLI 调用约定,将该建议扩展为支持实例方法。The proposal could be extended to support instance methods by taking advantage of the EXPLICITTHIS
CLI calling convention (named instance
in C# code). 这种形式的 CLI 函数指针将 this
参数作为函数指针语法的显式第一个参数。This form of CLI function pointers puts the this
parameter as an explicit first parameter of the function pointer syntax.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
这听起来很复杂,但在提议中增加一些复杂化。This is sound but adds some complication to the proposal. 特别是,由于与调用约定不同的函数指针 instance
并不 managed
兼容,即使两种情况都用于使用相同的 c # 签名调用托管方法。Particularly because function pointers which differed by the calling convention instance
and managed
would be incompatible even though both cases are used to invoke managed methods with the same C# signature. 此外,在每种情况下,在这种情况下,有一个简单的解决方法是很有价值的:使用 static
本地函数。Also in every case considered where this would be valuable to have there was a simple work around: use a static
local function.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
声明时不需要 unsafeDon't require unsafe at declaration
不需要 unsafe
每次使用 delegate*
,只需要在将方法组转换为的点上使用 delegate*
。Instead of requiring unsafe
at every use of a delegate*
, only require it at the point where a method group is converted to a delegate*
. 在这种情况下,核心安全问题就 (知道在值处于活动状态时不能卸载包含程序集) 。This is where the core safety issues come into play (knowing that the containing assembly cannot be unloaded while the value is alive). 如果需要 unsafe
其他位置,则可能会出现过多。Requiring unsafe
on the other locations can be seen as excessive.
这就是设计的最初设计方式。This is how the design was originally intended. 但产生的语言规则觉得非常笨拙。But the resulting language rules felt very awkward. 这是不可能的,因为这是一个指针值,并且即使不使用关键字,它仍可查看 unsafe
。It's impossible to hide the fact that this is a pointer value and it kept peeking through even without the unsafe
keyword. 例如,无法允许转换为, object
它不能是等的成员。 class
C # 设计需要 unsafe
使用所有指针,因此这种设计遵循这一设计。For example the conversion to object
can't be allowed, it can't be a member of a class
, etc ... The C# design is to require unsafe
for all pointer uses and hence this design follows that.
开发人员仍可使用与 delegate*
今天普通指针类型相同的方式在值顶部呈现安全包装。Developers will still be capable of presenting a safe wrapper on top of delegate*
values the same way that they do for normal pointer types today. 请注意以下几点:Consider:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
使用委托Using delegates
delegate*
只需使用 delegate
具有以下类型的现有类型,而不是使用新的语法元素 *
:Instead of using a new syntax element, delegate*
, simply use existing delegate
types with a *
following the type:
Func<object, object, bool>* ptr = &object.ReferenceEquals;
可以通过使用指定值的属性对类型进行批注来处理调用约定 delegate
CallingConvention
。Handling calling convention can be done by annotating the delegate
types with an attribute that specifies a CallingConvention
value. 缺少特性会表示托管调用约定。The lack of an attribute would signify the managed calling convention.
在 IL 中对此进行编码会出现问题。Encoding this in IL is problematic. 基础值需要表示为一个指针,但它也必须:The underlying value needs to be represented as a pointer yet it also must:
- 具有唯一的类型,以允许具有不同函数指针类型的重载。Have a unique type to allow for overloads with different function pointer types.
- 对于跨程序集边界的 OHI 是等效的。Be equivalent for OHI purposes across assembly boundaries.
最后一点特别有问题。The last point is particularly problematic. 这意味着,使用的每个程序集都 Func<int>*
必须在元数据中对等效类型进行编码,即使 Func<int>*
是在程序集中定义的,但不进行控制。This mean that every assembly which uses Func<int>*
must encode an equivalent type in metadata even though Func<int>*
is defined in an assembly though don't control.
此外,在不是 mscorlib 的程序集中,使用名称定义的任何其他类型 System.Func<T>
必须与 mscorlib 中定义的版本不同。Additionally any other type which is defined with the name System.Func<T>
in an assembly that is not mscorlib must be different than the version defined in mscorlib.
研究的一个选项是发出这样的指针 mod_req(Func<int>) void*
。One option that was explored was emitting such a pointer as mod_req(Func<int>) void*
. 但这不起作用,因为 mod_req
无法绑定到 TypeSpec
,因此不能以泛型实例化为目标。This doesn't work though as a mod_req
cannot bind to a TypeSpec
and hence cannot target generic instantiations.
命名函数指针Named function pointers
函数指针语法可能比较繁琐,尤其是在复杂情况下,例如嵌套函数指针。The function pointer syntax can be cumbersome, particularly in complex cases like nested function pointers. 不是让开发人员在每次使用时都可以输入签名的函数指针的命名声明 delegate
。Rather than have developers type out the signature every time the language could allow for named declarations of function pointers as is done with delegate
.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
此处所述的问题是基础 CLI 基元没有名称,因此,这只是一个 c # 发明,需要使用一种元数据才能实现。Part of the problem here is the underlying CLI primitive doesn't have names hence this would be purely a C# invention and require a bit of metadata work to enable. 这是可行的,但这是一个重要的工作。That is doable but is a significant about of work. 实质上,它要求 c # 将类型定义表与这些名称一起使用。It essentially requires C# to have a companion to the type def table purely for these names.
此外,当检查命名函数指针的参数时,我们发现它们可以同样适用于许多其他方案。Also when the arguments for named function pointers were examined we found they could apply equally well to a number of other scenarios. 例如,将命名元组声明为在所有情况下都可以轻松地在所有情况下都需要键入完整的签名。For example it would be just as convenient to declare named tuples to reduce the need to type out the full signature in all cases.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
讨论后,我们决定不允许类型为的声明声明 delegate*
。After discussion we decided to not allow named declaration of delegate*
types. 如果我们发现,根据客户使用反馈,需要特别需要此功能,我们将调查适用于函数指针、元组、泛型等的命名解决方案。这种形式的可能与其他建议类似,如 typedef
语言的完全支持。If we find there is significant need for this based on customer usage feedback then we will investigate a naming solution that works for function pointers, tuples, generics, etc ... This is likely to be similar in form to other suggestions like full typedef
support in the language.
未来的注意事项Future Considerations
静态委托static delegates
这是指允许类型声明的 建议 , delegate
这些类型只能引用 static
成员。This refers to the proposal to allow for the declaration of delegate
types which can only refer to static
members. 其优势在于,此类 delegate
实例可以在性能敏感的情况下免费分配和更好地分配。The advantage being that such delegate
instances can be allocation free and better in performance sensitive scenarios.
如果实现函数指针功能, static delegate
建议可能会关闭。此功能的建议优点是分配免费性质。If the function pointer feature is implemented the static delegate
proposal will likely be closed out. The proposed advantage of that feature is the allocation free nature. 但最近的调查发现,由于程序集卸载,无法实现。However recent investigations have found that is not possible to achieve due to assembly unloading. 必须有一个从到它所引用的方法的强句柄, static delegate
以防止程序集从其下卸载。There must be a strong handle from the static delegate
to the method it refers to in order to keep the assembly from being unloaded out from under it.
若要维护每个 static delegate
实例,则需要分配一个新的句柄,该句柄运行到提议的目标。To maintain every static delegate
instance would be required to allocate a new handle which runs counter to the goals of the proposal. 在某些设计中,分配可能会分摊到每个调用站点的单个分配,但这有点复杂,不值得权衡。There were some designs where the allocation could be amortized to a single allocation per call-site but that was a bit complex and didn't seem worth the trade off.
这意味着开发人员实质上必须决定以下权衡:That means developers essentially have to decide between the following trade offs:
- 程序集卸载面临的安全性:这需要分配,因此
delegate
已是一个足够的选项。Safety in the face of assembly unloading: this requires allocations and hencedelegate
is already a sufficient option. - 程序集卸载没有任何安全:使用
delegate*
。No safety in face of assembly unloading: use adelegate*
. 这可以包装在中struct
,以允许unsafe
在其余代码的上下文之外使用。This can be wrapped in astruct
to allow usage outside anunsafe
context in the rest of the code.