Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Jegyzet
Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.
A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.
A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.
Összefoglalás
Ez a javaslat olyan nyelvi szerkezeteket biztosít, amelyek olyan IL-opkódokat fednek le, amelyek jelenleg nem érhetők el hatékonyan vagy egyáltalán nem c# nyelven: ldftn és calli. Ezek az IL-opkódok fontosak lehetnek a nagy teljesítményű kódban, és a fejlesztőknek hatékony módon kell elérniük őket.
Motiváció
A funkció motivációit és hátterét a következő probléma ismerteti (ahogy a funkció lehetséges megvalósítása is):
Ez egy alternatív tervezési javaslat a fordító belső funkciói számára.
Részletes tervezés
Függvénymutatók
A nyelv lehetővé teszi a függvénymutatók deklarálását a delegate* szintaxis használatával. A teljes szintaxist a következő szakaszban ismertetjük részletesen, de az Func és Action típusdeklarációk által használt szintaxishoz hasonlít.
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Ezek a típusok az ECMA-335-ben ismertetett függvénymutató-típussal jelennek meg. Ez azt jelenti, hogy a(z) delegate* meghívása a(z) calli-t fogja használni, ahol a(z) delegate meghívása a(z) callvirt-t fogja használni a(z) Invoke metóduson.
Szinaktikailag azonban a meghívás mindkét szerkezet esetében azonos.
A metódusmutatók ECMA-335-definíciója tartalmazza a hívó konvenciót a típusaláírás részeként (7.1. szakasz).
Az alapértelmezett hívási konvenció managedlesz. A nem felügyelt hívási konvenciók úgy határozhatók meg, hogy egy unmanaged kulcsszót ad a delegate* szintaxisra, amely a futtatókörnyezeti platform alapértelmezett értékét fogja használni. Ezután a unmanaged kulcsszóhoz szögletes zárójelben megadhatók bizonyos nem felügyelt konvenciók a CallConv névtérben System.Runtime.CompilerServices kezdődő típus megadásával, a CallConv előtag elhagyásával. Ezeknek a típusoknak a program alapkönyvtárából kell származnia, és az érvényes kombinációknak platformfüggetlennek kell lenniük.
//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>;
A delegate* típusok közötti átalakítás a szignatúrájuk, beleértve a hívási konvenciót, alapján történik.
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
}
}
A delegate* típusú mutatótípus, amely azt jelenti, hogy rendelkezik a szabványos mutatótípus összes képességével és korlátozásával:
- Csak
unsafekörnyezetben érvényes. - A
delegate*paramétert vagy visszatérési típust tartalmazó metódusok csakunsafekörnyezetből hívhatók meg. - Nem alakítható át
object-ra. - Általános argumentumként nem használható.
- Implicit módon átalakíthatja
delegate*void*. - Explicit módon konvertálható
void*-róldelegate*- ra.
Korlátozások:
- Az egyéni attribútumok nem alkalmazhatók
delegate*-ra vagy bármely elemére. - A
delegate*paraméter nem jelölhető megparams - Egy
delegate*típus a normál mutatótípus összes korlátozásával rendelkezik. - A mutató aritmetikai nem végezhető el közvetlenül a függvénymutató-típusokon.
Függvénymutató szintaxisa
A függvénymutató teljes szintaxisát a következő nyelvhelyesség jelöli:
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'
;
Ha nincs megadva calling_convention_specifier, az alapértelmezett érték managed. A calling_convention_specifier pontos metaadat-kódolása és az, hogy mely identifierérvényesek a unmanaged_calling_convention-ben, a hívási konvenciók metaadat-ábrázolásánálszerepel.
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>>;
Függvénymutatók konvertálása
Nem biztonságos környezetben a rendelkezésre álló implicit konverziók (implicit konverziók) halmaza ki van terjesztve a következő implicit mutatókonverziókra:
- Meglévő átalakítások - (§23.5)
- Egy funcptr_type
F0átalakítása egy másik funcptr_typeF1típusúvá, feltéve, hogy az alábbiak mindegyike igaz:-
F0ésF1ugyanazzal a paraméterszámmal rendelkeznek, és aD0nminden paraméterénekF0ugyanazokkal aref,outvagyinmódosítókkal rendelkezik, mint aD1nmegfelelő paraméterF1. - Az egyes értékparaméterek (
ref,outvagyinmódosító nélküli paraméterek) esetében identitáskonverzió, implicit referenciaátalakítás vagy implicit mutatóátalakítás létezik aF0paramétertípusától aF1megfelelő paramétertípusig. - Minden
ref,outvagyinparaméter esetében aF0paramétertípusa megegyezik azF1megfelelő paramétertípusával. - Ha a visszatérési típus értékként adódik meg (nincs
refvagyref readonly), akkor identitás, implicit hivatkozás vagy implicit mutatókonverzió létezik aF1és aF0visszatérési típus között. - Ha a visszatérési típus referenciaként van megadva (
refvagyref readonly), akkor arefvisszatérési típusa ésF1módosítói megegyeznek arefvisszatérési típusával ésF0módosítóival. - A
F0hívási konvenciója megegyezik aF1hívási konvencióval.
-
Célmeta-metódusok címének engedélyezése
A metóduscsoportok mostantól argumentumként engedélyezve lesznek egy kifejezés címében. Az ilyen kifejezés típusa olyan delegate* lesz, amely a célmetódus és a felügyelt hívási konvenció egyenértékű aláírásával rendelkezik:
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;
}
}
Nem biztonságos környezetben a M metódus kompatibilis egy függvénymutató-típussal F, ha az alábbiak mindegyike igaz:
-
MésFugyanazzal a paraméterszámmal rendelkeznek, és aMminden paramétere ugyanazokkal aref,outvagyinmódosítókkal rendelkezik, mint aFmegfelelő paramétere. - Az egyes értékparaméterek (
ref,outvagyinmódosító nélküli paraméterek) esetében identitáskonverzió, implicit referenciaátalakítás vagy implicit mutatóátalakítás létezik aMparamétertípusától aFmegfelelő paramétertípusig. - Minden
ref,outvagyinparaméter esetében aMparamétertípusa megegyezik azFmegfelelő paramétertípusával. - Ha a visszatérési típus értékként adódik meg (nincs
refvagyref readonly), akkor identitás, implicit hivatkozás vagy implicit mutatókonverzió létezik aFés aMvisszatérési típus között. - Ha a visszatérési típus referenciaként van megadva (
refvagyref readonly), akkor arefvisszatérési típusa ésFmódosítói megegyeznek arefvisszatérési típusával ésMmódosítóival. - A
Mhívási konvenciója megegyezik aFhívási konvencióval. Ez magában foglalja a hívási konvenció bitjét, valamint a nem felügyelt azonosító által megadott hívókonvenciók jelzőit is. -
Mstatikus módszer.
Nem biztonságos környezetben implicit átalakítás áll fenn egy olyan kifejezés címéből, amelynek célja egy metóduscsoport, E egy kompatibilis függvénymutató-típusra F, ha E tartalmaz legalább egy metódust, amely a szokásos formájában alkalmazható a Fparamétertípusai és módosítói által létrehozott argumentumlistára, az alábbiakban leírtak szerint.
- Egyetlen metódus
Mvan kiválasztva, amely megfelel a metódusE(A)hívásának a következő módosításokkal:- Az argumentumok listája
Aegy változóként besorolt kifejezéslista, amely arefmegfelelőouttípusával és módosítójával (in, vagyF) rendelkezik . - A jelölt módszerek csak azok a metódusok, amelyek a normál formájukban alkalmazhatók, nem pedig azok, amelyek kiterjesztett formájukban alkalmazhatók.
- A jelölt metódusok csak statikus metódusok.
- Az argumentumok listája
- Ha a túlterhelés-feloldás algoritmusa hibát okoz, akkor fordítási időhiba lép fel. Ellenkező esetben az algoritmus egyetlen legjobb metódust hoz létre,
Mugyanazzal a számú paraméterrel rendelkezik, mintF, és az átalakítás létezőnek minősül. - A kiválasztott metódusnak
Mkompatibilisnek kell lennie (a fent meghatározottak szerint) a függvénymutató típusávalF. Ellenkező esetben fordítási időhiba lép fel. - Az átalakítás eredménye egy
Ftípusú függvénymutató.
Ez azt jelenti, hogy a fejlesztők a túlterhelés feloldására vonatkozó szabályoktól függhetnek, hogy az operátor címével együtt működjenek:
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;
}
}
Az operátor címe a ldftn utasítással lesz implementálva.
A szolgáltatás korlátozásai:
- Csak a
staticjelölésű metódusokra vonatkozik. - A nem
statichelyi függvények nem használhatók&. Ezeknek a módszereknek a megvalósítási részleteit szándékosan nem határozza meg a nyelv. Ez magában foglalja azt is, hogy statikusak vagy példányok, vagy pontosan milyen aláírással vannak kibocsátva.
Függvénymutató-típusok operátorai
A kifejezések nem biztonságos kódjának szakasza a következőképpen módosul:
Nem biztonságos környezetben számos konstrukció érhető el az összes olyan _pointer_type_s, amely nem _funcptr_type_s:
- A
*operátor használható a mutató közvetett végrehajtására (§23.6.2).- A
->operátorral egy mutatón keresztül érheti el a szerkezet egy tagját (§23.6.3).- A
[]operátor használható mutató indexelésére (§23.6.4).- A
&operátor használható egy változó címének lekérésére (§23.6.5).- A
++és--operátorok használhatók a mutatók növekményére és dekrementálására (§23.6.6).- A
+és-operátorok a mutató aritmetikai végrehajtására használhatók (§23.6.7).- A
==,!=,<,>,<=és=>operátorok használhatók a mutatók összehasonlítására (§23.6.8).- A
stackallocoperátor használható a hívásverem memóriájának lefoglalására (§23.8).- A
fixedutasítás használható a változó ideiglenes rögzítésére, hogy annak címe meghatározható legyen (§23.7).Nem biztonságos környezetben számos konstrukció érhető el az összes _funcptr_type_s kezeléséhez.
- A
&operátor használható a statikus metódusok címének lekéréséhez (A címkezelés engedélyezése a célmetelyek számára)- A
==,!=,<,>,<=és=>operátorok használhatók a mutatók összehasonlítására (§23.6.8).
Emellett az Pointers in expressions összes szakaszát módosítjuk, hogy megtiltsuk a függvénymutató-típusokat, kivéve Pointer comparison és The sizeof operator.
Jobb függvénytag
§12.6.4.3 A jobb függvénytag módosul, hogy a következő sort tartalmazza:
A
delegate*pontosabb, mintvoid*
Ez azt jelenti, hogy túlterhelhetjük a void*-t és a delegate*-et, és továbbra is ésszerűen használhatjuk a cím operátort.
Típuskövetkeztetés
A nem biztonságos kódban a következő módosítások történnek a típuskövető algoritmusokon:
Bemeneti típusok
A rendszer a következőt adja hozzá:
Ha
Emetóduscsoport, ésTfüggvénymutató-típus, akkor aTösszes paramétertípusaEtípusúTbemeneti típusa.
Kimeneti típusok
A rendszer a következőt adja hozzá:
Ha
Emetóduscsoport, ésTfüggvénymutató-típus, akkor aTvisszatérési típusaEtípusúTkimeneti típusa.
Kimeneti típus következtetései
A rendszer a következő listajelet adja hozzá a 2. és a 3. listajel közé:
- Ha
Eegy cím szerinti metóduscsoport, ésTegy függvénymutató-típus, amelynek paramétertípusaiT1...Tkés visszatérési típusaTb, és aEtúlterhelési feloldása aT1..Tktípusokkal egyetlen metódust eredményez a visszatérési típussalU, akkor alsó határú következtetést készítünk aU-ból aTb-be.
Jobb átalakítás a kifejezésből
A következő almenüpontot adják hozzá esetként a 2. listajelhez:
Vegy függvénymutató típus,delegate*<V2..Vk, V1>ésUszinténdelegate*<U2..Uk, U1>függvénymutató típusok, aVhívási konvenciói megegyeznekU-vel, és aVireferenciajellege megegyezikUi-tel.
Alsó határú következtetések
A következő esetet adjuk hozzá a 3. ponthoz:
Vfüggvénymutató típusdelegate*<V2..Vk, V1>, és létezik egy függvénymutató típusdelegate*<U2..Uk, U1>olyan, hogyUmegegyezikdelegate*<U2..Uk, U1>-gyel, aVhívási konvenció megegyezikU-tal, és aVirefnév megegyezikUi-cal.
A Ui-ból Vi-be történő következtetés első pontja módosítva van:
- Ha
Unem funkciómutató típus és nem ismert, hogyUihivatkozás típus-e, vagy haUfunkciómutató típus és nem ismert, hogyUifunkciómutató vagy hivatkozás típus-e, akkor egy pontos következtetést vonunk le alapján.
Ezután a 3. következtetési listajel után adja hozzá a Ui-tól a Vi-ig:
- Ellenkező esetben, ha
Vdelegate*<V2..Vk, V1>akkor a következtetés adelegate*<V2..Vk, V1>i-edik paraméterétől függ:
- V1 esetén:
- Ha a visszatérés érték szerint történik, akkor alsó határú következtetés történik.
- Ha a visszatérés hivatkozással történik, akkor pontos következtetési történik.
- Ha V2..Vk:
- Ha a paraméter érték szerint van megadva, akkor egy felső korlátos következtetés történik.
- Ha a paraméter hivatkozás alapján van megadva, akkor pontos következtetés történik meg.
Felső határú következtetések
A következő esetet adjuk hozzá a 2. ponthoz:
Ufüggvénymutató-típus,delegate*<U2..Uk, U1>ésVolyan függvénymutató-típusok, amelyek azonosakdelegate*<V2..Vk, V1>-mal,Uhívási konvenciója pedig azonosV-tel, és aUireferenciajellege azonosVi-tel.
A Ui-ból Vi-be történő következtetés első pontja módosítva van:
- Ha
Unem funkciómutató típus és nem ismert, hogyUihivatkozás típus-e, vagy haUfunkciómutató típus és nem ismert, hogyUifunkciómutató vagy hivatkozás típus-e, akkor egy pontos következtetést vonunk le alapján.
Ezután hozzáadva a Ui-tól Vi-ig terjedő következtetések 3. pontja után:
- Ellenkező esetben, ha
Udelegate*<U2..Uk, U1>akkor a következtetés adelegate*<U2..Uk, U1>i-edik paraméterétől függ:
- Ha U1:
- Ha a visszatérés érték szerint történik, akkor felső határú következtetési jön létre.
- Ha a visszatérés hivatkozással történik, akkor pontos következtetési történik.
- Ha U2..Uk:
- Ha a paraméter átadása érték szerint történik, akkor egy alsó határú következtetést vonunk le.
- Ha a paraméter hivatkozás alapján van megadva, akkor pontos következtetés történik meg.
A in, outés ref readonly paraméterek és visszatérési típusok metaadatai
A függvénymutató-aláírások nem rendelkeznek paraméterjelölőkkel, ezért modreqs használatával kódolni kell, hogy a paraméterek és a visszatérési típus in, outvagy ref readonly.
in
A paraméter vagy visszatérési típus ref-megadójára alkalmazott System.Runtime.InteropServices.InAttribute-et újra felhasználjuk modreqformájában, hogy a következőket jelentse:
- Ha paraméter-újrafokozóra alkalmazva van, a rendszer ezt a paramétert
inként kezeli. - Amennyiben a visszatérési típus ref specifikátorra van alkalmazva, a visszatérési típust
ref readonly-ként kezeli.
out
A System.Runtime.InteropServices.OutAttribute-t, modreq-ként alkalmazzuk a paramétertípus referencia specifikátorán, ami azt jelenti, hogy a paraméter egy out paraméter.
Hibák
- Hibás
OutAttribute-t modreqként alkalmazni egy visszatérési típusnál. - Hiba, ha a
InAttributeés aOutAttributeis modreqként van alkalmazva egy paramétertípusra. - Ha bármelyiket modopt használatával adja meg, a rendszer figyelmen kívül hagyja őket.
A hívási konvenciók metaadat-ábrázolása
A hívási konvenciók a metaadatokban egy metódus-aláírásban vannak kódolva az aláírásban lévő CallKind jelölő és az aláírás elején lévő nulla vagy több modoptkombinációjával. Az ECMA-335 jelenleg a következő elemeket deklarálja a CallKind jelzőben:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Ezek közül a C# függvénymutatói az összeset támogatják, kivéve a varargs-et.
Emellett a futtatókörnyezet (és végül a 335-ös verzió) is frissül, hogy új CallKind-t is tartalmazzon az új platformokon. Ez jelenleg nem rendelkezik hivatalos névvel, de ez a dokumentum unmanaged ext-t használ helyőrzőként az új bővíthető hívási konvenció formátumára.
modoptnélkül a unmanaged ext a platform alapértelmezett hívási konvenciója, unmanaged szögletes zárójelek nélkül.
A calling_convention_specifier leképezése CallKind-re
A kihagyott vagy calling_convention_specifierként megadott managed a defaultCallKindképez le. Ez az alapértelmezett CallKind a UnmanagedCallersOnlyattribútummal nem rendelkező metódusok esetében.
A C# 4 speciális azonosítót ismer fel, amelyek az ECMA 335-ből származó, nem felügyelt CallKindadott meglévő CallKindképeznek le. Ahhoz, hogy ez a megfeleltetés megtörténjen, ezeket az azonosítókat önállóan kell megadni, más azonosítók nélkül, és ezt a követelményt a unmanaged_calling_convention-ek specifikációjába kell kódolni. Ezek az azonosítók Cdecl, Thiscall, Stdcallés Fastcall, amelyek unmanaged cdecl, unmanaged thiscall, unmanaged stdcallés unmanaged fastcallfelelnek meg. Ha több identifer van megadva, vagy az egyetlen identifier nem a speciálisan felismert azonosítók közé tartozik, speciális névkeresést hajtunk végre az azonosítón az alábbi szabályokkal:
- A
identifiersztringet aCallConvelé helyezzük. - Csak a
System.Runtime.CompilerServicesnévtérben definiált típusokat tekintjük meg. - Csak az alkalmazás alapvető kódtárában definiált típusokat tekintjük át, amely az
System.Objectdefiniáló és függőségeket nem tartalmazó kódtár. - Csak a nyilvános típusokat tekintjük meg.
Ha a keresés sikeres az identifier-ben megadott összes unmanaged_calling_convention-ra, a CallKind-t unmanaged ext-ként kódoljuk, és a függvénymutató szignatúra elején található modopt-s halmaz minden feloldott típusát kódoljuk. Mint megjegyzés, ezek a szabályok azt jelentik, hogy a felhasználók nem használhatják előtagként ezeket a identifier-ket a CallConv-el, mert az a CallConvCallConvVectorCallkereséséhez vezet.
A metaadatok értelmezésekor először a CallKindtekintjük meg. Ha nem unmanaged ext, a hívási konvenció meghatározása céljából figyelmen kívül hagyjuk a visszatérési típus összes modopt, és csak a CallKindhasználjuk. Ha CallKind megegyezik unmanaged ext-gyel, megnézzük a függvénymutató típusának elején található modoptokat, és az alábbi követelményeknek megfelelő összes típus egyesítését végezzük el.
- A definíció az alapvető kódtárban van megadva, amely az a kódtár, amely nem hivatkozik más kódtárakra, és
System.Objectdefiniál. - A típus a
System.Runtime.CompilerServicesnévtérben van definiálva. - A típus a
CallConvelőtaggal kezdődik. - A típus nyilvános.
Ezek azok a típusok, amelyeket meg kell találni a identifierelemek keresésekor egy unmanaged_calling_convention-ben, amikor a függvénymutató típust definiáljuk a forrásban.
Hibát követünk el, ha megpróbálunk egy CallKindunmanaged ext függvénymutatót használni, amikor a cél futtatókörnyezet nem támogatja ezt a jellemzőt. Ezt a System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind állandó jelenlétének keresése határozza meg. Ha ez az állandó jelen van, a futtatókörnyezet támogatja a funkciót.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute a CLR által használt attribútum, amely azt jelzi, hogy egy metódust egy adott hívási konvencióval kell meghívni. Emiatt a következő támogatást vezetjük be az attribútummal való munkához:
- Hiba közvetlenül meghívni egy, ezzel az attribútummal jegyzett metódust a C#-ból. A felhasználóknak be kell szereznie egy függvénymutatót a metódushoz, majd meg kell hívniuk a mutatót.
- Hiba az attribútum alkalmazása a szokásos statikus módszeren vagy a szokásos statikus helyi függvényen kívül bármire. A C#-fordító a metaadatokból importált nem statikus vagy statikus nem szokásos metódusokat a nyelv által nem támogatottként jelöli meg.
- Hiba, ha egy attribútummal megjelölt metódus olyan paraméterrel vagy visszatérési típussal rendelkezik, amely nem
unmanaged_type. - Hiba, ha egy attribútummal jelölt metódus típusparaméterekkel rendelkezik, még akkor is, ha ezek a típusparaméterek
unmanagedvannak korlátozva. - Hiba, ha egy általános típusú metódust az attribútummal kell megjelölni.
- Hiba az attribútummal megjelölt metódus delegált típussá alakítása.
- Hiba az
UnmanagedCallersOnly.CallConvsolyan típusainak megadása, amelyek nem felelnek meg a metaadatokban szereplő konvenciókmodoptmeghívására vonatkozó követelményeknek.
Egy érvényes UnmanagedCallersOnly attribútummal megjelölt metódus hívási konvenciójának meghatározásakor a fordító a következő ellenőrzéseket hajtja végre a CallConvs tulajdonságban megadott típusokon a hívó konvenció meghatározásához használandó tényleges CallKind és modoptmeghatározásához:
- Ha nincsenek megadva típusok, a
CallKindunmanaged extként kezeli a rendszer, és a függvénymutató típusának elején nincs hívási konvenciómodopts. - Ha egy típus van megadva, és a típus neve
CallConvCdecl,CallConvThiscall,CallConvStdcall, vagyCallConvFastcall, akkor aCallKindunmanaged cdecl,unmanaged thiscall,unmanaged stdcall, vagyunmanaged fastcallnéven lesz kezelve, és a függvénymutató típus elején nincsenekmodopthívási konvenciók. - Ha több típus van megadva, vagy az egyetlen típus nem a fenti speciálisan kihívott típusok egyikének van elnevezve, a
CallKindunmanaged extként lesz kezelve, a függvénymutató-típus elejénmodopts-ként megadott típusok egyesítésével.
A fordító ezután megvizsgálja ezt a hatékony CallKind és modopt gyűjteményt, és normál metaadat-szabályokat használ a függvénymutató típusának végső hívási konvenciójának meghatározásához.
Kérdések megnyitása
Futtatókörnyezet támogatásának észlelése unmanaged ext
https://github.com/dotnet/runtime/issues/38135 nyomon követi a jelző hozzáadását. A felülvizsgálat visszajelzésétől függően vagy a problémában megadott tulajdonságot használjuk, vagy a UnmanagedCallersOnlyAttribute jelenlétét használjuk jelzőként, amely meghatározza, hogy a futtatókörnyezetek támogatják-e unmanaged ext.
Megfontolások
Példánymetódusok engedélyezése
A javaslat kiterjeszthető a példánymódszerek támogatására a EXPLICITTHIS CLI-hívási konvenció (C#-kódban instance elnevezett) előnyeinek kihasználásával. A CLI-függvénymutatók ezen formája a this paramétert a függvénymutató szintaxisának explicit első paramétereként helyezi el.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
Ez jó, de némi bonyodalmat ad a javaslathoz. Különösen azért, mert a hívási konvenció instance és managed által eltérő függvénymutatók nem kompatibilisek, annak ellenére, hogy mindkét esetben ugyanazzal a C#-aláírással rendelkező felügyelt metódusok meghívására szolgálnak. Emellett minden olyan esetben, ahol hasznos lenne, volt egy egyszerű megoldás: használjon egy static helyi függvényt.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
A deklaráláskor ne követelje meg a biztonsági kockázatú kódot.
Ahelyett, hogy a unsafe-ra minden egyes delegate*használatakor szükség lenne, csak akkor szükséges, amikor a metóduscsoportot delegate*-vé alakítják át. Ez az a pont, ahol az alapvető biztonsági kérdések kerülnek előtérbe (tudva, hogy az összetevő szerelvény nem távolítható el, amíg az érték aktív). A unsafe megkövetelése a többi helyen túlzásnak tekinthető.
Eredetileg így tervezték a tervet. De az eredményül kapott nyelvi szabályok nagyon kínosnak tűntek. Lehetetlen elrejteni, hogy ez egy mutatóérték, és a unsafe kulcsszó nélkül is folyamatosan átszivárgott. Például a object konvertálása nem engedélyezett, nem lehet tagja egy classstb. A C#-kialakításhoz minden mutatóhoz unsafe kell megkövetelni, ezért ez a kialakítás ezt követi.
A fejlesztők továbbra is képesek lesznek egy biztonságos burkoló használatára a delegate* értékek fölé, ugyanúgy, mint a normál mutatótípusok esetében. Tekint:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Meghatalmazottak használata
Új szintaxiselem használata helyett delegate*egyszerűen használjon meglévő delegate típust a típust követő *:
Func<object, object, bool>* ptr = &object.ReferenceEquals;
A hívási konvenciók kezelése a delegate-típusok CallingConvention értéket meghatározó attribútummal történő megjegyzésével végezhető el. Az attribútum hiánya a felügyelt hívási konvenciót jelentené.
Az IL-ben való kódolás problémás. A mögöttes értéket mutatóként kell ábrázolni, de a következőket is meg kell tennie:
- Egyedi típussal rendelkezik, amely lehetővé teszi a különböző függvénymutató-típusok túlterhelését.
- Az összeszerelési határok közötti OHI-lehetőségeknek egyenértékűnek kell lenniük.
Az utolsó pont különösen problémás. Ez azt jelenti, hogy minden olyan szerelvénynek, amely Func<int>*-t használ, azonos típusú kódot kell kódolnia a metaadatokban, annak ellenére, hogy a Func<int>* egy másik szerelvényben van definiálva, bár nincs irányításuk alatt.
Ezenkívül minden más típusnak, amelyet a System.Func<T> névvel nem az mscorlib szerelvényben definiálnak, különböznie kell az mscorlibban definiált verziótól.
Az egyik vizsgált lehetőség az volt, hogy olyan mutatót bocsát ki, mint mod_req(Func<int>) void*. Ez azonban nem működik, mivel egy mod_req nem tud TypeSpec-hez hozzákötni, így nem lehet általános példányosításokat célozni.
Elnevezett függvénymutatók
A függvénymutató szintaxisa nehézkes lehet, különösen összetett esetekben, például beágyazott függvénymutatók esetén. Ahelyett, hogy a fejlesztők minden alkalommal beírták volna a függvény szignatúráját, a nyelv lehetővé teszi a függvénymutatók elnevezett deklarációit, ahogyan az delegateis történik.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
A probléma része, hogy az alapul szolgáló cli primitív nem rendelkezik névvel, ezért ez pusztán egy C#-találmány lenne, és egy kis metaadat-munkát igényelne az engedélyezéshez. Ez végrehajtható, de jelentős mennyiségű munka. Ez lényegében megköveteli, hogy a C#-nak legyen társa a típus def táblához pusztán ezekhez a nevekhez.
Az elnevezett függvénymutatók argumentumainak vizsgálatakor azt is megállapítottuk, hogy számos más forgatókönyvre is egyformán alkalmazhatók. Például, ha elnevezett tuplákat deklarálunk, az ugyanolyan kényelmes lenne, mivel minden esetben csökkentheti a teljes szignatúra beírásának szükségességét.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
A vita után úgy döntöttünk, hogy nem engedélyezzük delegate* típusok elnevezett deklarációját. Ha az ügyfél használati visszajelzései alapján jelentős szükség van erre, akkor megvizsgálunk egy elnevezési megoldást, amely függvénymutatókhoz, tuplesekhez, általános eszközökhöz stb. használható. Ez valószínűleg hasonló a többi javaslathoz, például a nyelv teljes typedef támogatásához.
Jövőbeli szempontok
statikus delegátusok
Ez a javaslat , amely lehetővé teszi delegate olyan típusok deklarálását, amelyek csak static tagokra hivatkozhatnak. Ennek az az előnye, hogy az ilyen delegate példányok foglalásmentesek lehetnek, és jobbak a teljesítményérzékeny helyzetekben.
Ha a függvénymutató jellemző implementálásra kerül, a static delegate javaslat valószínűleg lezárásra kerül. Ennek a jellemzőnek a javasolt előnye a foglalásmentesség. A legutóbbi vizsgálatok azonban azt találták, hogy ez nem érhető el a szerelvények kirakodása miatt. A static delegate-nak erős hivatkozási ponttal kell rendelkeznie az általa hivatkozott módszerhez, hogy megakadályozza a szerelvény eltávolítását.
Minden static delegate példány fenntartásához új leírót kell lefoglalni, ami ellenkezőleg működik a javaslat céljaival. Volt néhány terv, ahol a memóriafoglalást egy hívási helyenként egyetlen foglalásra lehetett amortizálni, de ez kissé összetett volt, és úgy tűnt, hogy nem éri meg a kompromisszumot.
Ez azt jelenti, hogy a fejlesztőknek alapvetően a következő kompromisszumok között kell dönteniük:
- Az összeszerelés leszerelésekor fontos a biztonság: ehhez megfelelő erőforrásokra van szükség, így a
delegatemáris megfelelő lehetőség. - Nincs biztonsági óvintézkedés a szerelvények kirakodása során: használjon
delegate*. Ez becsomagolható egystruct-ba, ami lehetővé teszi aunsafekörnyezeten kívüli használatát a kód többi részében.
C# feature specifications