関数ポインター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
Unsafe コンテキストでは、次の暗黙的なポインター変換を含むように、使用可能な暗黙の変換 (暗黙の変換) のセットが拡張されます。In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:
- 既存の変換Existing conversions
- 次のすべてに該当する場合は、ファンク ptr _ 型 から別のファンク
F0
ptr _ 型 にすること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
.- 各値パラメーター (、、または修飾子を持たないパラメーター) では、の
ref
out
パラメーターの型から、のin
対応するパラメーターの型に、id 変換、暗黙の参照変換、または暗黙的なポインター変換が存在し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
.- 戻り値の型が値渡し (
ref
または) である場合ref readonly
、の戻り値の型からの戻り値の型に、id、暗黙の参照、または暗黙的なポインターの変換が存在し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;
}
}
Unsafe コンテキストでは、 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
.- 各値パラメーター (、、または修飾子を持たないパラメーター) では、の
ref
out
パラメーターの型から、のin
対応するパラメーターの型に、id 変換、暗黙の参照変換、または暗黙的なポインター変換が存在し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
.- 戻り値の型が値渡し (
ref
または) である場合ref readonly
、の戻り値の型からの戻り値の型に、id、暗黙の参照、または暗黙的なポインターの変換が存在し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.
Unsafe コンテキストでは、 E
F
E
次に示すように、のパラメーターの型と修飾子を使用して構築された引数リストに通常の形式で適用可能なメソッドが少なくとも1つ含まれている場合、には、ターゲットがメソッドグループであるアドレス式からの暗黙的な変換が存在し 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
フォームのメソッド呼び出しに対応する1つのメソッドが選択され、E(A)
次のように変更されます。A single methodM
is selected corresponding to a method invocation of the formE(A)
with the following modifications:- 引数リスト
A
は式のリストであり、それぞれが変数として分類され、ref
out
in
の対応するファンク ptr _ パラメーター _ リスト の型および修飾子 (、、または) を使用し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. それ以外の場合、アルゴリズムでは、と同じ数のパラメーターを持つ1つの最適なメソッドが生成され、
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
次のように、演算子の unsafe コードのセクションが変更されます。The section in unsafe code on operators is modified as such:
Unsafe コンテキストでは、type_s _funcptr されていないすべての _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).Unsafe コンテキストでは、すべての _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*
sensibly 演算子を引き続き使用することができます。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
1 つのメソッドが生成されると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
であり、の refnessVi
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
U
Vi
と同じであり、の ref の規則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
次に、からの推定の3番目の行頭文字の後にを追加し 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
であり、の refnessUi
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
次に、推論の3番目の行頭文字の後 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.
- U2..府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
関数ポインターシグネチャにはパラメーターフラグの場所がないため、パラメーターと戻り値の型が in
、、 out
または ref readonly
modreqs 要件を使用しているかどうかをエンコードする必要があります。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
- これは、
OutAttribute
戻り値の型に modreq として適用するとエラーになります。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
フラグとシグネチャの先頭にある0個以上のを組み合わせて、メタデータのメソッドシグネチャでエンコードされ 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. 複数のが指定されている場合、または1つのが特別に認識された識別子では 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.
で指定されたすべてのに対して lookup が成功した場合は 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
. - 型は public です。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
関数ポインター型の先頭には呼び出し規約がありません。If no types are specified, theCallKind
is treated asunmanaged ext
, with no calling conventionmodopt
s at the start of the function pointer type. - 1つの型が指定されていて、その型が、、、またはという名前である場合、はそれぞれ、、、
CallConvCdecl
CallConvThiscall
CallConvStdcall
CallConvFastcall
CallKind
またはとして扱われ、unmanaged cdecl
unmanaged thiscall
unmanaged stdcall
unmanaged fastcall
modopt
関数ポインター型の先頭には呼び出し規則がありません。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. - 複数の型が指定されている場合、または1つの型が上記の特別に呼び出された型のいずれかという名前になっていない場合、は
CallKind
として扱われunmanaged ext
ます。指定された型の共用体は、modopt
関数ポインター型の開始時に s として扱われます。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
CLI の呼び出し規約 (C# コードでは) を利用して、インスタンスメソッドをサポートするように拡張でき instance
ます。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);
}
}
宣言で unsafe を必要としないDon'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.
探索された1つのオプションは、このようなポインターをとして出力することでした 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# では、これらの名前の型 def テーブルに対するコンパニオンが必要です。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. いくつかの設計では、割り当てを1つの呼び出しサイトに1回の割り当てに償却できましたが、それは少し複雑で、トレードオフではないと思われました。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.