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
unsafecontexto.Only valid in anunsafecontext. - Solo se puede llamar a los métodos que contienen un
delegate*parámetro o tipo de valor devuelto desde ununsafecontexto.Methods which contain adelegate*parameter or return type can only be called from anunsafecontext. - No se puede convertir en
object.Cannot be converted toobject. - No se puede usar como argumento genérico.Cannot be used as a generic argument.
- Se puede convertir implícitamente
delegate*envoid*.Can implicitly convertdelegate*tovoid*. - Puede convertir explícitamente de
void*endelegate*.Can explicitly convert fromvoid*todelegate*.
Restricciones:Restrictions:
- Los atributos personalizados no se pueden aplicar a
delegate*o a cualquiera de sus elementos.Custom attributes cannot be applied to adelegate*or any of its elements. - Un
delegate*parámetro no se puede marcar comoparamsAdelegate*parameter cannot be marked asparams - Un
delegate*tipo tiene todas las restricciones de un tipo de puntero normal.Adelegate*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
F0a otro _ tipo funcptrF1, siempre que se cumplan todas las condiciones siguientes:From funcptr_typeF0to another funcptr_typeF1, provided all of the following are true:F0yF1tienen el mismo número de parámetros, y cada parámetroD0ndeF0tiene los mismosrefoutmodificadores, o,inque el parámetro correspondienteD1ndeF1.F0andF1have the same number of parameters, and each parameterD0ninF0has the sameref,out, orinmodifiers as the corresponding parameterD1ninF1.- Para cada parámetro de valor (un parámetro sin
refoutinmodificador, 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 enF0al tipo de parámetro correspondiente enF1.For each value parameter (a parameter with noref,out, orinmodifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type inF0to the corresponding parameter type inF1. - Para cada
refoutparámetro, oin, el tipo de parámetro deF0es el mismo que el tipo de parámetro correspondiente enF1.For eachref,out, orinparameter, the parameter type inF0is the same as the corresponding parameter type inF1. - Si el tipo de valor devuelto es por valor (no
reforef readonly), existe una identidad, una referencia implícita o una conversión de puntero implícita del tipo de valor devuelto deF1al tipo de valor devuelto deF0.If the return type is by value (noreforref readonly), an identity, implicit reference, or implicit pointer conversion exists from the return type ofF1to the return type ofF0. - Si el tipo de valor devuelto es por referencia (
reforef readonly), el tipo de valor devuelto y losrefmodificadores deF1son los mismos que el tipo de valor devuelto y losrefmodificadores deF0.If the return type is by reference (reforref readonly), the return type andrefmodifiers ofF1are the same as the return type andrefmodifiers ofF0. - La Convención de llamada de
F0es la misma que la Convención de llamada deF1.The calling convention ofF0is the same as the calling convention ofF1.
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:
MyFtienen el mismo número de parámetros, y cada parámetro deMtiene los mismosrefoutmodificadores, o,inque el parámetro correspondiente deF.MandFhave the same number of parameters, and each parameter inMhas the sameref,out, orinmodifiers as the corresponding parameter inF.- Para cada parámetro de valor (un parámetro sin
refoutinmodificador, 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 enMal tipo de parámetro correspondiente enF.For each value parameter (a parameter with noref,out, orinmodifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type inMto the corresponding parameter type inF. - Para cada
refoutparámetro, oin, el tipo de parámetro deMes el mismo que el tipo de parámetro correspondiente enF.For eachref,out, orinparameter, the parameter type inMis the same as the corresponding parameter type inF. - Si el tipo de valor devuelto es por valor (no
reforef readonly), existe una identidad, una referencia implícita o una conversión de puntero implícita del tipo de valor devuelto deFal tipo de valor devuelto deM.If the return type is by value (noreforref readonly), an identity, implicit reference, or implicit pointer conversion exists from the return type ofFto the return type ofM. - Si el tipo de valor devuelto es por referencia (
reforef readonly), el tipo de valor devuelto y losrefmodificadores deFson los mismos que el tipo de valor devuelto y losrefmodificadores deM.If the return type is by reference (reforref readonly), the return type andrefmodifiers ofFare the same as the return type andrefmodifiers ofM. - La Convención de llamada de
Mes la misma que la Convención de llamada deF.The calling convention ofMis the same as the calling convention ofF. 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. Mes un método estático.Mis 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
Mque corresponde a una invocación de método del formularioE(A)con las siguientes modificaciones:A single methodMis selected corresponding to a method invocation of the formE(A)with the following modifications:- La lista
Ade argumentos es una lista de expresiones, cada una clasificada como una variable y con el tipo y el modificador (ref,outoin) de la _ _ lista de parámetros funcptr correspondiente deF.The arguments listAis a list of expressions, each classified as a variable and with the type and modifier (ref,out, orin) of the corresponding funcptr_parameter_list ofF. - 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.
- La lista
- 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
Mcon el mismo número de parámetros queFy se considera que la conversión existe.Otherwise, the algorithm produces a single best methodMhaving the same number of parameters asFand the conversion is considered to exist. - El método seleccionado
Mdebe ser compatible (tal y como se definió anteriormente) con el tipo de puntero de funciónF.The selected methodMmust be compatible (as defined above) with the function pointer typeF. 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 typeF.
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 asstatic. - No se
staticpueden usar funciones no locales en&.Non-staticlocal 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:
- El
*operador se puede utilizar para realizar la direccionamiento indirecto del puntero (direccionamiento indirecto del puntero).The*operator may be used to perform pointer indirection (Pointer indirection).- El
->operador se puede utilizar para tener acceso a un miembro de un struct a través de un puntero (acceso a miembros de puntero).The->operator may be used to access a member of a struct through a pointer (Pointer member access).- El
[]operador se puede utilizar para indizar un puntero (acceso a elementos de puntero).The[]operator may be used to index a pointer (Pointer element access).- El
&operador se puede utilizar para obtener la dirección de una variable (el operador Address-of).The&operator may be used to obtain the address of a variable (The address-of operator).- Los
++--operadores y se pueden usar para aumentar y disminuir punteros (incremento y decremento del puntero).The++and--operators may be used to increment and decrement pointers (Pointer increment and decrement).- Los
+-operadores y se pueden usar para realizar operaciones aritméticas de punteros (aritmética de puntero).The+and-operators may be used to perform pointer arithmetic (Pointer arithmetic).- Los
==!=operadores,,<,,><=y=>se pueden usar para comparar punteros (comparación de punteros).The==,!=,<,>,<=, and=>operators may be used to compare pointers (Pointer comparison).- El
stackallocoperador se puede utilizar para asignar memoria de la pila de llamadas (búferes de tamaño fijo).Thestackallocoperator may be used to allocate memory from the call stack (Fixed size buffers).- La
fixedinstrucción se puede utilizar para corregir temporalmente una variable para que se pueda obtener su dirección (la instrucción Fixed).Thefixedstatement may be used to temporarily fix a variable so its address can be obtained (The fixed statement).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:
- El
&operador se puede utilizar para obtener la dirección de los métodos estáticos (permitir la dirección de los métodos de destino)The&operator may be used to obtain the address of static methods (Allow address-of to target methods)- Los
==!=operadores,,<,,><=y=>se pueden usar para comparar punteros (comparación de punteros).The==,!=,<,>,<=, and=>operators may be used to compare pointers (Pointer comparison).
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*Adelegate*is more specific thanvoid*
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
Ees una dirección de un grupo de métodos yTes un tipo de puntero a función, todos los tipos de parámetro deTson tipos de entrada deEtipoT.IfEis an address-of method group andTis a function pointer type then all the parameter types ofTare input types ofEwith typeT.
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
Ees una dirección del grupo de métodos yTes un tipo de puntero a función, el tipo de valor devuelto deTes un tipo de salida deEcon el tipoT.IfEis an address-of method group andTis a function pointer type then the return type ofTis an output type ofEwith typeT.
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
Ees una dirección de grupo de métodos yTes un tipo de puntero de función con tipos de parámetroT1...Tky tipo de valor devueltoTb, y la resolución de sobrecarga deEcon los tiposT1..Tkproduce un único método con el tipo de valor devueltoU, una inferencia de límite inferior se realiza desdeUaTb.IfEis an address-of method group andTis a function pointer type with parameter typesT1...Tkand return typeTb, and overload resolution ofEwith the typesT1..Tkyields a single method with return typeU, then a lower-bound inference is made fromUtoTb.
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:
Ves un tipo de puntero a funcióndelegate*<V2..Vk, V1>yUes un tipo de puntero a funcióndelegate*<U2..Uk, U1>, y la Convención de llamada deVes idéntica aU, y el refness deVies idéntico aUi.Vis a function pointer typedelegate*<V2..Vk, V1>andUis a function pointer typedelegate*<U2..Uk, U1>, and the calling convention ofVis identical toU, and the refness ofViis identical toUi.
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:
Ves un tipo de punterodelegate*<V2..Vk, V1>de función y hay un tipo de punterodelegate*<U2..Uk, U1>de función queUes idéntico adelegate*<U2..Uk, U1>, y la Convención de llamada deVes idéntica aU, y el refness deVies idéntico aUi.Vis a function pointer typedelegate*<V2..Vk, V1>and there is a function pointer typedelegate*<U2..Uk, U1>such thatUis identical todelegate*<U2..Uk, U1>, and the calling convention ofVis identical toU, and the refness ofViis identical toUi.
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
Ues un tipo de puntero de función yUino se sabe que es un tipo de referencia, o siUes un tipo de puntero de función yUino se sabe que es un tipo de puntero de función o un tipo de referencia, se realiza una inferencia exacta .IfUis not a function pointer type andUiis not known to be a reference type, or ifUis a function pointer type andUiis 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
Vesdelegate*<V2..Vk, V1>, la inferencia depende del parámetro i-TH dedelegate*<V2..Vk, V1>:Otherwise, ifVisdelegate*<V2..Vk, V1>then inference depends on the i-th parameter ofdelegate*<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:
Ues un tipo de puntero a funcióndelegate*<U2..Uk, U1>yVes un tipo de puntero a función que es idéntico adelegate*<V2..Vk, V1>, y la Convención de llamada deUes idéntica aV, y el refness deUies idéntico aVi.Uis a function pointer typedelegate*<U2..Uk, U1>andVis a function pointer type which is identical todelegate*<V2..Vk, V1>, and the calling convention ofUis identical toV, and the refness ofUiis identical toVi.
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
Ues un tipo de puntero de función yUino se sabe que es un tipo de referencia, o siUes un tipo de puntero de función yUino se sabe que es un tipo de puntero de función o un tipo de referencia, se realiza una inferencia exacta .IfUis not a function pointer type andUiis not known to be a reference type, or ifUis a function pointer type andUiis 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
Uesdelegate*<U2..Uk, U1>, la inferencia depende del parámetro i-TH dedelegate*<U2..Uk, U1>:Otherwise, ifUisdelegate*<U2..Uk, U1>then inference depends on the i-th parameter ofdelegate*<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 asin. - 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 asref 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
OutAttributecomo modreq a un tipo de valor devuelto.It is an error to applyOutAttributeas a modreq to a return type. - Es un error aplicar
InAttributeyOutAttributecomo modreq a un tipo de parámetro.It is an error to apply bothInAttributeandOutAttributeas 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
identifiera la cadenaCallConvWe prepend theidentifierwith the stringCallConv - Solo veremos los tipos definidos en el
System.Runtime.CompilerServicesespacio de nombres.We look only at types defined in theSystem.Runtime.CompilerServicesnamespace. - Solo veremos los tipos definidos en la biblioteca principal de la aplicación, que es la biblioteca que define
System.Objecty que no tiene dependencias.We look only at types defined in the core library of the application, which is the library that definesSystem.Objectand 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 definesSystem.Object. - El tipo se define en el
System.Runtime.CompilerServicesespacio de nombres.The type is defined in theSystem.Runtime.CompilerServicesnamespace. - El tipo comienza con el prefijo
CallConv.The type starts with the prefixCallConv. - 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 anunmanaged_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 tounmanaged. - 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.CallConvsque no cumpla los requisitos de la Convención de llamadamodopts en los metadatos.It is an error to specify any types forUnmanagedCallersOnly.CallConvsthat do not meet the requirements for calling conventionmodopts 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,
CallKindse trata comounmanaged ext, sin ninguna Convenciónmodoptde llamada s al principio del tipo de puntero de función.If no types are specified, theCallKindis treated asunmanaged ext, with no calling conventionmodopts at the start of the function pointer type. - Si se especifica un tipo y ese tipo se denomina
CallConvCdecl,CallConvThiscall,CallConvStdcalloCallConvFastcall,CallKindse trata comounmanaged cdecl,unmanaged thiscall,unmanaged stdcallounmanaged fastcallrespectivamente, sin ninguna Convenciónmodoptde llamada s al principio del tipo de puntero de función.If there is one type specified, and that type is namedCallConvCdecl,CallConvThiscall,CallConvStdcall, orCallConvFastcall, theCallKindis treated asunmanaged cdecl,unmanaged thiscall,unmanaged stdcall, orunmanaged fastcall, respectively, with no calling conventionmodopts 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,
CallKindse trata comounmanaged ext, con la Unión de los tipos especificados comomodopts 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, theCallKindis treated asunmanaged ext, with the union of the types specified treated asmodopts 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:
- 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.
- 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:
- 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 hencedelegateis already a sufficient option. - No hay seguridad en la descarga de ensamblados: use
delegate*.No safety in face of assembly unloading: use adelegate*. Se puede ajustar en unstructpara permitir el uso fueraunsafede un contexto en el resto del código.This can be wrapped in astructto allow usage outside anunsafecontext in the rest of the code.