Ponteiros de funçãoFunction Pointers

ResumoSummary

Esta proposta fornece construções de linguagem que expõem opcodes IL que atualmente não podem ser acessados com eficiência ou, em C#, hoje: ldftn e 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. Esses opcodes de IL podem ser importantes em código de alto desempenho e os desenvolvedores precisam de uma maneira eficiente para acessá-los.These IL opcodes can be important in high performance code and developers need an efficient way to access them.

MotivaçãoMotivation

As motivações e o plano de fundo desse recurso são descritos no seguinte problema (como é uma implementação potencial do recurso):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

Esta é uma proposta de design alternativa para intrínsecos do compiladorThis is an alternate design proposal to compiler intrinsics

Design detalhadoDetailed Design

Ponteiros de funçãoFunction pointers

O idioma permitirá a declaração de ponteiros de função usando a delegate* sintaxe.The language will allow for the declaration of function pointers using the delegate* syntax. A sintaxe completa é descrita detalhadamente na próxima seção, mas ela se destina a se assemelhar à sintaxe usada Func pelas Action declarações de tipo e.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);
    }
}

Esses tipos são representados usando o tipo de ponteiro de função, conforme descrito em ECMA-335.These types are represented using the function pointer type as outlined in ECMA-335. Isso significa que a invocação de um delegate* usará calli onde a invocação de um delegate será usada callvirt no Invoke método.This means invocation of a delegate* will use calli where invocation of a delegate will use callvirt on the Invoke method. Sintaticamente, embora a invocação seja idêntica para ambas as construções.Syntactically though invocation is identical for both constructs.

A definição ECMA-335 dos ponteiros de método inclui a Convenção de chamada como parte da assinatura de tipo (seção 7,1).The ECMA-335 definition of method pointers includes the calling convention as part of the type signature (section 7.1). A Convenção de chamada padrão será managed .The default calling convention will be managed. As convenções de chamada não gerenciadas podem ser especificadas por meio da inserção de uma unmanaged palavra-chave após a delegate* sintaxe, que usará o padrão da plataforma de tempo de execução.Unmanaged calling conventions can by specified by putting an unmanaged keyword afer the delegate* syntax, which will use the runtime platform default. Convenções não gerenciadas específicas podem então ser especificadas entre colchetes para a unmanaged palavra-chave, especificando qualquer tipo começando com CallConv no System.Runtime.CompilerServices namespace, deixando o CallConv prefixo.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. Esses tipos devem vir da biblioteca principal do programa, e o conjunto de combinações válidas é dependente da 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>;

As conversões entre delegate* os tipos são feitas com base em sua assinatura, incluindo a Convenção de chamada.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
    }
}

Um delegate* tipo é um tipo de ponteiro que significa que ele tem todos os recursos e restrições de um tipo de ponteiro padrão:A delegate* type is a pointer type which means it has all of the capabilities and restrictions of a standard pointer type:

  • Válido somente em um unsafe contexto.Only valid in an unsafe context.
  • Os métodos que contêm um delegate* parâmetro ou tipo de retorno só podem ser chamados de um unsafe contexto.Methods which contain a delegate* parameter or return type can only be called from an unsafe context.
  • Não pode ser convertido em object .Cannot be converted to object.
  • Não pode ser usado como um argumento genérico.Cannot be used as a generic argument.
  • Pode converter implicitamente delegate* em void* .Can implicitly convert delegate* to void*.
  • Pode converter explicitamente de void* para delegate* .Can explicitly convert from void* to delegate*.

Restrições:Restrictions:

  • Atributos personalizados não podem ser aplicados a um delegate* ou a qualquer um de seus elementos.Custom attributes cannot be applied to a delegate* or any of its elements.
  • Um delegate* parâmetro não pode ser marcado como paramsA delegate* parameter cannot be marked as params
  • Um delegate* tipo tem todas as restrições de um tipo de ponteiro normal.A delegate* type has all of the restrictions of a normal pointer type.
  • A aritmética de ponteiro não pode ser executada diretamente em tipos de ponteiro de função.Pointer arithmetic cannot be performed directly on function pointer types.

Sintaxe de ponteiro de funçãoFunction pointer syntax

A sintaxe de ponteiro de função completa é representada pela seguinte 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'
    ;

Se não calling_convention_specifier for fornecido, o padrão será managed .If no calling_convention_specifier is provided, the default is managed. A codificação de metadados precisa dos calling_convention_specifier e quais identifier s são válidos no unmanaged_calling_convention é abordada na representação de metadados das convenções de chamada.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>>;

Conversões de ponteiro de funçãoFunction pointer conversions

Em um contexto sem segurança, o conjunto de conversões implícitas disponíveis (conversões implícitas) é estendido para incluir as seguintes conversões de ponteiro implícitas:In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:

  • Conversões existentesExisting conversions
  • De _ tipo funcptr F0 para outro _ tipo de funcptr F1 , desde que todos os seguintes itens sejam verdadeiros:From funcptr_type F0 to another funcptr_type F1, provided all of the following are true:
    • F0 e F1 têm o mesmo número de parâmetros, e cada parâmetro D0n no F0 tem os mesmos ref out modificadores,, ou in como o parâmetro correspondente D1n no 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 (um parâmetro sem nenhum ref , out ou in Modificador), uma conversão de identidade, conversão de referência implícita ou conversão de ponteiro implícita existe do tipo de parâmetro em F0 para o tipo de parâmetro correspondente no 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,, ou in , o tipo de parâmetro no F0 é o mesmo que o tipo de parâmetro correspondente no F1 .For each ref, out, or in parameter, the parameter type in F0 is the same as the corresponding parameter type in F1.
    • Se o tipo de retorno for por valor (não ref ou ref readonly ), uma identidade, referência implícita ou conversão implícita do ponteiro existirá do tipo de retorno de F1 para o tipo de retorno 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.
    • Se o tipo de retorno for por referência ( ref ou ref readonly ), o tipo de retorno e os ref modificadores de F1 serão iguais aos do tipo de retorno e ref dos 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.
    • A Convenção de chamada do F0 é igual à Convenção de chamada do F1 .The calling convention of F0 is the same as the calling convention of F1.

Permitir endereço para métodos de destinoAllow address-of to target methods

Os grupos de métodos agora serão permitidos como argumentos para uma expressão de endereço.Method groups will now be allowed as arguments to an address-of expression. O tipo de tal expressão será um delegate* que tem a assinatura equivalente do método de destino e uma Convenção de chamada gerenciada: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;
   }
}

Em um contexto sem segurança, um método M é compatível com um tipo de ponteiro F de função se todas as seguintes opções forem verdadeiras:In an unsafe context, a method M is compatible with a function pointer type F if all of the following are true:

  • M e F têm o mesmo número de parâmetros, e cada parâmetro no M tem os mesmos ref out modificadores,, ou in como o parâmetro correspondente no 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 (um parâmetro sem nenhum ref , out ou in Modificador), uma conversão de identidade, conversão de referência implícita ou conversão de ponteiro implícita existe do tipo de parâmetro em M para o tipo de parâmetro correspondente no 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,, ou in , o tipo de parâmetro no M é o mesmo que o tipo de parâmetro correspondente no F .For each ref, out, or in parameter, the parameter type in M is the same as the corresponding parameter type in F.
  • Se o tipo de retorno for por valor (não ref ou ref readonly ), uma identidade, referência implícita ou conversão implícita do ponteiro existirá do tipo de retorno de F para o tipo de retorno 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.
  • Se o tipo de retorno for por referência ( ref ou ref readonly ), o tipo de retorno e os ref modificadores de F serão iguais aos do tipo de retorno e ref dos 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.
  • A Convenção de chamada do M é igual à Convenção de chamada do F .The calling convention of M is the same as the calling convention of F. Isso inclui o bit da Convenção de chamada, bem como quaisquer sinalizadores de Convenção de chamada especificados no identificador não gerenciado.This includes both the calling convention bit, as well as any calling convention flags specified in the unmanaged identifier.
  • M é um método estático.M is a static method.

Em um contexto não seguro, existe uma conversão implícita de uma expressão de endereço cujo destino é um grupo de métodos E para um tipo de ponteiro de função compatível F se E contiver pelo menos um método que seja aplicável em seu formato normal a uma lista de argumentos construída pelo uso dos tipos de parâmetro e modificadores de F , conforme descrito no seguinte.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.

  • Um único método M é selecionado, correspondendo a uma invocação de método do formulário E(A) com as seguintes modificações:A single method M is selected corresponding to a method invocation of the form E(A) with the following modifications:
    • A lista de argumentos A é uma lista de expressões, cada uma classificada como variável e com o tipo e o modificador ( ref , out ou in ) da _ _ lista de parâmetros funcptr correspondente 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.
    • Os métodos candidatos são apenas os métodos que são aplicáveis em seu formato normal, não aqueles aplicáveis em sua forma expandida.The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form.
    • Os métodos candidatos são apenas os métodos que são estáticos.The candidate methods are only those methods that are static.
  • Se o algoritmo de resolução de sobrecarga produzir um erro, ocorrerá um erro de tempo de compilação.If the algorithm of overload resolution produces an error, then a compile-time error occurs. Caso contrário, o algoritmo produz um único método melhor M com o mesmo número de parâmetros que F e a conversão é considerada como existente.Otherwise, the algorithm produces a single best method M having the same number of parameters as F and the conversion is considered to exist.
  • O método selecionado M deve ser compatível (conforme definido acima) com o tipo de ponteiro de função F .The selected method M must be compatible (as defined above) with the function pointer type F. Caso contrário, ocorrerá um erro de tempo de compilação.Otherwise, a compile-time error occurs.
  • O resultado da conversão é um ponteiro de função do tipo F .The result of the conversion is a function pointer of type F.

Isso significa que os desenvolvedores podem depender das regras de resolução de sobrecarga para trabalhar em conjunto com o 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;
    }

O operador address-of será implementado usando a ldftn instrução.The address-of operator will be implemented using the ldftn instruction.

Restrições deste recurso:Restrictions of this feature:

  • Aplica-se somente a métodos marcados como static .Only applies to methods marked as static.
  • Funções não static locais não podem ser usadas no & .Non-static local functions cannot be used in &. Os detalhes de implementação desses métodos são deliberadamente não especificados pelo idioma.The implementation details of these methods are deliberately not specified by the language. Isso inclui se eles são estáticos versus instância ou exatamente em qual assinatura eles são emitidos.This includes whether they are static vs. instance or exactly what signature they are emitted with.

Operadores em tipos de ponteiro de funçãoOperators on Function Pointer Types

A seção em código não seguro em operadores é modificada da seguinte maneira:The section in unsafe code on operators is modified as such:

Em um contexto não seguro, várias construções estão disponíveis para operar em todos os type_s de _pointer _ que não são _funcptr _ type_s:In an unsafe context, several constructs are available for operating on all _pointer_type_s that are not _funcptr_type_s:

Em um contexto não seguro, várias construções estão disponíveis para operar em todos os _funcptr _ type_s:In an unsafe context, several constructs are available for operating on all _funcptr_type_s:

Além disso, modificamos todas as seções em Pointers in expressions para proibir tipos de ponteiro de função, exceto Pointer comparison e 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.

Melhor membro da funçãoBetter function member

A melhor especificação de membro de função será alterada para incluir a seguinte linha:The better function member specification will be changed to include the following line:

Um delegate* é mais específico do que void*A delegate* is more specific than void*

Isso significa que é possível sobrecarregar on void* e a delegate* e ainda facilmente usar o operador address-of.This means that it is possible to overload on void* and a delegate* and still sensibly use the address-of operator.

Inferência de tiposType Inference

Em código não seguro, as seguintes alterações são feitas nos algoritmos de inferência 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

O seguinte é adicionado:The following is added:

Se E for um grupo de métodos de endereço e T for um tipo de ponteiro de função, todos os tipos de parâmetro de T são tipos de entrada de E com o 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 saídaOutput types

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

O seguinte é adicionado:The following is added:

Se E for um grupo de métodos de endereço e T for um tipo de ponteiro de função, o tipo de retorno de T será um tipo de saída E com o 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.

Inferências de tipo de saídaOutput type inferences

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

O marcador a seguir é adicionado entre os marcadores 2 e 3:The following bullet is added between bullets 2 and 3:

  • Se E é um grupo de métodos de endereço e T é um tipo de ponteiro de função com tipos de parâmetro T1...Tk e tipo de retorno Tb , e a resolução de sobrecarga de E com os tipos T1..Tk gera um único método com tipo de retorno U , uma inferência de limite inferior é feita de U para 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.

Inferências exatasExact inferences

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

O submarcador a seguir é adicionado como um caso no marcador 2:The following sub-bullet is added as a case to bullet 2:

  • V é um tipo de ponteiro de função delegate*<V2..Vk, V1> e U é um tipo de ponteiro de função delegate*<U2..Uk, U1> , e a Convenção de chamada de V é idêntica a U e o refness de Vi é 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.

Inferências de limite inferiorLower-bound inferences

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

O seguinte caso é adicionado ao marcador 3:The following case is added to bullet 3:

  • V é um tipo de ponteiro de função delegate*<V2..Vk, V1> e há um tipo de ponteiro de função delegate*<U2..Uk, U1> que U é idêntico a delegate*<U2..Uk, U1> , e a Convenção de chamada de V é idêntica a U e o refness de Vi é 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.

O primeiro marcador de inferência de Ui para Vi é modificado para:The first bullet of inference from Ui to Vi is modified to:

  • Se U não for um tipo de ponteiro de função e Ui não for conhecido como um tipo de referência, ou se U for um tipo de ponteiro de função e Ui não for conhecido como um tipo de ponteiro de função ou um tipo de referência, uma inferência exata será feitaIf 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

Depois, adicionado após o submarcador de inferência de Ui para Vi :Then, added after the 3rd bullet of inference from Ui to Vi:

  • Caso contrário, se V for delegate*<V2..Vk, V1> , a inferência dependerá do 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>:
    • Se V1:If V1:
      • Se o retorno for por valor, será feita uma inferência de limite inferior .If the return is by value, then a lower-bound inference is made.
      • Se o retorno for por referência, uma inferência exata será feita.If the return is by reference, then an exact inference is made.
    • Se v2.. Vk:If V2..Vk:
      • Se o parâmetro for por valor, uma inferência de limite superior será feita.If the parameter is by value, then an upper-bound inference is made.
      • Se o parâmetro for por referência, uma inferência exata será feita.If the parameter is by reference, then an exact inference is made.

Inferências de limite superiorUpper-bound inferences

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

O seguinte caso é adicionado ao marcador 2:The following case is added to bullet 2:

  • U é um tipo de ponteiro de função delegate*<U2..Uk, U1> e V é um tipo de ponteiro de função que é idêntico a delegate*<V2..Vk, V1> , e a Convenção de chamada de U é idêntica a V e o refness de Ui é 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.

O primeiro marcador de inferência de Ui para Vi é modificado para:The first bullet of inference from Ui to Vi is modified to:

  • Se U não for um tipo de ponteiro de função e Ui não for conhecido como um tipo de referência, ou se U for um tipo de ponteiro de função e Ui não for conhecido como um tipo de ponteiro de função ou um tipo de referência, uma inferência exata será feitaIf 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

Depois, adicionado após o submarcador de inferência de Ui para Vi :Then added after the 3rd bullet of inference from Ui to Vi:

  • Caso contrário, se U for delegate*<U2..Uk, U1> , a inferência dependerá do 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>:
    • Se U1:If U1:
      • Se o retorno for por valor, uma inferência de limite superior será feita.If the return is by value, then an upper-bound inference is made.
      • Se o retorno for por referência, uma inferência exata será feita.If the return is by reference, then an exact inference is made.
    • Se U2.. BritânicoIf U2..Uk:
      • Se o parâmetro for por valor, será feita uma inferência de limite inferior .If the parameter is by value, then a lower-bound inference is made.
      • Se o parâmetro for por referência, uma inferência exata será feita.If the parameter is by reference, then an exact inference is made.

Representação de metadados in de out parâmetros,, e ref readonly tipos de retornoMetadata representation of in, out, and ref readonly parameters and return types

As assinaturas de ponteiro de função não têm nenhum local de sinalizadores de parâmetro, portanto, devemos codificar se os parâmetros e o tipo de retorno são in , out ou ref readonly usando 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

Reutilizamos System.Runtime.InteropServices.InAttribute , aplicada como um modreq para o especificador de referência em um parâmetro ou tipo de retorno, para significar o seguinte:We reuse System.Runtime.InteropServices.InAttribute, applied as a modreq to the ref specifier on a parameter or return type, to mean the following:

  • Se aplicado a um especificador de referência de parâmetro, esse parâmetro será tratado como in .If applied to a parameter ref specifier, this parameter is treated as in.
  • Se aplicado ao especificador de referência de tipo de retorno, o tipo de retorno será tratado 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 , aplicados como um modreq para o especificador de referência em um tipo de parâmetro, para significar que o parâmetro é um 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

  • É um erro a ser aplicado OutAttribute como um modreq a um tipo de retorno.It is an error to apply OutAttribute as a modreq to a return type.
  • É um erro aplicar InAttribute e OutAttribute como um modreq a um tipo de parâmetro.It is an error to apply both InAttribute and OutAttribute as a modreq to a parameter type.
  • Se ambos forem especificados via modopt, eles serão ignorados.If either are specified via modopt, they are ignored.

Representação de metadados de convenções de chamadaMetadata Representation of Calling Conventions

As convenções de chamada são codificadas em uma assinatura de método nos metadados por uma combinação do CallKind sinalizador na assinatura e zero ou mais modopt s no início da assinatura.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. O ECMA-335 declara atualmente os seguintes elementos no CallKind sinalizador:ECMA-335 currently declares the following elements in the CallKind flag:

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

Desses, os ponteiros de função no C# oferecerão suporte a todos, exceto varargs .Of these, function pointers in C# will support all but varargs.

Além disso, o tempo de execução (e, eventualmente, 335) será atualizado para incluir um novo CallKind nas novas plataformas.In addition, the runtime (and eventually 335) will be updated to include a new CallKind on new platforms. Isso não tem um nome formal no momento, mas este documento será usado unmanaged ext como um espaço reservado para destacar o novo formato de Convenção de chamada extensível.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. Sem modopt s, unmanaged ext é a Convenção de chamada padrão da plataforma, com unmanaged colchetes.With no modopts, unmanaged ext is the platform default calling convention, unmanaged without the square brackets.

Mapeando o calling_convention_specifier para um CallKindMapping the calling_convention_specifier to a CallKind

Um calling_convention_specifier que é omitido ou especificado como managed , é mapeado para o default CallKind .A calling_convention_specifier that is omitted, or specified as managed, maps to the default CallKind. Isso é o padrão CallKind de qualquer método não atribuído com UnmanagedCallersOnly .This is default CallKind of any method not attributed with UnmanagedCallersOnly.

O C# reconhece 4 identificadores especiais que são mapeados para s não gerenciados específicos existentes CallKind do ECMA 335.C# recognizes 4 special identifiers that map to specific existing unmanaged CallKinds from ECMA 335. Para que esse mapeamento ocorra, esses identificadores devem ser especificados por conta própria, sem nenhum outro identificador, e esse requisito é codificado na especificação para 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. Esses identificadores são Cdecl , Thiscall , Stdcall e Fastcall , que correspondem a unmanaged cdecl , unmanaged thiscall , unmanaged stdcall e unmanaged fastcall , respectivamente.These identifiers are Cdecl, Thiscall, Stdcall, and Fastcall, which correspond to unmanaged cdecl, unmanaged thiscall, unmanaged stdcall, and unmanaged fastcall, respectively. Se mais de um identifer for especificado ou o único identifier não for dos identificadores reconhecidos especialmente, executaremos uma pesquisa de nome especial no identificador com as seguintes regras: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:

  • Precedemos o identifier com a cadeia de caracteres CallConvWe prepend the identifier with the string CallConv
  • Examinamos apenas os tipos definidos no System.Runtime.CompilerServices namespace.We look only at types defined in the System.Runtime.CompilerServices namespace.
  • Examinamos apenas os tipos definidos na biblioteca principal do aplicativo, que é a biblioteca que define System.Object e não tem dependências.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.
  • Estamos procurando apenas em tipos públicos.We look only at public types.

Se a pesquisa for realizada com sucesso em todos os identifier s especificados em um unmanaged_calling_convention , codificaremos o as CallKind unmanaged ext e codificaremos cada um dos tipos resolvidos no conjunto de modopt s no início da assinatura do ponteiro de função.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 uma observação, essas regras significam que os usuários não podem prefixar esses identifier s com CallConv , pois isso resultará na pesquisa CallConvCallConvVectorCall .As a note, these rules mean that users cannot prefix these identifiers with CallConv, as that will result in looking up CallConvCallConvVectorCall.

Ao interpretar metadados, primeiro vamos examinar o CallKind .When interpreting metadata, we first look at the CallKind. Se for algo diferente de unmanaged ext , ignoramos todos os modopt s no tipo de retorno para fins de determinação da Convenção de chamada e usam apenas o 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. Se CallKind for, veremos unmanaged ext o modopts no início do tipo de ponteiro de função, assumindo a União de todos os tipos que atendem aos seguintes 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:

  • O é definido na biblioteca principal, que é a biblioteca que faz referência a nenhuma outra biblioteca e define System.Object .The is defined in the core library, which is the library that references no other libraries and defines System.Object.
  • O tipo é definido no System.Runtime.CompilerServices namespace.The type is defined in the System.Runtime.CompilerServices namespace.
  • O tipo começa com o prefixo CallConv .The type starts with the prefix CallConv.
  • O tipo é público.The type is public.

Eles representam os tipos que devem ser encontrados ao executar a pesquisa nos identifier s em um unmanaged_calling_convention ao definir um tipo de ponteiro de função na origem.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.

É um erro tentar usar um ponteiro de função com um CallKind de unmanaged ext se o tempo de execução de destino não oferecer suporte ao recurso.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. Isso será determinado procurando a presença da System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind constante.This will be determined by looking for the presence of the System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind constant. Se essa constante estiver presente, o tempo de execução será considerado para dar suporte ao recurso.If this constant is present, the runtime is considered to support the feature.

System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute

System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute é um atributo usado pelo CLR para indicar que um método deve ser chamado com uma Convenção de chamada específica.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 isso, apresentamos o seguinte suporte para trabalhar com o atributo:Because of this, we introduce the following support for working with the attribute:

  • É um erro chamar diretamente um método anotado com esse atributo em C#.It is an error to directly call a method annotated with this attribute from C#. Os usuários devem obter um ponteiro de função para o método e, em seguida, invocar esse ponteiro.Users must obtain a function pointer to the method and then invoke that pointer.
  • É um erro aplicar o atributo a qualquer coisa que não seja um método estático comum ou uma função local estática comum.It is an error to apply the attribute to anything other than an ordinary static method or ordinary static local function. O compilador C# marcará quaisquer métodos não estáticos ou estáticos não-comuns importados de metadados com esse atributo como sem suporte no idioma.The C# compiler will mark any non-static or static non-ordinary methods imported from metadata with this attribute as unsupported by the language.
  • É um erro para um método marcado com o atributo para ter um parâmetro ou tipo de retorno que não seja um 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.
  • É um erro para um método marcado com o atributo para ter parâmetros de tipo, mesmo se esses parâmetros de tipo forem restritos 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.
  • É um erro para um método em um tipo genérico ser marcado com o atributo.It is an error for a method in a generic type to be marked with the attribute.
  • É um erro converter um método marcado com o atributo para um tipo delegado.It is an error to convert a method marked with the attribute to a delegate type.
  • É um erro especificar quaisquer tipos para UnmanagedCallersOnly.CallConvs os quais não atendem aos requisitos para chamar a Convenção modopt s nos metadados.It is an error to specify any types for UnmanagedCallersOnly.CallConvs that do not meet the requirements for calling convention modopts in metadata.

Ao determinar a Convenção de chamada de um método marcado com um UnmanagedCallersOnly atributo válido, o compilador executa as seguintes verificações nos tipos especificados na CallConvs propriedade para determinar o efetivo CallKind e os modopt s que devem ser usados para determinar a Convenção de chamada: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:

  • Se nenhum tipo for especificado, o CallKind será tratado como unmanaged ext , sem Convenção de chamada modopt s no início do tipo de ponteiro de função.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.
  • Se houver um tipo especificado, e esse tipo for chamado CallConvCdecl , CallConvThiscall , CallConvStdcall ou CallConvFastcall , o CallKind será tratado como unmanaged cdecl , unmanaged thiscall , unmanaged stdcall ou unmanaged fastcall , respectivamente, sem nenhuma convenção modopt de chamada s no início do tipo de ponteiro de função.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.
  • Se vários tipos forem especificados ou o tipo único não for nomeado um dos tipos especialmente chamados acima, o CallKind será tratado como unmanaged ext , com a União dos tipos especificados tratados como modopt s no início do tipo de ponteiro de função.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.

O compilador então examina esse efetivo CallKind e a modopt coleção e usa as regras de metadados normais para determinar a Convenção de chamada final do tipo de ponteiro de função.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.

Perguntas em abertoOpen Questions

Detectando suporte de tempo de execução para unmanaged extDetecting runtime support for unmanaged ext

https://github.com/dotnet/runtime/issues/38135 controla a adição deste sinalizador.https://github.com/dotnet/runtime/issues/38135 tracks adding this flag. Dependendo dos comentários da revisão, usaremos a propriedade especificada no problema ou usamos a presença de UnmanagedCallersOnlyAttribute como o sinalizador que determina se os tempos de execução são compatíveis com o 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.

ConsideraçõesConsiderations

Permitir métodos de instânciaAllow instance methods

A proposta pode ser estendida para dar suporte a métodos de instância aproveitando a EXPLICITTHIS Convenção de chamada da CLI (chamada instance em código C#).The proposal could be extended to support instance methods by taking advantage of the EXPLICITTHIS CLI calling convention (named instance in C# code). Essa forma de ponteiros de função da CLI coloca o this parâmetro como um primeiro parâmetro explícito da sintaxe do ponteiro de função.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);
    }
}

Isso é bom, mas adiciona certa complicação à proposta.This is sound but adds some complication to the proposal. Particularmente porque os ponteiros de função que diferem pela Convenção de chamada instance e managed seriam incompatíveis mesmo que ambos os casos sejam usados para invocar métodos gerenciados com a mesma assinatura 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. Além disso, em todos os casos, consideramos que isso seria valioso ter uma solução simples: usar uma static função 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);
    }
}

Não requer segurança na declaraçãoDon't require unsafe at declaration

Em vez de exigir unsafe em cada uso de um delegate* , só é necessário no ponto em que um grupo de métodos é convertido em um 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*. É aí que os problemas de segurança principal entram em cena (sabendo que o assembly recipiente não pode ser descarregado enquanto o valor estiver ativo).This is where the core safety issues come into play (knowing that the containing assembly cannot be unloaded while the value is alive). Exigir unsafe em outros locais pode ser visto como excessivo.Requiring unsafe on the other locations can be seen as excessive.

É assim que o design foi originalmente planejado.This is how the design was originally intended. Mas as regras de linguagem resultantes pareciam muito estranhas.But the resulting language rules felt very awkward. É impossível ocultar o fato de que esse é um valor de ponteiro e que ele continua exibindo mesmo sem a unsafe palavra-chave.It's impossible to hide the fact that this is a pointer value and it kept peeking through even without the unsafe keyword. Por exemplo, a conversão para object não pode ser permitida, não pode ser membro de um class , etc... O design do C# é exigir unsafe para todos os usos de ponteiro e, portanto, esse design é o seguinte.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.

Os desenvolvedores ainda poderão apresentar um wrapper seguro sobre delegate* os valores da mesma maneira que fazem para tipos de ponteiros normais hoje em dia.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();
}

Usando delegadosUsing delegates

Em vez de usar um novo elemento Syntax, delegate* basta usar delegate tipos existentes com * o seguinte tipo: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;

A manipulação de Convenção de chamada pode ser feita anotando os delegate tipos com um atributo que especifica um CallingConvention valor.Handling calling convention can be done by annotating the delegate types with an attribute that specifies a CallingConvention value. A falta de um atributo significaria a Convenção de chamada gerenciada.The lack of an attribute would signify the managed calling convention.

Codificar isso no IL é problemático.Encoding this in IL is problematic. O valor subjacente precisa ser representado como um ponteiro, ainda que ele também deva:The underlying value needs to be represented as a pointer yet it also must:

  1. Ter um tipo exclusivo para permitir sobrecargas com tipos diferentes de ponteiro de função.Have a unique type to allow for overloads with different function pointer types.
  2. Ser equivalente para fins de OHI em limites de assembly.Be equivalent for OHI purposes across assembly boundaries.

O último ponto é particularmente problemático.The last point is particularly problematic. Isso significa que cada assembly que usa Func<int>* deve codificar um tipo equivalente em metadados, embora Func<int>* esteja definido em um assembly, embora não controle.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. Além disso, qualquer outro tipo que é definido com o nome System.Func<T> em um assembly que não seja mscorlib deve ser diferente da versão definida em 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.

Uma opção que foi explorada estava emitindo tal ponteiro como mod_req(Func<int>) void* .One option that was explored was emitting such a pointer as mod_req(Func<int>) void*. Isso não funciona, embora mod_req não seja possível associar a a TypeSpec e, portanto, não pode direcionar instanciações genéricas.This doesn't work though as a mod_req cannot bind to a TypeSpec and hence cannot target generic instantiations.

Ponteiros de função nomeadosNamed function pointers

A sintaxe do ponteiro de função pode ser complicada, particularmente em casos complexos, como ponteiros de funções aninhadas.The function pointer syntax can be cumbersome, particularly in complex cases like nested function pointers. Em vez de os desenvolvedores digitarem a assinatura sempre que o idioma puder permitir declarações nomeadas de ponteiros de função como é feito com 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 do problema aqui é que o primitivo de CLI subjacente não tem nomes, portanto, essa seria apenas uma invenção de C# e requer um pouco de trabalho de metadados para habilitar.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. Isso é factível, mas é um grande respeito do trabalho.That is doable but is a significant about of work. Basicamente, ele requer que o C# tenha um complemento para a tabela de tipo def puramente para esses nomes.It essentially requires C# to have a companion to the type def table purely for these names.

Além disso, quando os argumentos dos ponteiros de função nomeados foram examinados, eles poderiam ser aplicados igualmente bem a vários outros cenários.Also when the arguments for named function pointers were examined we found they could apply equally well to a number of other scenarios. Por exemplo, seria conveniente declarar tuplas nomeadas para reduzir a necessidade de digitar a assinatura completa em todos os 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);
    }
}

Após a discussão, decidimos não permitir a declaração nomeada de delegate* tipos.After discussion we decided to not allow named declaration of delegate* types. Se encontrarmos uma necessidade significativa para isso com base nos comentários de uso do cliente, investigaremos uma solução de nomenclatura que funciona para ponteiros de função, tuplas, genéricos, etc... Isso provavelmente será semelhante em forma de outras sugestões, como suporte completo typedef no idioma.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.

Considerações futurasFuture Considerations

delegados estáticosstatic delegates

Isso se refere à proposta para permitir a declaração de delegate tipos que só podem se referir a static Membros.This refers to the proposal to allow for the declaration of delegate types which can only refer to static members. A vantagem é que essas delegate instâncias podem ser de alocação gratuita e melhor em cenários de desempenho.The advantage being that such delegate instances can be allocation free and better in performance sensitive scenarios.

Se o recurso de ponteiro de função for implementado static delegate , a proposta provavelmente será fechada. A vantagem proposta desse recurso é a natureza livre de alocação.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. No entanto, foram encontradas investigações recentes que não podem ser obtidas devido ao descarregamento do assembly.However recent investigations have found that is not possible to achieve due to assembly unloading. Deve haver um identificador forte do static delegate para o método ao qual se refere para impedir que o assembly seja descarregado de dentro dele.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 manter cada static delegate instância, seria necessário alocar um novo identificador que executa um contador para os objetivos da proposta.To maintain every static delegate instance would be required to allocate a new handle which runs counter to the goals of the proposal. Havia alguns designs em que a alocação poderia ser amortizada para uma única alocação por site de chamada, mas isso era um pouco complexo e não parece que vale a pena compensar.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.

Isso significa que os desenvolvedores têm, essencialmente, decidir entre as seguintes compensações:That means developers essentially have to decide between the following trade offs:

  1. Segurança diante do descarregamento do assembly: isso requer alocações e, portanto, delegate já é uma opção suficiente.Safety in the face of assembly unloading: this requires allocations and hence delegate is already a sufficient option.
  2. Não há segurança em face de descarregamento de assembly: Use um delegate* .No safety in face of assembly unloading: use a delegate*. Isso pode ser encapsulado em um struct para permitir o uso fora de um unsafe contexto no restante do código.This can be wrapped in a struct to allow usage outside an unsafe context in the rest of the code.