Punteros a funcionesFunction Pointers

ResumenSummary

Esta propuesta proporciona construcciones de lenguaje que exponen códigos de tiempo de IL a los que actualmente no se puede acceder de forma eficaz o, en todo, en C# actualmente: ldftn y 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. Estos códigos de acceso de IL pueden ser importantes en el código de alto rendimiento y los desarrolladores necesitan un método eficaz para acceder a ellos.These IL opcodes can be important in high performance code and developers need an efficient way to access them.

MotivaciónMotivation

Las motivaciones y el fondo de esta característica se describen en el siguiente problema (como una posible implementación de la característica):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

Se trata de una propuesta de diseño alternativa a las funciones intrínsecas del compiladorThis is an alternate design proposal to compiler intrinsics

Diseño detalladoDetailed Design

Punteros de funciónFunction pointers

El lenguaje permitirá la declaración de punteros a función mediante la delegate* Sintaxis.The language will allow for the declaration of function pointers using the delegate* syntax. La sintaxis completa se describe en detalle en la sección siguiente, pero está diseñada para ser similar a la sintaxis usada por Func y las Action declaraciones de tipos.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);
    }
}

Estos tipos se representan mediante el tipo de puntero de función tal y como se describe en ECMA-335.These types are represented using the function pointer type as outlined in ECMA-335. Esto significa que la invocación de utilizará delegate* calli donde la invocación de a usará delegate callvirt en el Invoke método.This means invocation of a delegate* will use calli where invocation of a delegate will use callvirt on the Invoke method. Sintácticamente, a pesar de que la invocación es idéntica para ambas construcciones.Syntactically though invocation is identical for both constructs.

La definición de ECMA-335 de punteros de método incluye la Convención de llamada como parte de la signatura de tipo (sección 7,1).The ECMA-335 definition of method pointers includes the calling convention as part of the type signature (section 7.1). La Convención de llamada predeterminada será managed .The default calling convention will be managed. Las convenciones de llamada no administradas pueden especificarse colocando una unmanaged palabra clave tras la delegate* sintaxis, que utilizará el valor predeterminado de la plataforma en tiempo de ejecución.Unmanaged calling conventions can by specified by putting an unmanaged keyword afer the delegate* syntax, which will use the runtime platform default. Las convenciones no administradas específicas se pueden especificar entre corchetes para la unmanaged palabra clave especificando cualquier tipo que empiece por CallConv en el System.Runtime.CompilerServices espacio de nombres, sin salir del CallConv prefijo.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. Estos tipos deben provienen de la biblioteca básica del programa y el conjunto de combinaciones válidas depende de la plataforma.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>;

Las conversiones entre delegate* tipos se realizan en función de su firma, incluida la Convención de llamada.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
    }
}

Un delegate* tipo es un tipo de puntero, lo que significa que tiene todas las capacidades y restricciones de un tipo de puntero estándar:A delegate* type is a pointer type which means it has all of the capabilities and restrictions of a standard pointer type:

  • Solo es válido en un unsafe contexto.Only valid in an unsafe context.
  • Solo se puede llamar a los métodos que contienen un delegate* parámetro o tipo de valor devuelto desde un unsafe contexto.Methods which contain a delegate* parameter or return type can only be called from an unsafe context.
  • No se puede convertir en object .Cannot be converted to object.
  • No se puede usar como argumento genérico.Cannot be used as a generic argument.
  • Se puede convertir implícitamente delegate* en void* .Can implicitly convert delegate* to void*.
  • Puede convertir explícitamente de void* en delegate* .Can explicitly convert from void* to delegate*.

Restricciones:Restrictions:

  • Los atributos personalizados no se pueden aplicar a delegate* o a cualquiera de sus elementos.Custom attributes cannot be applied to a delegate* or any of its elements.
  • Un delegate* parámetro no se puede marcar como paramsA delegate* parameter cannot be marked as params
  • Un delegate* tipo tiene todas las restricciones de un tipo de puntero normal.A delegate* type has all of the restrictions of a normal pointer type.
  • La aritmética de puntero no se puede realizar directamente en los tipos de puntero de función.Pointer arithmetic cannot be performed directly on function pointer types.

Sintaxis de puntero de funciónFunction pointer syntax

La sintaxis de puntero de función completa se representa mediante la siguiente gramática: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'
    ;

Si no calling_convention_specifier se proporciona, el valor predeterminado es managed .If no calling_convention_specifier is provided, the default is managed. La codificación exacta de metadatos de calling_convention_specifier y las que identifier son válidas en unmanaged_calling_convention se trata en representación de metadatos de convenciones de llamada.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>>;

Conversiones de puntero de funciónFunction pointer conversions

En un contexto no seguro, el conjunto de conversiones implícitas disponibles (conversiones implícitas) se extiende para incluir las siguientes conversiones de puntero implícitas:In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:

  • Conversiones existentesExisting conversions
  • De _ tipo funcptr F0 a otro _ tipo funcptr F1 , siempre que se cumplan todas las condiciones siguientes:From funcptr_type F0 to another funcptr_type F1, provided all of the following are true:
    • F0 y F1 tienen el mismo número de parámetros, y cada parámetro D0n de F0 tiene los mismos ref out modificadores, o, in que el parámetro correspondiente D1n de 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.
    • Para cada parámetro de valor (un parámetro sin ref out in modificador, o), existe una conversión de identidad, una conversión de referencia implícita o una conversión de puntero implícita desde el tipo de parámetro en F0 al tipo de parámetro correspondiente en 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.
    • Para cada ref out parámetro, o in , el tipo de parámetro de F0 es el mismo que el tipo de parámetro correspondiente en F1 .For each ref, out, or in parameter, the parameter type in F0 is the same as the corresponding parameter type in F1.
    • Si el tipo de valor devuelto es por valor (no ref o ref readonly ), existe una identidad, una referencia implícita o una conversión de puntero implícita del tipo de valor devuelto de F1 al tipo de valor devuelto de 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.
    • Si el tipo de valor devuelto es por referencia ( ref o ref readonly ), el tipo de valor devuelto y los ref modificadores de F1 son los mismos que el tipo de valor devuelto y los ref modificadores de 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.
    • La Convención de llamada de F0 es la misma que la Convención de llamada de F1 .The calling convention of F0 is the same as the calling convention of F1.

Permitir la dirección de los métodos de destinoAllow address-of to target methods

Ahora se permitirá a los grupos de métodos como argumentos para una expresión Address-of.Method groups will now be allowed as arguments to an address-of expression. El tipo de dicha expresión será un delegate* que tenga la firma equivalente del método de destino y una Convención de llamada administrada: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;
   }
}

En un contexto no seguro, un método M es compatible con un tipo de puntero F de función si se cumplen todas las condiciones siguientes:In an unsafe context, a method M is compatible with a function pointer type F if all of the following are true:

  • M y F tienen el mismo número de parámetros, y cada parámetro de M tiene los mismos ref out modificadores, o, in que el parámetro correspondiente de 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.
  • Para cada parámetro de valor (un parámetro sin ref out in modificador, o), existe una conversión de identidad, una conversión de referencia implícita o una conversión de puntero implícita desde el tipo de parámetro en M al tipo de parámetro correspondiente en 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.
  • Para cada ref out parámetro, o in , el tipo de parámetro de M es el mismo que el tipo de parámetro correspondiente en F .For each ref, out, or in parameter, the parameter type in M is the same as the corresponding parameter type in F.
  • Si el tipo de valor devuelto es por valor (no ref o ref readonly ), existe una identidad, una referencia implícita o una conversión de puntero implícita del tipo de valor devuelto de F al tipo de valor devuelto de 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.
  • Si el tipo de valor devuelto es por referencia ( ref o ref readonly ), el tipo de valor devuelto y los ref modificadores de F son los mismos que el tipo de valor devuelto y los ref modificadores de 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.
  • La Convención de llamada de M es la misma que la Convención de llamada de F .The calling convention of M is the same as the calling convention of F. Esto incluye el bit de la Convención de llamada, así como cualquier marcador de Convención de llamada especificado en el identificador no administrado.This includes both the calling convention bit, as well as any calling convention flags specified in the unmanaged identifier.
  • M es un método estático.M is a static method.

En un contexto no seguro, existe una conversión implícita de una dirección de una expresión cuyo destino es un grupo de métodos E a un tipo de puntero de función compatible F si E contiene al menos un método que se puede aplicar en su forma normal a una lista de argumentos construida mediante el uso de los tipos de parámetros y modificadores de F , como se describe a continuación.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.

  • Se selecciona un solo método M que corresponde a una invocación de método del formulario E(A) con las siguientes modificaciones:A single method M is selected corresponding to a method invocation of the form E(A) with the following modifications:
    • La lista A de argumentos es una lista de expresiones, cada una clasificada como una variable y con el tipo y el modificador ( ref , out o in ) de la _ _ lista de parámetros funcptr correspondiente de 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.
    • Los métodos candidatos son solo aquellos que se aplican en su forma normal, no los que se aplican en su forma expandida.The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form.
    • Los métodos candidatos son solo los métodos que son estáticos.The candidate methods are only those methods that are static.
  • Si el algoritmo de la resolución de sobrecarga produce un error, se produce un error en tiempo de compilación.If the algorithm of overload resolution produces an error, then a compile-time error occurs. De lo contrario, el algoritmo genera un único método mejor M con el mismo número de parámetros que F y se considera que la conversión existe.Otherwise, the algorithm produces a single best method M having the same number of parameters as F and the conversion is considered to exist.
  • El método seleccionado M debe ser compatible (tal y como se definió anteriormente) con el tipo de puntero de función F .The selected method M must be compatible (as defined above) with the function pointer type F. De lo contrario, se produce un error en tiempo de compilación.Otherwise, a compile-time error occurs.
  • El resultado de la conversión es un puntero de función de tipo F .The result of the conversion is a function pointer of type F.

Esto significa que los desarrolladores pueden depender de reglas de resolución de sobrecarga para trabajar junto con el operador Address-of: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;
    }

El operador Address-of se implementará mediante la ldftn instrucción.The address-of operator will be implemented using the ldftn instruction.

Restricciones de esta característica:Restrictions of this feature:

  • Solo se aplica a los métodos marcados como static .Only applies to methods marked as static.
  • No se static pueden usar funciones no locales en & .Non-static local functions cannot be used in &. El lenguaje no especifica deliberadamente los detalles de implementación de estos métodos.The implementation details of these methods are deliberately not specified by the language. Esto incluye si son estáticos frente a instancia o exactamente con qué firma se emiten.This includes whether they are static vs. instance or exactly what signature they are emitted with.

Operadores en tipos de puntero de funciónOperators on Function Pointer Types

La sección de código no seguro en operadores se modifica como tal:The section in unsafe code on operators is modified as such:

En un contexto no seguro, hay varias construcciones disponibles para funcionar en todos los _pointer _ type_s que no _funcptr _ type_s:In an unsafe context, several constructs are available for operating on all _pointer_type_s that are not _funcptr_type_s:

En un contexto no seguro, hay varias construcciones disponibles para funcionar en todos los _funcptr _ type_s:In an unsafe context, several constructs are available for operating on all _funcptr_type_s:

Además, se modifican todas las secciones de Pointers in expressions para prohibir tipos de puntero de función, excepto Pointer comparison y 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.

Mejor miembro de funciónBetter function member

Se cambiará la especificación de miembro de función mejor para incluir la siguiente línea:The better function member specification will be changed to include the following line:

delegate*Es más específico quevoid*A delegate* is more specific than void*

Esto significa que es posible sobrecargar en void* y en delegate* y seguir implementar usar el operador Address-of.This means that it is possible to overload on void* and a delegate* and still sensibly use the address-of operator.

Inferencia de tiposType Inference

En código no seguro, se realizan los siguientes cambios en los algoritmos de inferencia de tipos:In unsafe code, the following changes are made to the type inference algorithms:

Tipos de entradaInput types

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

Se agrega lo siguiente:The following is added:

Si E es una dirección de un grupo de métodos y T es un tipo de puntero a función, todos los tipos de parámetro de T son tipos de entrada de E tipo 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.

Tipos de salidaOutput types

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

Se agrega lo siguiente:The following is added:

Si E es una dirección del grupo de métodos y T es un tipo de puntero a función, el tipo de valor devuelto de T es un tipo de salida de E con el tipo 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.

Inferencias de tipos de salidaOutput type inferences

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

La siguiente viñeta se agrega entre las viñetas 2 y 3:The following bullet is added between bullets 2 and 3:

  • Si E es una dirección de grupo de métodos y T es un tipo de puntero de función con tipos de parámetro T1...Tk y tipo de valor devuelto Tb , y la resolución de sobrecarga de E con los tipos T1..Tk produce un único método con el tipo de valor devuelto U , una inferencia de límite inferior se realiza desde U a 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.

Inferencias exactasExact inferences

https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#exact-inferences

La siguiente subviñeta se agrega como un caso a la viñeta 2:The following sub-bullet is added as a case to bullet 2:

  • V es un tipo de puntero a función delegate*<V2..Vk, V1> y U es un tipo de puntero a función delegate*<U2..Uk, U1> , y la Convención de llamada de V es idéntica a U , y el refness de Vi es idéntico a 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.

Inferencias con límite inferiorLower-bound inferences

https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#lower-bound-inferences

El siguiente caso se agrega a la viñeta 3:The following case is added to bullet 3:

  • V es un tipo de puntero delegate*<V2..Vk, V1> de función y hay un tipo de puntero delegate*<U2..Uk, U1> de función que U es idéntico a delegate*<U2..Uk, U1> , y la Convención de llamada de V es idéntica a U , y el refness de Vi es idéntico a 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.

La primera viñeta de inferencia de Ui a Vi se modifica a:The first bullet of inference from Ui to Vi is modified to:

  • Si no U es un tipo de puntero de función y Ui no se sabe que es un tipo de referencia, o si U es un tipo de puntero de función y Ui no se sabe que es un tipo de puntero de función o un tipo de referencia, se realiza una inferencia exacta .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

Después, se agrega después de la tercera viñeta de inferencia de Ui a Vi :Then, added after the 3rd bullet of inference from Ui to Vi:

  • De lo contrario, si V es delegate*<V2..Vk, V1> , la inferencia depende del parámetro i-TH de 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>:
    • Si v1:If V1:
      • Si la devolución es por valor, se realiza una inferencia de límite inferior .If the return is by value, then a lower-bound inference is made.
      • Si la devolución es por referencia, se realiza una inferencia exacta .If the return is by reference, then an exact inference is made.
    • Si V2.. VKIf V2..Vk:
      • Si el parámetro es por valor, se realiza una inferencia enlazada en el límite superior .If the parameter is by value, then an upper-bound inference is made.
      • Si el parámetro es por referencia, se realiza una inferencia exacta .If the parameter is by reference, then an exact inference is made.

Inferencias de límite superiorUpper-bound inferences

https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#upper-bound-inferences

En la viñeta 2 se agrega el siguiente caso:The following case is added to bullet 2:

  • U es un tipo de puntero a función delegate*<U2..Uk, U1> y V es un tipo de puntero a función que es idéntico a delegate*<V2..Vk, V1> , y la Convención de llamada de U es idéntica a V , y el refness de Ui es idéntico a 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.

La primera viñeta de inferencia de Ui a Vi se modifica a:The first bullet of inference from Ui to Vi is modified to:

  • Si no U es un tipo de puntero de función y Ui no se sabe que es un tipo de referencia, o si U es un tipo de puntero de función y Ui no se sabe que es un tipo de puntero de función o un tipo de referencia, se realiza una inferencia exacta .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

Después, se agrega después de la tercera viñeta de inferencia de Ui a Vi :Then added after the 3rd bullet of inference from Ui to Vi:

  • De lo contrario, si U es delegate*<U2..Uk, U1> , la inferencia depende del parámetro i-TH de 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>:
    • Si U1:If U1:
      • Si la devolución es por valor, se realiza una inferencia enlazada en el límite superior .If the return is by value, then an upper-bound inference is made.
      • Si la devolución es por referencia, se realiza una inferencia exacta .If the return is by reference, then an exact inference is made.
    • Si U2.. Reino UnidoIf U2..Uk:
      • Si el parámetro es por valor, se realiza una inferencia de límite inferior .If the parameter is by value, then a lower-bound inference is made.
      • Si el parámetro es por referencia, se realiza una inferencia exacta .If the parameter is by reference, then an exact inference is made.

Representación de metadatos de in out los parámetros, y ref readonly y los tipos de valor devueltosMetadata representation of in, out, and ref readonly parameters and return types

Las signaturas de puntero de función no tienen ninguna ubicación de marcadores de parámetros, por lo que debemos codificar si los parámetros y el tipo de valor devuelto son in , out o mediante 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

Volvemos a usar, que se System.Runtime.InteropServices.InAttribute aplica como modreq al especificador de referencia en un parámetro o tipo de valor devuelto, para indicar lo siguiente:We reuse System.Runtime.InteropServices.InAttribute, applied as a modreq to the ref specifier on a parameter or return type, to mean the following:

  • Si se aplica a un especificador de referencia de parámetro, este parámetro se trata como in .If applied to a parameter ref specifier, this parameter is treated as in.
  • Si se aplica al especificador de referencia de tipo de valor devuelto, el tipo de valor devuelto se trata como ref readonly .If applied to the return type ref specifier, the return type is treated as ref readonly.

out

Usamos System.Runtime.InteropServices.OutAttribute , que se aplica como modreq al especificador de referencia en un tipo de parámetro, para indicar que el parámetro es un out parámetro.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.

ErrorsErrors

  • Es un error que se aplica OutAttribute como modreq a un tipo de valor devuelto.It is an error to apply OutAttribute as a modreq to a return type.
  • Es un error aplicar InAttribute y OutAttribute como modreq a un tipo de parámetro.It is an error to apply both InAttribute and OutAttribute as a modreq to a parameter type.
  • Si se especifican a través de modopt, se omiten.If either are specified via modopt, they are ignored.

Representación de metadatos de convenciones de llamadaMetadata Representation of Calling Conventions

Las convenciones de llamada se codifican en una firma de método en los metadatos mediante una combinación de la CallKind marca de la firma y cero o más modopt en el inicio de la firma.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 actualmente declara los siguientes elementos en la CallKind marca:ECMA-335 currently declares the following elements in the CallKind flag:

CallKind
   : default
   | unmanaged cdecl
   | unmanaged fastcall
   | unmanaged thiscall
   | unmanaged stdcall
   | varargs
   ;

De estos, los punteros de función en C# serán compatibles con todos los demás varargs .Of these, function pointers in C# will support all but varargs.

Además, el tiempo de ejecución (y, finalmente, 335) se actualizará para incluir una nueva CallKind en las nuevas plataformas.In addition, the runtime (and eventually 335) will be updated to include a new CallKind on new platforms. No tiene un nombre formal actualmente, pero este documento se usará unmanaged ext como marcador de posición para el nuevo formato de Convención de llamada extensible.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. Sin modopt s, unmanaged ext es la Convención de llamada predeterminada de la plataforma, unmanaged sin los corchetes.With no modopts, unmanaged ext is the platform default calling convention, unmanaged without the square brackets.

Asignar calling_convention_specifier a un CallKindMapping the calling_convention_specifier to a CallKind

Un calling_convention_specifier que se omite, o se especifica como managed , se asigna a default CallKind .A calling_convention_specifier that is omitted, or specified as managed, maps to the default CallKind. Este es el valor predeterminado CallKind de cualquier método no atribuido con UnmanagedCallersOnly .This is default CallKind of any method not attributed with UnmanagedCallersOnly.

C# reconoce 4 identificadores especiales que se asignan a s no administrados específicos CallKind de ECMA 335.C# recognizes 4 special identifiers that map to specific existing unmanaged CallKinds from ECMA 335. Para que se produzca esta asignación, estos identificadores deben especificarse por sí solos, sin otros identificadores, y este requisito se codifica en las especificaciones de unmanaged_calling_convention s.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. Estos identificadores son Cdecl , Thiscall , Stdcall y Fastcall , que corresponden a unmanaged cdecl , unmanaged thiscall , unmanaged stdcall y unmanaged fastcall , respectivamente.These identifiers are Cdecl, Thiscall, Stdcall, and Fastcall, which correspond to unmanaged cdecl, unmanaged thiscall, unmanaged stdcall, and unmanaged fastcall, respectively. Si se especifica más de una identifer o la única identifier no es de los identificadores reconocidos especialmente, se realiza una búsqueda de nombre especial en el identificador con las siguientes reglas: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:

  • Anteponemos identifier a la cadena CallConvWe prepend the identifier with the string CallConv
  • Solo veremos los tipos definidos en el System.Runtime.CompilerServices espacio de nombres.We look only at types defined in the System.Runtime.CompilerServices namespace.
  • Solo veremos los tipos definidos en la biblioteca principal de la aplicación, que es la biblioteca que define System.Object y que no tiene dependencias.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.
  • Solo veremos los tipos públicos.We look only at public types.

Si la búsqueda se realiza correctamente en todos los identifier s especificados en unmanaged_calling_convention , codificamos el CallKind como unmanaged ext y codificamos cada uno de los tipos resueltos en el conjunto de modopt s al principio de la firma del puntero de función.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. Como nota, estas reglas implican que los usuarios no pueden prefijar identifier con CallConv , ya que esto provocará una búsqueda CallConvCallConvVectorCall .As a note, these rules mean that users cannot prefix these identifiers with CallConv, as that will result in looking up CallConvCallConvVectorCall.

Al interpretar los metadatos, primero veremos el CallKind .When interpreting metadata, we first look at the CallKind. Si es distinto de, se unmanaged ext omiten todos los modopt s en el tipo de valor devuelto con el fin de determinar la Convención de llamada y usar solo el 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. Si CallKind es, veremos unmanaged ext el modopts al principio del tipo de puntero de función, tomando la Unión de todos los tipos que cumplen los siguientes requisitos: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:

  • Se define en la biblioteca principal, que es la biblioteca que no hace referencia a ninguna otra biblioteca y define System.Object .The is defined in the core library, which is the library that references no other libraries and defines System.Object.
  • El tipo se define en el System.Runtime.CompilerServices espacio de nombres.The type is defined in the System.Runtime.CompilerServices namespace.
  • El tipo comienza con el prefijo CallConv .The type starts with the prefix CallConv.
  • El tipo es público.The type is public.

Estos representan los tipos que se deben encontrar al realizar búsquedas en las identifier s de un unmanaged_calling_convention al definir un tipo de puntero de función en el origen.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.

Es un error intentar usar un puntero de función con un CallKind de unmanaged ext si el tiempo de ejecución de destino no admite la característica.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. Esto se determinará buscando la presencia de la System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind constante.This will be determined by looking for the presence of the System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind constant. Si esta constante está presente, el tiempo de ejecución se considera compatible con la característica.If this constant is present, the runtime is considered to support the feature.

System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute

System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute es un atributo utilizado por el CLR para indicar que se debe llamar a un método con una Convención de llamada concreta.System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute is an attribute used by the CLR to indicate that a method should be called with a specific calling convention. Por este motivo, se presenta la siguiente compatibilidad para trabajar con el atributo:Because of this, we introduce the following support for working with the attribute:

  • Es un error llamar directamente a un método anotado con este atributo desde C#.It is an error to directly call a method annotated with this attribute from C#. Los usuarios deben obtener un puntero de función al método y, a continuación, invocar ese puntero.Users must obtain a function pointer to the method and then invoke that pointer.
  • Es un error aplicar el atributo a cualquier elemento que no sea un método estático ordinario o una función local estática normal.It is an error to apply the attribute to anything other than an ordinary static method or ordinary static local function. El compilador de C# marcará los métodos no ordinarios no estáticos o estáticos importados de los metadatos con este atributo como no admitidos por el lenguaje.The C# compiler will mark any non-static or static non-ordinary methods imported from metadata with this attribute as unsupported by the language.
  • Es un error que un método marcado con el atributo tenga un parámetro o un tipo de valor devuelto que no sea 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.
  • Es un error que un método marcado con el atributo tenga parámetros de tipo, incluso si esos parámetros de tipo están restringidos a 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.
  • Es un error que un método de un tipo genérico se marque con el atributo.It is an error for a method in a generic type to be marked with the attribute.
  • Es un error convertir un método marcado con el atributo en un tipo de delegado.It is an error to convert a method marked with the attribute to a delegate type.
  • Es un error especificar cualquier tipo para UnmanagedCallersOnly.CallConvs que no cumpla los requisitos de la Convención de llamada modopt s en los metadatos.It is an error to specify any types for UnmanagedCallersOnly.CallConvs that do not meet the requirements for calling convention modopts in metadata.

Al determinar la Convención de llamada de un método marcado con un UnmanagedCallersOnly atributo válido, el compilador realiza las siguientes comprobaciones en los tipos especificados en la CallConvs propiedad para determinar el efectivo CallKind y el modopt que se debe utilizar para determinar la Convención de llamada: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:

  • Si no se especifica ningún tipo, CallKind se trata como unmanaged ext , sin ninguna Convención modopt de llamada s al principio del tipo de puntero de función.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.
  • Si se especifica un tipo y ese tipo se denomina CallConvCdecl , CallConvThiscall , CallConvStdcall o CallConvFastcall , CallKind se trata como unmanaged cdecl , unmanaged thiscall , unmanaged stdcall o unmanaged fastcall respectivamente, sin ninguna Convención modopt de llamada s al principio del tipo de puntero de función.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.
  • Si se especifican varios tipos o no se llama a uno de los tipos de salida especialmente mencionados, CallKind se trata como unmanaged ext , con la Unión de los tipos especificados como modopt s al principio del tipo de puntero de función.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.

A continuación, el compilador examina esta CallKind colección efectiva y modopt usa reglas de metadatos normales para determinar la Convención de llamada final del tipo de puntero de función.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.

Preguntas abiertasOpen Questions

Detección de compatibilidad en tiempo de ejecución para unmanaged extDetecting runtime support for unmanaged ext

https://github.com/dotnet/runtime/issues/38135 realiza un seguimiento de la adición de esta marca.https://github.com/dotnet/runtime/issues/38135 tracks adding this flag. En función de los comentarios de la revisión, usaremos la propiedad especificada en el problema o la presencia de UnmanagedCallersOnlyAttribute como la marca que determina si admite los tiempos de ejecución 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.

ConsideracionesConsiderations

Permitir métodos de instanciaAllow instance methods

La propuesta podría ampliarse para admitir métodos de instancia aprovechando la Convención de llamada de la EXPLICITTHIS CLI (denominada instance en el código de C#).The proposal could be extended to support instance methods by taking advantage of the EXPLICITTHIS CLI calling convention (named instance in C# code). Esta forma de punteros de función de la CLI coloca el this parámetro como primer parámetro explícito de la sintaxis de puntero de función.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);
    }
}

Este es un sonido, pero agrega cierta complicación a la propuesta.This is sound but adds some complication to the proposal. En particular, dado que los punteros de función difieren en la Convención de llamada instance y managed serían incompatibles aunque ambos casos se usen para invocar métodos administrados con la misma firma de 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. Además, en todos los casos en los que sería útil tener una solución sencilla: usar una static función local.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);
    }
}

No requerir Unsafe en la declaraciónDon't require unsafe at declaration

En lugar de requerir unsafe en cada uso de delegate* , solo es necesario en el punto en el que se convierte un grupo de métodos en 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*. Aquí es donde entran en juego los problemas de seguridad principales (sabiendo que no se puede descargar el ensamblado contenedor mientras el valor está activo).This is where the core safety issues come into play (knowing that the containing assembly cannot be unloaded while the value is alive). Requerir unsafe en las otras ubicaciones puede considerarse excesivo.Requiring unsafe on the other locations can be seen as excessive.

Así es como se diseñó originalmente el diseño.This is how the design was originally intended. Sin embargo, las reglas de lenguaje resultaron muy poco complicadas.But the resulting language rules felt very awkward. No es posible ocultar el hecho de que se trata de un valor de puntero y de que se ha mantenido el vistazo incluso sin la unsafe palabra clave.It's impossible to hide the fact that this is a pointer value and it kept peeking through even without the unsafe keyword. Por ejemplo, no se permite la conversión a object , no puede ser miembro de un class , etc. El diseño de C# es necesario unsafe para todos los usos de los punteros y, por lo tanto, este diseño lo sigue.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.

Los desarrolladores seguirán pudiendo presentar un contenedor seguro sobre delegate* los valores de la misma manera que en los tipos de puntero normales hoy en día.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. Considere:Consider:

unsafe struct Action {
    delegate*<void> _ptr;

    Action(delegate*<void> ptr) => _ptr = ptr;
    public void Invoke() => _ptr();
}

Usar delegadosUsing delegates

En lugar de usar un nuevo elemento de sintaxis, delegate* , simplemente use delegate los tipos existentes con un * tipo siguiente: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;

La administración de la Convención de llamada se puede realizar anotando los delegate tipos con un atributo que especifica un CallingConvention valor.Handling calling convention can be done by annotating the delegate types with an attribute that specifies a CallingConvention value. La falta de un atributo significaría la Convención de llamada administrada.The lack of an attribute would signify the managed calling convention.

La codificación en IL es problemática.Encoding this in IL is problematic. El valor subyacente debe representarse como un puntero, pero también debe:The underlying value needs to be represented as a pointer yet it also must:

  1. Tener un tipo único para permitir sobrecargas con distintos tipos de puntero de función.Have a unique type to allow for overloads with different function pointer types.
  2. Ser equivalente a fines de OHI en los límites de los ensamblados.Be equivalent for OHI purposes across assembly boundaries.

El último punto es especialmente problemático.The last point is particularly problematic. Esto significa que cada ensamblado que utiliza Func<int>* debe codificar un tipo equivalente en los metadatos, aunque Func<int>* se haya definido en un ensamblado sin controlar.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. Además, cualquier otro tipo que se defina con el nombre System.Func<T> en un ensamblado que no sea mscorlib debe ser diferente de la versión definida en 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.

Una opción que se exploró estaba emitiendo un puntero como mod_req(Func<int>) void* .One option that was explored was emitting such a pointer as mod_req(Func<int>) void*. Esto no funciona sin embargo, ya que mod_req no se puede enlazar a y, por TypeSpec tanto, no puede tener como destino instancias genéricas.This doesn't work though as a mod_req cannot bind to a TypeSpec and hence cannot target generic instantiations.

Punteros de función con nombreNamed function pointers

La sintaxis de puntero de función puede ser complicada, especialmente en casos complejos como punteros de función anidados.The function pointer syntax can be cumbersome, particularly in complex cases like nested function pointers. En lugar de hacer que los desarrolladores escriban la firma cada vez que el lenguaje pueda permitir declaraciones con nombre de punteros de función tal y como se hace con 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();
    }
}

Parte del problema aquí es que el primitivo subyacente de la CLI no tiene nombres, por lo tanto, esto sería meramente una invención de C# y requiere un poco de trabajo de metadatos para habilitarlo.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. Es decir, factible, pero es una tarea importante sobre el trabajo.That is doable but is a significant about of work. Esencialmente, requiere que C# tenga un complemento a la tabla de tipo Def únicamente para estos nombres.It essentially requires C# to have a companion to the type def table purely for these names.

Además, cuando se examinan los argumentos de los punteros de función con nombre, encontramos que podrían aplicarse igualmente bien a otros escenarios.Also when the arguments for named function pointers were examined we found they could apply equally well to a number of other scenarios. Por ejemplo, sería tan práctico declarar tuplas con nombre para reducir la necesidad de escribir la firma completa en todos los casos.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);
    }
}

Después de la explicación, decidimos no permitir la declaración con nombre de delegate* tipos.After discussion we decided to not allow named declaration of delegate* types. Si encontramos una necesidad importante para esto en función de los comentarios de uso de los clientes, investigaremos una solución de nomenclatura que funciona para punteros de función, tuplas, genéricos, etc. Esto es probable que sea similar en forma de otras sugerencias, como typedef la compatibilidad completa en el lenguaje.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.

Consideraciones futurasFuture Considerations

delegados estáticosstatic delegates

Esto hace referencia a la propuesta para permitir la declaración de delegate tipos que solo pueden hacer referencia a static miembros.This refers to the proposal to allow for the declaration of delegate types which can only refer to static members. La ventaja es que estas delegate instancias se pueden asignar de forma gratuita y mejor en escenarios sensibles al rendimiento.The advantage being that such delegate instances can be allocation free and better in performance sensitive scenarios.

Si se implementa la característica de puntero de función static delegate , es probable que se cierre la propuesta. La ventaja propuesta de esa característica es la naturaleza gratuita de la asignación.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. Sin embargo, las investigaciones recientes han detectado que no es posible lograr debido a la descarga de ensamblados.However recent investigations have found that is not possible to achieve due to assembly unloading. Debe haber un identificador seguro desde el static delegate al método al que hace referencia para evitar que el ensamblado se descargue fuera de él.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.

Para mantener cada static delegate instancia sería necesario asignar un nuevo identificador que ejecute el contador en los objetivos de la propuesta.To maintain every static delegate instance would be required to allocate a new handle which runs counter to the goals of the proposal. Había algunos diseños en los que la asignación podía amortizarse en una única asignación por sitio de llamada, pero esto era un poco complejo y no parecía merecer la pena para el compromiso.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.

Esto significa que los desarrolladores tienen que decidir esencialmente entre las siguientes ventajas e inconvenientes:That means developers essentially have to decide between the following trade offs:

  1. Seguridad ante la descarga de ensamblados: Esto requiere asignaciones y delegate , por lo tanto, ya es una opción suficiente.Safety in the face of assembly unloading: this requires allocations and hence delegate is already a sufficient option.
  2. No hay seguridad en la descarga de ensamblados: use delegate* .No safety in face of assembly unloading: use a delegate*. Se puede ajustar en un struct para permitir el uso fuera unsafe de un contexto en el resto del código.This can be wrapped in a struct to allow usage outside an unsafe context in the rest of the code.