Megosztás a következőn keresztül:


Függvénymutatók

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):

dotnet/csharplang#191

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 unsafe környezetben érvényes.
  • A delegate* paramétert vagy visszatérési típust tartalmazó metódusok csak unsafe kö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ól delegate*- 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ő meg params
  • 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_typeF0 átalakítása egy másik funcptr_typeF1típusúvá, feltéve, hogy az alábbiak mindegyike igaz:
    • F0 és F1 ugyanazzal a paraméterszámmal rendelkeznek, és a D0n minden paraméterének F0 ugyanazokkal a ref, outvagy in módosítókkal rendelkezik, mint a D1nmegfelelő paraméter F1 .
    • Az egyes értékparaméterek (ref, outvagy in módosító nélküli paraméterek) esetében identitáskonverzió, implicit referenciaátalakítás vagy implicit mutatóátalakítás létezik a F0 paramétertípusától a F1megfelelő paramétertípusig.
    • Minden ref, outvagy in paraméter esetében a F0 paramétertípusa megegyezik az F1megfelelő paramétertípusával.
    • Ha a visszatérési típus értékként adódik meg (nincs ref vagy ref readonly), akkor identitás, implicit hivatkozás vagy implicit mutatókonverzió létezik a F1 és a F0visszatérési típus között.
    • Ha a visszatérési típus referenciaként van megadva (ref vagy ref readonly), akkor a ref visszatérési típusa és F1 módosítói megegyeznek a refvisszatérési típusával és F0 módosítóival.
    • A F0 hívási konvenciója megegyezik a F1hí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 és F ugyanazzal a paraméterszámmal rendelkeznek, és a M minden paramétere ugyanazokkal a ref, outvagy in módosítókkal rendelkezik, mint a Fmegfelelő paramétere.
  • Az egyes értékparaméterek (ref, outvagy in módosító nélküli paraméterek) esetében identitáskonverzió, implicit referenciaátalakítás vagy implicit mutatóátalakítás létezik a M paramétertípusától a Fmegfelelő paramétertípusig.
  • Minden ref, outvagy in paraméter esetében a M paramétertípusa megegyezik az Fmegfelelő paramétertípusával.
  • Ha a visszatérési típus értékként adódik meg (nincs ref vagy ref readonly), akkor identitás, implicit hivatkozás vagy implicit mutatókonverzió létezik a F és a Mvisszatérési típus között.
  • Ha a visszatérési típus referenciaként van megadva (ref vagy ref readonly), akkor a ref visszatérési típusa és F módosítói megegyeznek a refvisszatérési típusával és M módosítóival.
  • A M hívási konvenciója megegyezik a Fhí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.
  • M statikus 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 M van kiválasztva, amely megfelel a metódus E(A) hívásának a következő módosításokkal:
    • Az argumentumok listája A egy változóként besorolt kifejezéslista, amely a refmegfelelő out típusával és módosítójával (in, vagy F) 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.
  • 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, M ugyanazzal a számú paraméterrel rendelkezik, mint F, és az átalakítás létezőnek minősül.
  • A kiválasztott metódusnak M kompatibilisnek kell lennie (a fent meghatározottak szerint) a függvénymutató típusával F. 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 nemstatic helyi 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 stackalloc operátor használható a hívásverem memóriájának lefoglalására (§23.8).
  • A fixed utasí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.

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, mint void*

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

§12.6.3.4

A rendszer a következőt adja hozzá:

Ha E metóduscsoport, és T függvénymutató-típus, akkor a T összes paramétertípusa Etípusú T bemeneti típusa.

Kimeneti típusok

§12.6.3.5

A rendszer a következőt adja hozzá:

Ha E metóduscsoport, és T függvénymutató-típus, akkor a T visszatérési típusa Etípusú T kimeneti típusa.

Kimeneti típus következtetései

§12.6.3.7

A rendszer a következő listajelet adja hozzá a 2. és a 3. listajel közé:

  • Ha E egy cím szerinti metóduscsoport, és T egy függvénymutató-típus, amelynek paramétertípusai T1...Tk és visszatérési típusa Tb, és a E túlterhelési feloldása a T1..Tk típusokkal egyetlen metódust eredményez a visszatérési típussal U, akkor alsó határú következtetést készítünk a U-ból a Tb-be.

Jobb átalakítás a kifejezésből

§12.6.4.5

A következő almenüpontot adják hozzá esetként a 2. listajelhez:

  • V egy függvénymutató típus, delegate*<V2..Vk, V1> és U szintén delegate*<U2..Uk, U1>függvénymutató típusok, a V hívási konvenciói megegyeznek U-vel, és a Vi referenciajellege megegyezik Ui-tel.

Alsó határú következtetések

§12.6.3.10

A következő esetet adjuk hozzá a 3. ponthoz:

  • V függvénymutató típus delegate*<V2..Vk, V1>, és létezik egy függvénymutató típus delegate*<U2..Uk, U1> olyan, hogy U megegyezik delegate*<U2..Uk, U1>-gyel, a V hívási konvenció megegyezik U-tal, és a Vi refnév megegyezik Ui-cal.

A Ui-ból Vi-be történő következtetés első pontja módosítva van:

  • Ha U nem funkciómutató típus és nem ismert, hogy Ui hivatkozás típus-e, vagy ha U funkciómutató típus és nem ismert, hogy Ui funkció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 a delegate*<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

§12.6.3.11

A következő esetet adjuk hozzá a 2. ponthoz:

  • U függvénymutató-típus, delegate*<U2..Uk, U1> és V olyan függvénymutató-típusok, amelyek azonosak delegate*<V2..Vk, V1>-mal, U hívási konvenciója pedig azonos V-tel, és a Ui referenciajellege azonos Vi-tel.

A Ui-ból Vi-be történő következtetés első pontja módosítva van:

  • Ha U nem funkciómutató típus és nem ismert, hogy Ui hivatkozás típus-e, vagy ha U funkciómutató típus és nem ismert, hogy Ui funkció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 a delegate*<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 a OutAttribute is 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 identifier sztringet a CallConv elé helyezzük.
  • Csak a System.Runtime.CompilerServices né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.Object definiá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.CompilerServices né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.CallConvs olyan típusainak megadása, amelyek nem felelnek meg a metaadatokban szereplő konvenciók modoptmeghí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, vagy CallConvFastcall, akkor a CallKindunmanaged cdecl, unmanaged thiscall, unmanaged stdcall, vagy unmanaged fastcallnéven lesz kezelve, és a függvénymutató típus elején nincsenek modopthí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én modopts-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:

  1. Egyedi típussal rendelkezik, amely lehetővé teszi a különböző függvénymutató-típusok túlterhelését.
  2. 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:

  1. Az összeszerelés leszerelésekor fontos a biztonság: ehhez megfelelő erőforrásokra van szükség, így a delegate máris megfelelő lehetőség.
  2. Nincs biztonsági óvintézkedés a szerelvények kirakodása során: használjon delegate*. Ez becsomagolható egy struct-ba, ami lehetővé teszi a unsafe környezeten kívüli használatát a kód többi részében.