Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Różnice te są ujęte w notatkach z odpowiednich spotkań projektowych języka (LDM) .
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Streszczenie
Ta propozycja zawiera konstrukcje językowe, które uwidaczniają kode operacji IL, do których obecnie nie można uzyskać efektywnego dostępu lub w ogóle, w języku C#: ldftn i calli. Te kody operacji IL mogą być ważne w kodzie o wysokiej wydajności, a deweloperzy potrzebują wydajnego sposobu uzyskiwania do nich dostępu.
Motywacja
Motywacje i tło dla tej funkcji opisano w następującym zagadnieniu (tak jak potencjalna implementacja tej funkcji):
Jest to alternatywna propozycja projektu funkcji wewnętrznych kompilatora
Szczegółowy projekt
Wskaźniki funkcji
Język umożliwia deklarowanie wskaźników funkcji przy użyciu składni delegate*. Pełną składnię opisano szczegółowo w następnej sekcji, ale ma ona przypominać składnię używaną przez Func i deklaracje typów Action.
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Te typy są reprezentowane przy użyciu typu wskaźnika funkcji zgodnie z opisem w ECMA-335. Oznacza to, że wywołanie delegate* będzie używać calli, gdy wywołanie delegate będzie używać callvirt w metodzie Invoke.
Choć składniowo wywołanie jest identyczne dla obu konstrukcji.
Definicja wskaźników metody ECMA-335 zawiera konwencję wywoływania w ramach podpisu typu (sekcja 7.1).
Domyślna konwencja wywoływania będzie managed. Konwencje wywoływania niezarządzanego można określić, umieszczając słowo kluczowe unmanaged po składni delegate*, co spowoduje użycie domyślnej platformy systemu uruchomieniowego. Określone konwencje niezarządzane można następnie określić w nawiasach do słowa kluczowego unmanaged, określając dowolny typ rozpoczynający się od CallConv w przestrzeni nazw System.Runtime.CompilerServices, pozostawiając prefiks CallConv. Te typy muszą pochodzić z podstawowej biblioteki programu, a zestaw prawidłowych kombinacji jest zależny od platformy.
//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>;
Konwersje między typami delegate* są wykonywane na podstawie ich podpisu, wraz z konwencją wywoływania.
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
}
}
Typ delegate* jest typem wskaźnika, co oznacza, że ma wszystkie możliwości i ograniczenia standardowego typu wskaźnika:
- Jest prawidłowe tylko w kontekście
unsafe. - Metody zawierające parametr
delegate*lub typ zwracany mogą być wywoływane tylko z kontekstuunsafe. - Nie można przekonwertować na
object. - Nie można użyć jako argumentu ogólnego.
- Może niejawnie konwertować
delegate*navoid*. - Może jawnie konwertować z
void*nadelegate*.
Ograniczenia:
- Atrybutów niestandardowych nie można zastosować do
delegate*ani żadnego z jego elementów. - Nie można oznaczyć parametru
delegate*jakoparams - Typ
delegate*ma wszystkie ograniczenia normalnego typu wskaźnika. - Nie można wykonać arytmetyki wskaźnika bezpośrednio na typach wskaźników funkcji.
Składnia wskaźnika funkcji
Pełna składnia wskaźnika funkcji jest reprezentowana przez następującą gramatykę:
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'
;
Jeśli nie podano calling_convention_specifier, wartość domyślna to managed. Dokładne kodowanie metadanych calling_convention_specifier oraz które identifiersą prawidłowe w unmanaged_calling_convention, opisano w reprezentacji metadanych konwencji wywoływania .
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>>;
Konwersje wskaźnika funkcji
W niebezpiecznym kontekście zestaw dostępnych niejawnych konwersji jest rozszerzony, aby uwzględnić następujące niejawne konwersje wskaźnika:
- istniejące konwersje — (§23.5)
- Od funcptr_type
F0do innego funcptr_typeF1, pod warunkiem, że spełnione są wszystkie poniższe warunki:-
F0iF1mają taką samą liczbę parametrów, a każdy parametrD0nwF0ma ten samref,outlub modyfikatoryinco odpowiedni parametrD1nwF1. - Dla każdego parametru wartości (czyli parametru bez modyfikatora
ref,outlubin), istnieje konwersja tożsamości, niejawna konwersja odwołania lub niejawna konwersja wskaźnika z typu parametru wF0do odpowiadającego typu parametru wF1. - Dla każdego parametru
ref,outlubintyp parametru wF0jest taki sam jak odpowiedni typ parametru wF1. - Jeśli zwracany typ jest według wartości (bez
reflubref readonly), to istnieje tożsamość, niejawne odwołanie lub niejawna konwersja wskaźnika ze zwracanego typuF1na zwracany typF0. - Jeśli zwracany typ jest według odwołania (
reflubref readonly), to zarówno zwracany typ, jak i modyfikatoryrefF1są takie same jak zwracany typ i modyfikatoryrefF0. - Konwencja wywoływania
F0jest taka sama jak konwencja wywoływaniaF1.
-
Pozwól na uzyskiwanie adresu metod
Grupy metod będą teraz dozwolone jako argumenty dla wyrażenia adres-of. Typ takiego wyrażenia to delegate*, który ma równoważny podpis metody docelowej oraz zarządzaną konwencję wywoływania.
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;
}
}
W niebezpiecznym kontekście metoda M jest zgodna z typem wskaźnika funkcji F, jeśli spełnione są wszystkie następujące elementy:
-
MiFmają taką samą liczbę parametrów, a każdy parametr wMma ten samref,outlub modyfikatoryinco odpowiedni parametr wF. - Dla każdego parametru wartości (czyli parametru bez modyfikatora
ref,outlubin), istnieje konwersja tożsamości, niejawna konwersja odwołania lub niejawna konwersja wskaźnika z typu parametru wMdo odpowiadającego typu parametru wF. - Dla każdego parametru
ref,outlubintyp parametru wMjest taki sam jak odpowiedni typ parametru wF. - Jeśli zwracany typ jest według wartości (bez
reflubref readonly), to istnieje tożsamość, niejawne odwołanie lub niejawna konwersja wskaźnika ze zwracanego typuFna zwracany typM. - Jeśli zwracany typ jest według odwołania (
reflubref readonly), to zarówno zwracany typ, jak i modyfikatoryrefFsą takie same jak zwracany typ i modyfikatoryrefM. - Konwencja wywoływania
Mjest taka sama jak konwencja wywoływaniaF. Obejmuje to zarówno bit konwencji wywoływania, jak i wszystkie flagi konwencji wywoływania określone w identyfikatorze niezarządzanym. -
Mjest metodą statyczną.
W niebezpiecznym kontekście istnieje niejawna konwersja z wyrażenia adresu, którego celem jest grupa metod E do odpowiedniego typu wskaźnika funkcji F, jeśli E zawiera co najmniej jedną metodę, którą można zastosować w postaci normalnej do listy argumentów skonstruowanej przy użyciu typów parametrów i modyfikatorów F, jak opisano poniżej.
- Wybrano pojedynczą metodę
Modpowiadającą wywołaniu metody formularzaE(A)z następującymi modyfikacjami:- Lista argumentów
Ajest listą wyrażeń, z których każde jest klasyfikowane jako zmienna oraz ma typ i modyfikator (ref,outlubin) odpowiadający elementom funcptr_parameter_list wF. - Metody kandydatów to tylko te metody, które mają zastosowanie w ich normalnej postaci, a nie te, które mają zastosowanie w ich rozszerzonej formie.
- Metody kandydatów to tylko te metody, które są statyczne.
- Lista argumentów
- Jeśli algorytm rozwiązywania przeciążeń generuje błąd, wystąpi błąd czasu kompilacji. W przeciwnym razie algorytm tworzy jedną najlepszą metodę
Mmającą taką samą liczbę parametrów, jakF, a konwersja jest uważana za istniejącą. - Wybrana metoda
Mmusi być zgodna (zgodnie z definicją powyżej) z typem wskaźnika funkcjiF. W przeciwnym razie wystąpi błąd czasu kompilacji. - Wynikiem konwersji jest wskaźnik funkcji typu
F.
Oznacza to, że deweloperzy mogą polegać na regułach rozpoznawania przeciążenia, aby działały w połączeniu z operatorem adresu.
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;
}
}
Operator address-of zostanie zaimplementowany przy użyciu instrukcji ldftn.
Ograniczenia tej funkcji:
- Dotyczy tylko metod oznaczonych jako
static. - Nie można używać funkcji lokalnych innych niż
staticw&. Szczegóły implementacji tych metod nie są celowo określone przez język. Obejmuje to, czy są statyczne, czy instancyjne, oraz dokładnie, z jaką sygnaturą są emitowane.
Operatory dla typów wskaźników funkcji
Sekcja w niebezpiecznym kodzie w wyrażeniach jest modyfikowana w następujący sposób:
W niebezpiecznym kontekście dostępnych jest kilka konstrukcji do pracy ze wszystkimi _pointer_type_s, które nie są _funcptr_type_s.
- Operator
*może być używany do wykonywania inderekcji wskaźnika (§23.6.2).- Operator
->może służyć do uzyskiwania dostępu do elementu członkowskiego struktury za pośrednictwem wskaźnika (§23.6.3).- Operator
[]może służyć do indeksowania wskaźnika (§23.6.4).- Operator
&może służyć do uzyskania adresu zmiennej (§23.6.5).- Operatory
++i--mogą służyć do przyrostowania i dekrementacji wskaźników (§23.6.6).- Operatory
+i-mogą służyć do wykonywania arytmetyki wskaźnika (§23.6.7).- Operatory
==,!=,<,>,<=i=>mogą służyć do porównywania wskaźników (§23.6.8).- Operator
stackallocmoże służyć do przydzielania pamięci ze stosu wywołań (§23.8).- Instrukcja
fixedmoże służyć do tymczasowego naprawienia zmiennej, aby można było uzyskać jej adres (§23.7).W niebezpiecznym kontekście dostępnych jest kilka konstrukcji do działania na wszystkich _funcptr_type_s:
- Operator
&może być używany do uzyskiwania adresu metod statycznych (Pozwól na uzyskiwanie adresu metod docelowych)- Operatory
==,!=,<,>,<=i=>mogą służyć do porównywania wskaźników (§23.6.8).
Ponadto zmodyfikujemy wszystkie sekcje w Pointers in expressions, aby zabronić typów wskaźników funkcji, z wyjątkiem Pointer comparison i The sizeof operator.
Lepszy element członkowski funkcji
§12.6.4.3 Lepszy członek funkcji zostanie zmieniony, aby zawierał następujący wiersz:
delegate*jest bardziej szczegółowa niżvoid*
Oznacza to, że można przeciążyć void* i delegate* i nadal rozsądnie używać operatora address-of.
Inferencja typów
W niebezpiecznym kodzie następujące zmiany są wprowadzane do algorytmów wnioskowania typów:
Typy danych wejściowych
Dodano następujące elementy:
Jeśli
Ejest grupą metod adresowych, aTjest typem wskaźnika funkcji, wszystkie typy parametrówTsą typami wejściowymiEz typemT.
Typy danych wyjściowych
Dodano następujące elementy:
Jeśli
Ejest grupą metod adresową, aTjest typem wskaźnika funkcji, zwracany typTjest typem danych wyjściowychEz typemT.
Wnioskowanie typów danych wyjściowych
Następujący punktor jest dodawany między punktorami 2 i 3:
- Jeśli
Ejest grupą metod adresowych, aTjest typem wskaźnika funkcji z typami parametrówT1...Tki zwracanym typemTb, a rozpoznawanie przeciążeniaEz typamiT1..Tkdaje jedną metodę z typem zwracanymUU,Ujest wykonywana zTbdo .
Lepsze przekształcenie z wyrażenia
Następujący punktor podrzędny jest dodawany jako przykład do punktora 2:
Vjest typem wskaźnika funkcjidelegate*<V2..Vk, V1>, podczas gdyUjest typem wskaźnika funkcjidelegate*<U2..Uk, U1>. Konwencja wywoływaniaVjest identyczna zU, a referencjaVijest taka sama jakUi.
Wnioskowanie o granicy dolnej
Następujący przypadek został dodany do punktu 3:
Vjest typem wskaźnika funkcjidelegate*<V2..Vk, V1>i istnieje typ wskaźnika funkcjidelegate*<U2..Uk, U1>taki, żeUjest identyczny zdelegate*<U2..Uk, U1>, a konwencja wywoływaniaVjest identyczna zU, a refnośćVijest identyczna zUi.
Pierwszy punkt wnioskowania z Ui do Vi został zmodyfikowany na:
- Jeśli
Unie jest typem wskaźnika funkcji iUinie jest znany jako typ referencyjny, lub jeśliUjest typem wskaźnika funkcji, aUinie jest znany jako typ wskaźnika funkcji ani typ referencyjny, to zostaje wykonane dokładne wnioskowanie .
Następnie dodano po trzecim punkcie wnioskowania z Ui do Vi:
- W przeciwnym razie, jeśli
Vjestdelegate*<V2..Vk, V1>, wnioskowanie zależy od i-tego parametrudelegate*<V2..Vk, V1>:
- Jeśli wersja 1:
- Jeśli zwracana jest według wartości, zostanie wykonane wnioskowanie o niższej granicy.
- Jeśli zwracanie następuje przez odwołanie, zostanie wykonane dokładne wnioskowanie.
- Jeśli V2..Vk:
- Jeśli parametr jest według wartości, zostanie wykonane wnioskowanie górnej granicy.
- Jeśli parametr jest przy użyciu odwołania, zostanie wykonane dokładne wnioskowanie.
Wnioskowanie o górnej granicy
Następujący przypadek jest dodawany do punktu 2:
Ujest typem wskaźnika funkcjidelegate*<U2..Uk, U1>, aVjest typem wskaźnika funkcji, który jest identyczny zdelegate*<V2..Vk, V1>, a konwencja wywoływaniaUjest identyczna zV, a refnośćUijest identyczna zVi.
Pierwszy punkt wnioskowania z Ui do Vi został zmodyfikowany na:
- Jeśli
Unie jest typem wskaźnika funkcji iUinie jest znany jako typ referencyjny, lub jeśliUjest typem wskaźnika funkcji, aUinie jest znany jako typ wskaźnika funkcji ani typ referencyjny, to zostaje wykonane dokładne wnioskowanie .
Następnie dodano po trzecim punkcie wnioskowania z Ui do Vi:
- W przeciwnym razie, jeśli
Ujestdelegate*<U2..Uk, U1>, wnioskowanie zależy od i-tego parametrudelegate*<U2..Uk, U1>:
- Jeśli U1:
- Jeśli zwracana jest według wartości, zostanie wykonana wnioskowanie górnej granicy.
- Jeśli zwracanie następuje przez odwołanie, zostanie wykonane dokładne wnioskowanie.
- Jeśli U2..Uk:
- Jeśli parametr jest przekazywany przez wartość, zostanie wykonana inferencja dolnej granicy .
- Jeśli parametr jest przy użyciu odwołania, zostanie wykonane dokładne wnioskowanie.
Reprezentacja metadanych in, outi ref readonly parametrów i typów zwracanych
Sygnatury wskaźników funkcji nie mają lokalizacji flag parametrów, dlatego musimy zakodować, czy parametry i typ zwracany są in, outlub ref readonly za pomocą modreqs.
in
Ponownie użyjemy System.Runtime.InteropServices.InAttribute, zastosowanego jako modreq do specyfikatora 'ref' w parametrze lub typie zwracanym, aby oznaczać następujące kwestie:
- Jeśli zastosowano go do specyfikatora ref parametru, ten parametr jest traktowany jako
in. - Jeśli zastosowano do specyfikatora ref typu zwracanego, zwracany typ jest traktowany jako
ref readonly.
out
Używamy System.Runtime.InteropServices.OutAttribute, które stosuje się jako modreq do specyfikatora ref w typie parametru, aby wskazać, że parametr jest parametrem out.
Błędy
- Stosowanie
OutAttributejako modreq dla typu zwracanego jest błędem. - Jest to błąd podczas stosowania zarówno
InAttribute, jak iOutAttributejako modreq do typu parametru. - Jeśli dowolny z nich jest określony za pośrednictwem modopt, są one ignorowane.
Reprezentacja metadanych konwencji wywoływania
Konwencje wywoływania są kodowane w podpisie metody w metadanych przez kombinację flagi CallKind w podpisie i zero lub więcej modopts na początku podpisu. EcMA-335 obecnie deklaruje następujące elementy w flagi CallKind:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Z tych elementów wskaźniki funkcji w języku C# będą obsługiwać wszystkie, z wyjątkiem varargs.
Ponadto środowisko uruchomieniowe (oraz ostatecznie 335) zostanie zaktualizowane, aby uwzględniać nowy CallKind na nowych platformach. Nie ma obecnie formalnej nazwy, ale ten dokument będzie używać unmanaged ext jako symbol zastępczy do oznaczania nowego rozszerzalnego formatu konwencji wywoływania. Gdy brak modopts, unmanaged ext jest domyślną konwencją wywoływania platformy, a unmanaged występuje bez nawiasów kwadratowych.
Mapowanie calling_convention_specifier na CallKind
calling_convention_specifier, który zostanie pominięty lub określony jako managed, odwzorowuje się na defaultCallKind. Jest to domyślny CallKind dla jakiejkolwiek metody, która nie jest przypisana UnmanagedCallersOnly.
Język C# rozpoznaje 4 specjalne identyfikatory mapowane na określone istniejące niezarządzane elementy zdefiniowane jako CallKindw ECMA 335. Aby to mapowanie było wykonywane, te identyfikatory muszą być określone samodzielnie, bez innych identyfikatorów, a to wymaganie jest zakodowane w specyfikacji dla unmanaged_calling_conventions. Te identyfikatory to Cdecl, Thiscall, Stdcalli Fastcall, które odpowiadają odpowiednio unmanaged cdecl, unmanaged thiscall, unmanaged stdcalli unmanaged fastcall. Jeśli określono więcej niż jedną identifer lub pojedynczy identifier nie należy do specjalnie uznawanych identyfikatorów, przeprowadzamy specjalne wyszukiwanie nazwy na identyfikatorze według następujących reguł:
- Poprzedzamy
identifierciągiemCallConv - Przyjrzymy się tylko typom zdefiniowanym w przestrzeni nazw
System.Runtime.CompilerServices. - Przyjrzymy się tylko typom zdefiniowanym w podstawowej bibliotece aplikacji, czyli tej, która definiuje
System.Objecti nie zależy od innych bibliotek. - Patrzymy tylko na typy publiczne.
Jeśli wyszukiwanie powiedzie się na wszystkich identifierokreślonych w unmanaged_calling_convention, kodujemy CallKind jako unmanaged exti kodujemy każdy z rozpoznanych typów w zestawie modopts na początku podpisu wskaźnika funkcji. Należy zauważyć, że te reguły oznaczają, iż użytkownicy nie mogą poprzedzać tych identifiertagami CallConv, ponieważ spowoduje to wyszukanie CallConvCallConvVectorCall.
Podczas interpretowania metadanych najpierw przyjrzymy się CallKind. Jeśli jest to coś innego niż unmanaged ext, ignorujemy wszystkie modoptw typie zwrotnym dla określania konwencji wywoływania i wykorzystujemy tylko CallKind. Jeśli CallKind jest unmanaged ext, przyjrzymy się modopts na początku typu wskaźnika funkcji, tworząc sumę wszystkich typów, które spełniają następujące wymagania:
- Element jest zdefiniowany w bibliotece podstawowej, czyli w bibliotece, która nie odwołuje się do innych bibliotek i definiuje
System.Object. - Typ jest zdefiniowany w przestrzeni nazw
System.Runtime.CompilerServices. - Typ rozpoczyna się od prefiksu
CallConv. - Typ jest publiczny.
Reprezentują one typy, które należy znaleźć podczas wyszukiwania w identifiers w unmanaged_calling_convention podczas definiowania typu wskaźnika funkcji w źródle.
Jest to błąd podczas próby użycia wskaźnika funkcji z CallKindunmanaged ext, jeśli docelowe środowisko uruchomieniowe nie obsługuje tej funkcji. Zostanie to ustalone, wyszukując obecność stałej System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind. Jeśli ta stała jest obecna, uznaje się, że środowisko uruchomieniowe obsługuje tę funkcję.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute jest atrybutem używanym przez CLR, aby wskazać, że metoda powinna być wywoływana z określoną konwencją wywoływania. W związku z tym wprowadzamy następującą obsługę atrybutu:
- Bezpośrednie wywołanie metody oznaczonej tym atrybutem w języku C# jest błędem. Użytkownicy muszą uzyskać wskaźnik funkcji do metody, a następnie wywołać ten wskaźnik.
- Błędem jest stosowanie atrybutu do czegoś innego niż zwykła metoda statyczna lub zwykła lokalna funkcja statyczna. Kompilator języka C# oznaczy wszystkie niestatyczne lub statyczne nietypowe metody importowane z metadanych za pomocą tego atrybutu jako nieobsługiwane przez język.
- Jest to błąd, gdy metoda oznaczona atrybutem ma parametr lub zwracany typ, który nie jest
unmanaged_type. - Jest to błędem, gdy metoda oznaczona atrybutem posiada parametry typu, jeśli te parametry typu są ograniczone do
unmanaged. - Błędem jest oznaczenie metody w typie ogólnym przy pomocy atrybutu.
- Jest to błąd podczas konwertowania metody oznaczonej atrybutem na typ delegata.
- Popełniono błąd, podając jakiekolwiek typy dla
UnmanagedCallersOnly.CallConvs, które nie spełniają wymagań dla konwencji wywoływaniamodoptw metadanych.
Podczas określania konwencji wywoływania metody oznaczonej prawidłowym atrybutem UnmanagedCallersOnly kompilator wykonuje następujące kontrole typów określonych we właściwości CallConvs w celu określenia skutecznych CallKind i modopt, które powinny być używane do określania konwencji wywoływania:
- Jeśli nie określono żadnych typów,
CallKindjest traktowana jakounmanaged ext, bez konwencji wywoływaniamodopts na początku typu wskaźnika funkcji. - Jeśli określono jeden typ, a ten typ ma nazwę
CallConvCdecl,CallConvThiscall,CallConvStdcalllubCallConvFastcall,CallKindjest traktowany jakounmanaged cdecl,unmanaged thiscall,unmanaged stdcalllubunmanaged fastcall, odpowiednio bez konwencji wywoływaniamodopts na początku typu wskaźnika funkcji. - Jeśli określono wiele typów lub pojedynczy typ nie jest nazwany jednym ze specjalnie wymienionych typów powyżej,
CallKindjest traktowana jakounmanaged ext, a unia określonych typów jest traktowana jakomodoptna początku typu wskaźnika funkcji.
Następnie kompilator analizuje tę skuteczną kolekcję CallKind i modopt oraz używa normalnych zasad metadanych do określenia ostatecznej konwencji wywołania wskaźnika do funkcji.
Otwórz pytania
Wykrywanie obsługi środowiska uruchomieniowego dla unmanaged ext
https://github.com/dotnet/runtime/issues/38135 śledzi dodawanie tej flagi. W zależności od opinii z przeglądu użyjemy właściwości określonej w problemie lub użyjemy obecności UnmanagedCallersOnlyAttribute jako flagi określającej, czy środowiska uruchomieniowe obsługują unmanaged ext.
Zagadnienia dotyczące
Zezwalaj na metody wystąpień
Wniosek można rozszerzyć na obsługę metod instancji, korzystając z konwencji wywoływania CLI EXPLICITTHIS (zwanej instance w kodzie C#). Ten sposób wskaźników funkcji CLI umieszcza parametr this jako jawny pierwszy parametr w składni wskaźnika funkcji.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
To jest rozsądne, ale dodaje pewne komplikacje do propozycji. Szczególnie dlatego, że wskaźniki funkcji różniące się konwencją wywoływania instance i managed byłyby niezgodne, mimo że oba przypadki są używane do wywoływania metod zarządzanych z tym samym podpisem języka C#. We wszystkich rozważanych przypadkach, gdzie byłoby to wartościowe, istniało proste obejście: użycie lokalnej funkcji static.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
Nie wymagaj niebezpiecznej deklaracji
Zamiast wymagać unsafe przy każdym użyciu delegate*, wymagaj jej tylko w punkcie, w którym grupa metod jest konwertowana na delegate*. W tym miejscu pojawiają się podstawowe problemy z bezpieczeństwem (wiedząc, że nie można zwolnić zestawu zawierającego, gdy wartość jest aktywna). Wymaganie unsafe w innych lokalizacjach może być postrzegane jako nadmierne.
W ten sposób projekt był pierwotnie zamierzony. Ale wynikające z tego reguły językowe wydawały się bardzo niezręczne. Nie można ukryć faktu, że jest to wartość wskaźnika i ciągle przebijała się nawet bez użycia słowa kluczowego unsafe. Na przykład konwersja na object nie może być dozwolona, nie może być członkiem classitp. Projekt języka C# wymaga unsafe dla wszystkich zastosowań wskaźnika, dlatego ten projekt jest zgodny z tym projektem.
Deweloperzy nadal będą w stanie przedstawić bezpieczne otoki na podstawie delegate* wartości w taki sam sposób, jak w przypadku normalnych typów wskaźników dzisiaj. Rozważ
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Korzystanie z delegatów
Zamiast używać nowego elementu składni, delegate*, po prostu użyj istniejących typów delegate z * po typie:
Func<object, object, bool>* ptr = &object.ReferenceEquals;
Obsługa konwencji wywoływania może być wykonywana przez dodawanie adnotacji do typów delegate za pomocą atrybutu określającego wartość CallingConvention. Brak atrybutu wskazywałby na użycie konwencji wywoływania zarządzanego.
Kodowanie tego w IL jest problematyczne. Wartość bazowa musi być reprezentowana jako wskaźnik, ale musi również:
- Mają unikatowy typ, aby zezwolić na przeciążenia z różnymi typami wskaźnika funkcji.
- Być odpowiednikiem dla celów OHI w granicach modułów.
Ostatni punkt jest szczególnie problematyczny. Oznacza to, że każde zgromadzenie używające Func<int>* musi kodować równoważny typ w metadanych, nawet jeśli Func<int>* jest zdefiniowany w zgromadzeniu, którego nie kontroluje.
Ponadto każdy inny typ, który jest zdefiniowany z nazwą System.Func<T> w zestawie, który nie jest mscorlib, musi być inny niż wersja zdefiniowana w mscorlib.
Jedną z zbadanych opcji było emitowanie takiego wskaźnika, jak mod_req(Func<int>) void*. Nie działa to jednak, ponieważ mod_req nie może się łączyć z TypeSpec, w związku z tym, nie może odnosić się do ogólnych instancji.
Nazwane wskaźniki funkcji
Składnia wskaźnika funkcji może być kłopotliwa, zwłaszcza w skomplikowanych sytuacjach, takich jak zagnieżdżone wskaźniki funkcji. Zamiast kazać deweloperom za każdym razem wpisywać sygnaturę funkcji, język mógłby zezwalać na nazwane deklaracje wskaźników funkcji, podobnie jak w przykładzie delegate.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
Część problemu polega na tym, że podstawowy prymityw interfejsu CLI nie ma nazw, dlatego byłoby to wyłącznie rozwiązanie dla języka C# i wymagałoby nieco pracy z metadanymi, aby to umożliwić. To jest możliwe, ale to znaczna ilość pracy. Zasadniczo język C# wymaga dodatkowego elementu, który wspiera tabelę definicji typów wyłącznie dla tych nazw.
Również w przypadku zbadania argumentów nazwanych wskaźników funkcji okazało się, że mogą one mieć równie dobre zastosowanie do wielu innych scenariuszy. Na przykład równie wygodne byłoby zadeklarowanie nazwanych krotek, aby zmniejszyć konieczność wpisywania pełnej sygnatury we wszystkich przypadkach.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
Po dyskusji postanowiliśmy nie zezwalać na nazwaną deklarację typów delegate*. Jeśli okaże się, że na podstawie opinii klientów istnieje znacząca potrzeba, zbadamy rozwiązanie nazewnictwa, które będzie działać dla wskaźników funkcji, krotek, typów generycznych itp. Prawdopodobnie będzie to podobne do innych sugestii, takich jak pełna obsługa typedef w języku.
Przyszłe zagadnienia
delegaty statyczne
Odnosi się to do propozycji umożliwiającej deklarowanie typów delegate, które mogą odnosić się wyłącznie do członków static. Zaletą jest to, że takie wystąpienia delegate mogą być bez alokacji i lepsze w scenariuszach wrażliwych na wydajność.
Jeśli funkcja wskaźnika funkcji zostanie zaimplementowana, propozycja static delegate prawdopodobnie zostanie zamknięta. Proponowaną zaletą tej funkcji jest swobodny charakter alokacji. Jednak ostatnie badania wykazały, że nie można tego osiągnąć z powodu rozładowania modułu. Musi istnieć silne powiązanie z static delegate do metody, do której się odwołuje, aby zapobiec przedwczesnemu zwolnieniu zestawu.
Aby zachować każde wystąpienie static delegate, konieczne byłoby przydzielenie nowego uchwytu, co jest sprzeczne z celami propozycji. Były pewne projekty, w których alokacja mogła być amortyzowana do pojedynczej alokacji na miejsce wywołania, ale to było nieco złożone i nie wydawało się warte tego poświęcenia.
Oznacza to, że deweloperzy muszą zasadniczo zdecydować między następującymi kompromisami:
- Bezpieczeństwo podczas rozładunku zespołu: wymaga to przydziałów, dlatego
delegatejest już wystarczającą opcją. - Brak bezpieczeństwa przed rozładowaniem zestawu: użyj
delegate*. Można to opakować wstruct, aby zezwolić na użycie poza kontekstemunsafew pozostałej części kodu.
C# feature specifications