関数ポインター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:

  • コンテキストでのみ有効です unsafeOnly valid in an unsafe context.
  • delegate*パラメーターまたは戻り値の型を含むメソッドは、コンテキストからのみ呼び出すことができ unsafe ます。Methods which contain a delegate* parameter or return type can only be called from an unsafe context.
  • をに変換できません objectCannot be converted to object.
  • を汎用引数として使用することはできません。Cannot be used as a generic argument.
  • delegate*をに暗黙的に変換でき void* ます。Can implicitly convert delegate* to void*.
  • からに明示的 void* に変換でき delegate* ます。Can explicitly convert from void* to delegate*.

制限:Restrictions:

  • カスタム属性は、 delegate* またはその要素には適用できません。Custom attributes cannot be applied to a delegate* or any of its elements.
  • delegate*パラメーターをとしてマークすることはできません。paramsA delegate* parameter cannot be marked as params
  • 型には、 delegate* 通常のポインター型のすべての制限があります。A delegate* 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 identifiers 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_type F0 to another funcptr_type F1, provided all of the following are true:
    • F0F1 のパラメーターの数は同じで、の各パラメーターには D0n F0 refout in の対応するパラメーターと同じ、、またはの修飾子があり D1n F1 ます。F0 and F1 have the same number of parameters, and each parameter D0n in F0 has the same ref, out, or in modifiers as the corresponding parameter D1n in F1.
    • 各値パラメーター (、、または修飾子を持たないパラメーター) では、の ref out パラメーターの型から、の in 対応するパラメーターの型に、id 変換、暗黙の参照変換、または暗黙的なポインター変換が存在し F0 F1 ます。For each value parameter (a parameter with no ref, out, or in modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in F0 to the corresponding parameter type in F1.
    • refout 、またはパラメーターごとに in 、のパラメーターの型 F0 は、の対応するパラメーターの型と同じに F1 なります。For each ref, out, or in parameter, the parameter type in F0 is the same as the corresponding parameter type in F1.
    • 戻り値の型が値渡し ( ref または) である場合 ref readonly 、の戻り値の型からの戻り値の型に、id、暗黙の参照、または暗黙的なポインターの変換が存在し F1 F0 ます。If the return type is by value (no ref or ref readonly), an identity, implicit reference, or implicit pointer conversion exists from the return type of F1 to the return type of F0.
    • 戻り値の型が参照渡し ( ref または) である場合 ref readonly 、の戻り値の型と修飾子は、 ref F1 の戻り値の型および修飾子と同じに ref F0 なります。If the return type is by reference (ref or ref readonly), the return type and ref modifiers of F1 are the same as the return type and ref modifiers of F0.
    • の呼び出し規約 F0 は、の呼び出し規約と同じです F1The calling convention of F0 is the same as the calling convention of F1.

ターゲットメソッドへのアドレスの許可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:

  • MF のパラメーターの数は同じで、の各パラメーターには M refout in の対応するパラメーターと同じ、、またはの修飾子があり F ます。M and F have the same number of parameters, and each parameter in M has the same ref, out, or in modifiers as the corresponding parameter in F.
  • 各値パラメーター (、、または修飾子を持たないパラメーター) では、の ref out パラメーターの型から、の in 対応するパラメーターの型に、id 変換、暗黙の参照変換、または暗黙的なポインター変換が存在し M F ます。For each value parameter (a parameter with no ref, out, or in modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in M to the corresponding parameter type in F.
  • refout 、またはパラメーターごとに in 、のパラメーターの型 M は、の対応するパラメーターの型と同じに F なります。For each ref, out, or in parameter, the parameter type in M is the same as the corresponding parameter type in F.
  • 戻り値の型が値渡し ( ref または) である場合 ref readonly 、の戻り値の型からの戻り値の型に、id、暗黙の参照、または暗黙的なポインターの変換が存在し F M ます。If the return type is by value (no ref or ref readonly), an identity, implicit reference, or implicit pointer conversion exists from the return type of F to the return type of M.
  • 戻り値の型が参照渡し ( ref または) である場合 ref readonly 、の戻り値の型と修飾子は、 ref F の戻り値の型および修飾子と同じに ref M なります。If the return type is by reference (ref or ref readonly), the return type and ref modifiers of F are the same as the return type and ref modifiers of M.
  • の呼び出し規約 M は、の呼び出し規約と同じです FThe calling convention of M is the same as the calling convention of F. これには、呼び出し規約ビットと、アンマネージ識別子で指定されている呼び出し規約フラグの両方が含まれます。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 method M is selected corresponding to a method invocation of the form E(A) with the following modifications:
    • 引数リスト A は式のリストであり、それぞれが変数として分類され、 ref out in の対応するファンク ptr _ パラメーター _ リスト の型および修飾子 (、、または) を使用し F ます。The arguments list A is a list of expressions, each classified as a variable and with the type and modifier (ref, out, or in) of the corresponding funcptr_parameter_list of F.
    • 候補となるメソッドは、通常の形式で適用可能なメソッドのみであり、拡張された形式では適用できません。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 method M having the same number of parameters as F and the conversion is considered to exist.
  • 選択したメソッドは、 M 関数ポインター型と互換性がある (前述のように) 必要があり F ます。The selected method M must be compatible (as defined above) with the function pointer type F. それ以外の場合は、コンパイル時のエラーが発生します。Otherwise, a compile-time error occurs.
  • 変換の結果は、型の関数ポインターになり F ます。The result of the conversion is a function pointer of type F.

これは、開発者がアドレス演算子と組み合わせて動作するために、オーバーロードの解決規則に依存することを意味します。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 as static.
  • ローカル以外 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:

Unsafe コンテキストでは、すべての _funcptr type_s での操作に使用できる構成要素がいくつかあり _ ます。In an unsafe context, several constructs are available for operating on all _funcptr_type_s:

また、とを除き、のすべてのセクションを変更し 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*A delegate* is more specific than void*

つまり、とに対してオーバーロードを実行 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 なります。If E is an address-of method group and T is a function pointer type then all the parameter types of T are input types of E with type T.

出力の種類Output types

https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#output-types

次のものが追加されます。The following is added:

Eがアドレスメソッドグループであり、 T 関数ポインター型である場合、の戻り値の型 T は型のの出力型に E T なります。If E is an address-of method group and T is a function pointer type then the return type of T is an output type of E with type T.

出力の型の推論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 ます。If E is an address-of method group and T is a function pointer type with parameter types T1...Tk and return type Tb, and overload resolution of E with the types T1..Tk yields a single method with return type U, then a lower-bound inference is made from U to Tb.

正確な推論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 type delegate*<V2..Vk, V1> and U is a function pointer type delegate*<U2..Uk, U1>, and the calling convention of V is identical to U, and the refness of Vi is identical to Ui.

下限の推論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 type delegate*<V2..Vk, V1> and there is a function pointer type delegate*<U2..Uk, U1> such that U is identical to delegate*<U2..Uk, U1>, and the calling convention of V is identical to U, and the refness of Vi is identical to Ui.

からへの推論の最初の行頭文字 Ui Vi は、次のように変更されます。The first bullet of inference from Ui to Vi is modified to:

  • が関数ポインター型ではなく、参照型でもない場合、またはが関数ポインター型であり、が関数ポインター型 U Ui または参照型では U ない場合 Ui正確な推論 が行われます。If U is not a function pointer type and Ui is not known to be a reference type, or if U is a function pointer type and Ui 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, if V is delegate*<V2..Vk, V1> then inference depends on the i-th parameter of delegate*<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 type delegate*<U2..Uk, U1> and V is a function pointer type which is identical to delegate*<V2..Vk, V1>, and the calling convention of U is identical to V, and the refness of Ui is identical to Vi.

からへの推論の最初の行頭文字 Ui Vi は、次のように変更されます。The first bullet of inference from Ui to Vi is modified to:

  • が関数ポインター型ではなく、参照型でもない場合、またはが関数ポインター型であり、が関数ポインター型 U Ui または参照型では U ない場合 Ui正確な推論 が行われます。If U is not a function pointer type and Ui is not known to be a reference type, or if U is a function pointer type and Ui 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, if U is delegate*<U2..Uk, U1> then inference depends on the i-th parameter of delegate*<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.

inout 、およびの各 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 as in.
  • 戻り値の型 ref 指定子に適用された場合、戻り値の型はとして扱われ ref readonly ます。If applied to the return type ref specifier, the return type is treated as ref 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 apply OutAttribute as a modreq to a return type.
  • との両方を InAttribute OutAttribute パラメーター型に modreq として適用すると、エラーになります。It is an error to apply both InAttribute and OutAttribute 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 modopts 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 modopts, unmanaged ext is the platform default calling convention, unmanaged without the square brackets.

をにマップする calling_convention_specifier``CallKindMapping 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 、に属性が設定されていないメソッドの既定値です UnmanagedCallersOnlyThis 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 CallKinds 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_conventions. これらの識別子は、それぞれ、、、およびに対応する、、、 Cdecl Thiscall およびです Stdcall Fastcall unmanaged cdecl unmanaged thiscall unmanaged stdcall unmanaged fastcallThese 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``CallConvWe prepend the identifier with the string CallConv
  • 名前空間で定義されている型のみを検索 System.Runtime.CompilerServices します。We look only at types defined in the System.Runtime.CompilerServices namespace.
  • ここでは、アプリケーションのコアライブラリに定義されている型についてのみ説明します。これは、 System.Object 依存関係を定義していないライブラリです。We look only at types defined in the core library of the application, which is the library that defines System.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 identifiers specified in an unmanaged_calling_convention, we encode the CallKind as unmanaged ext, and encode each of the resolved types in the set of modopts at the beginning of the function pointer signature. 注として、これらの規則は、ユーザーが検索結果としてをにプレフィックス付けることができないことを意味し identifier CallConv CallConvCallConvVectorCall ます。As a note, these rules mean that users cannot prefix these identifiers 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 modopts 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.ObjectThe is defined in the core library, which is the library that references no other libraries and defines System.Object.
  • 型は名前空間で定義されて System.Runtime.CompilerServices います。The type is defined in the System.Runtime.CompilerServices namespace.
  • 型はプレフィックスで始まり CallConv ます。The type starts with the prefix CallConv.
  • 型は public です。The type is public.

これらは、 identifier unmanaged_calling_convention ソースで関数ポインター型を定義するときに、内ので参照を実行するときに検出される必要がある型を表します。These represent the types that must be found when performing lookup on the identifiers 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 an unmanaged_type.
  • 属性でマークされたメソッドがに制約されていても、型パラメーターを持つことは unmanaged できません。It is an error for a method marked with the attribute to have type parameters, even if those type parameters are constrained to unmanaged.
  • ジェネリック型のメソッドが属性でマークされていると、エラーになります。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 for UnmanagedCallersOnly.CallConvs that do not meet the requirements for calling convention modopts 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 modopts that should be used to determine the calling convention:

  • 型が指定されていない場合、は CallKind として処理され、 unmanaged ext modopt 関数ポインター型の先頭には呼び出し規約がありません。If no types are specified, the CallKind is treated as unmanaged ext, with no calling convention modopts 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 named CallConvCdecl, CallConvThiscall, CallConvStdcall, or CallConvFastcall, the CallKind is treated as unmanaged cdecl, unmanaged thiscall, unmanaged stdcall, or unmanaged fastcall, respectively, with no calling convention modopts 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, the CallKind is treated as unmanaged ext, with the union of the types specified treated as modopts 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 extDetecting 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. また、どのような場合でも、ローカル関数を使用することによって、このことが有益であると考えられます staticAlso 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:

  1. 異なる関数ポインター型のオーバーロードに対して許可する一意の型を持つ。Have a unique type to allow for overloads with different function pointer types.
  2. アセンブリの境界を越えて、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 にバインドできず、ジェネリックインスタンス化をターゲットにできないため、これは機能しません TypeSpecThis 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:

  1. アセンブリのアンロード時の安全性: これには割り当てが必要であるため、 delegate 既に十分なオプションです。Safety in the face of assembly unloading: this requires allocations and hence delegate is already a sufficient option.
  2. アセンブリのアンロードに関して安全ではありません。を使用 delegate* します。No safety in face of assembly unloading: use a delegate*. このをにラップし struct unsafe て、コードの残りの部分でコンテキストの外部で使用できるようにすることができます。This can be wrapped in a struct to allow usage outside an unsafe context in the rest of the code.