Aracılığıyla paylaş


İşlev İşaretçileri

Not

Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.

Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar,ilgili dil tasarım toplantısı (LDM) notlarında yakalanır.

Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek içinbelirtimleri makalesinde bulabilirsiniz.

Özet

Bu teklif, C# dilinde bugün etkin bir şekilde erişilemeyen veya hiç erişilemeyen IL opcode'larını açığa çıkaran dil yapıları sağlar: ldftn ve calli. Bu IL opcode'ları yüksek performanslı kodlarda önemli olabilir ve geliştiricilerin bunlara erişmek için verimli bir yönteme ihtiyacı vardır.

Motivasyon

Bu özelliğin motivasyonları ve arka planı aşağıdaki sorunda açıklanmıştır (özelliğin olası bir uygulaması olduğu gibi):

dotnet/csharplang#191

Bu, derleyici intrinsics için alternatif bir tasarım teklifidir.

Ayrıntılı Tasarım

İşlev işaretçileri

Dil, delegate* söz dizimini kullanarak işlev işaretçilerinin bildirimini sağlar. Söz diziminin tamamı sonraki bölümde ayrıntılı olarak açıklanmıştır, ancak Func ve Action tür bildirimleri tarafından kullanılan söz dizimine benzemesi amaçlanmıştır.

unsafe class Example
{
    void M(Action<int> a, delegate*<int, void> f)
    {
        a(42);
        f(42);
    }
}

Bu türler, ECMA-335'te özetlendiği gibi işlev işaretçisi türü kullanılarak temsil edilir. Bir delegate* çağrılması, Invoke yönteminde bir delegate çağrısı callvirt kullanırken calli kullanacağı anlamına gelir. Sözdizimi açısından çağırmanın her iki yapı için de aynı olduğunu söyleyebiliriz.

Yöntem işaretçilerinin ECMA-335 tanımı, tür imzası (bölüm 7.1) kapsamında çağırma kuralını içerir. Varsayılan çağırma kuralı managedolacaktır. Yönetilmeyen çağrı kuralları, çalışma zamanı platformunun varsayılanını kullanacak şekilde delegate* söz diziminin ardından bir unmanaged anahtar sözcüğü ekleyerek belirtilebilir. Belirli yönetilmeyen kurallar daha sonra System.Runtime.CompilerServices ad alanında CallConv ile başlayan herhangi bir tür belirtilerek unmanaged anahtar sözcüğüne köşeli ayraçlar halinde belirtilebilir ve CallConv ön eki bırakılabilir. Bu türler programın çekirdek kitaplığından gelmelidir ve geçerli birleşimler kümesi platforma bağımlıdır.

//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>;

delegate* türleri arasındaki dönüştürmeler, çağırma kuralı dahil olmak üzere imzalarına göre yapılır.

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
    }
}

delegate* türü, standart bir işaretçi türünün tüm özelliklerine ve kısıtlamalarına sahip olduğu anlamına gelen bir işaretçi türüdür:

  • Yalnızca unsafe bağlamında geçerlidir.
  • delegate* parametresi veya dönüş türü içeren yöntemler yalnızca unsafe bağlamından çağrılabilir.
  • objectolarak dönüştürülemez.
  • Genel bağımsız değişken olarak kullanılamaz.
  • delegate*, örtük olarak void*'e dönüştürülebilir.
  • açıkça void*'den delegate*dönüştürebilir.

Kısıtlama -ları:

  • Özel öznitelikler bir delegate* veya öğelerinin herhangi birine uygulanamaz.
  • delegate* parametresi params olarak işaretlenemez
  • delegate* türü, normal bir işaretçi türünün tüm kısıtlamalarına sahiptir.
  • İşaretçi aritmetiği doğrudan işlev işaretçisi türlerinde gerçekleştirilemez.

İşlev işaretçisi söz dizimi

tam işlev işaretçisi söz dizimi aşağıdaki dil bilgisi ile gösterilir:

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'
    ;

calling_convention_specifier sağlanmadıysa, varsayılan değer managedolur. 'ın kesin meta veri kodlaması ve 'de hangi 'lerin geçerli olduğu,Çağırma Kurallarının Meta Veri Gösterimi bölümünde ele alınmıştır.

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>>;

İşlev işaretçisi dönüştürmeleri

Güvenli olmayan bir bağlamda, kullanılabilir örtük dönüştürmeler (Örtük dönüştürmeler) kümesi aşağıdaki örtük işaretçi dönüştürmelerini içerecek şekilde genişletilir:

  • Var olan dönüştürmeler - (§23.5)
  • funcptr_typeF0'den funcptr_typeF1'e, aşağıdakilerin tümü doğru olduğunda:
    • F0 ve F1 aynı sayıda parametreye sahiptir ve F0'teki her bir parametre D0n, F1'deki ilgili parametre D1n ile aynı ref, outveya in değiştiricilerine sahiptir.
    • Her değer parametresi için (ref, outveya in değiştiricisi olmayan bir parametre), F0 içindeki parametre türünden F1'deki ilgili parametre türüne kimlik dönüştürme, örtük başvuru dönüştürme veya örtük işaretçi dönüştürmesi vardır.
    • her ref, outveya in parametresi için, F0 içindeki parametre türü, F1içindeki ilgili parametre türüyle aynıdır.
    • Dönüş türü değere göreyse (ref veya ref readonlyolmadan), F1 dönüş türünden F0dönüş türüne bir kimlik, örtük başvuru veya örtük işaretçi dönüştürmesi vardır.
    • Dönüş türü referans olarak veriliyorsa (ref veya ref readonly), F1'ün dönüş türü ve ref değiştiricileri, F0'in dönüş türü ve ref değiştiricileriyle aynıdır.
    • F0 çağırma kuralı, F1çağırma kuralıyla aynıdır.

Hedef yöntemlerin adresine izin ver

Yöntem gruplarına artık bir ifadenin adresi için bağımsız değişken olarak izin verilir. Böyle bir ifadenin türü, hedef yöntemin eşdeğer imzasını ve yönetilen çağırma kuralını içeren bir delegate* olacaktır:

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;
   }
}

Güvenli olmayan bir bağlamda M yöntemi, aşağıdakilerin tümü doğruysa F işlev işaretçisi türüyle uyumludur:

  • M ve F aynı sayıda parametreye sahiptir ve M'daki her parametre, F'de karşılık gelen parametreyle aynı ref, outveya in değiştiricilere sahiptir.
  • Her değer parametresi için (ref, outveya in değiştiricisi olmayan bir parametre), M içindeki parametre türünden F'deki ilgili parametre türüne kimlik dönüştürme, örtük başvuru dönüştürme veya örtük işaretçi dönüştürmesi vardır.
  • her ref, outveya in parametresi için, M içindeki parametre türü, Fiçindeki ilgili parametre türüyle aynıdır.
  • Dönüş türü değere dayalıysa (ref veya ref readonlyolmadan), F dönüş türünden Mdönüş türüne bir kimlik dönüşümü, örtük başvuru veya örtük işaretçi dönüştürmesi vardır.
  • Dönüş türü başvuruya göreyse (ref veya ref readonly), F dönüş türü ve ref değiştiricileri, Mdönüş türü ve ref değiştiricileri ile aynıdır.
  • M çağırma kuralı, Fçağırma kuralıyla aynıdır. Bu, hem çağırma kuralı bitini hem de yönetilmeyen tanımlayıcıda belirtilen çağırma kuralı bayraklarını içerir.
  • M statik bir yöntemdir.

Güvenli olmayan bir bağlamda, E bir yöntem grubunu hedefleyen bir adres ifadesinden F uyumlu bir işlev işaretçisi türüne örtük bir dönüştürme vardır, eğer E, aşağıda açıklandığı gibi, Fparametre türleri ve değiştiricileri kullanılarak oluşturulan bir bağımsız değişken listesine normal biçiminde uygulanabilir en az bir yöntem içeriyorsa.

  • Aşağıdaki değişikliklerle form E(A) bir yöntem çağrısına karşılık gelen tek bir yöntem M seçilir:
    • Bağımsız değişkenler listesi A, her biri değişken olarak sınıflandırılmış ifadelerden oluşan bir listedir ve F'ya karşılık gelen funcptr_parameter_list türü ve değiştiricisi (ref, outveya in) belirtilmiştir.
    • Aday yöntemleri yalnızca normal biçimlerinde geçerli olan yöntemlerdir, genişletilmiş biçimlerinde geçerli olan yöntemler değildir.
    • Aday yöntemleri yalnızca statik olan yöntemlerdir.
  • Aşırı yükleme çözümleme algoritması bir hata üretirse, derleme zamanı hatası oluşur. Aksi takdirde algoritma, F ile aynı sayıda parametreye sahip M tek bir en iyi yöntem üretir ve dönüştürmenin mevcut olduğu kabul edilir.
  • Seçilen yöntem MFişlev işaretçisi türüyle uyumlu olmalıdır (yukarıda tanımlandığı gibi). Aksi takdirde derleme zamanı hatası oluşur.
  • Dönüştürmenin sonucu, Ftüründe bir işlev işaretçisidir.

Bu, geliştiricilerin adres-of operatörü ile birlikte çalışması için aşırı yük çözümleme kurallarına güvenebileceği anlamına gelir.

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;
    }
}

İşlecin adresi, ldftn yönergesi kullanılarak uygulanır.

Bu özelliğin kısıtlamaları:

  • Yalnızca staticolarak işaretlenmiş yöntemler için geçerlidir.
  • static olmayan yerel işlevler &içinde kullanılamaz. Bu yöntemlerin uygulama ayrıntıları, dil tarafından kasıtlı olarak belirtilmez. Bu, bunların statik mi yoksa örnekleme mi olduklarını veya tam olarak hangi imzayla yayımlandıklarını da içerir.

Fonksiyon İşaretçisi Türleri Üzerinde İşleçler

İfadelerdeki güvenli olmayan kod bölümü şu şekilde değiştirilir:

Güvenli olmayan bir bağlamda, _funcptr_type_s olmayan tüm _pointer_type_s üzerinde işlem yapmak için çeşitli yapılar kullanılabilir.

  • * işleci işaretçi dolaylı işlemi gerçekleştirmek için kullanılabilir (§23.6.2).
  • -> işleci, bir yapının üyesine bir işaretçi aracılığıyla (§23.6.3) erişmek için kullanılabilir.
  • [] işleci bir işaretçiyi dizine eklemek için kullanılabilir (§23.6.4).
  • & işleci bir değişkenin adresini almak için kullanılabilir (§23.6.5).
  • ++ ve -- işleçleri işaretçileri artırmak ve azaltmak için kullanılabilir (§23.6.6).
  • + ve - işleçleri işaretçi aritmetiği gerçekleştirmek için kullanılabilir (§23.6.7).
  • İşaretçileri karşılaştırmak için ==, !=, <, >, <=ve => işleçleri kullanılabilir (§23.6.8).
  • stackalloc işleci çağrı yığınından bellek ayırmak için kullanılabilir (§23.8).
  • fixed deyimi, adresinin alınabilmesi için değişkeni geçici olarak düzeltmek için kullanılabilir (§23.7).

Güvenli olmayan bir bağlamda, tüm _funcptr_type_s üzerinde birkaç yapı kullanılabilir:

Ayrıca, Pointers in expressions'da, Pointer comparison ve The sizeof operatorharicindeki tüm bölümlerde işlev işaretçisi türlerini yasakladık.

Daha iyi işlev üyesi

§12.6.4.3 Daha iyi işlev üyesi aşağıdaki satırı içerecek şekilde değiştirilecektir:

delegate* void* daha özeldir

Bu, void* ve delegate* üzerinde aşırı yükleme yapmanın ve hala adres-of operatörünü mantıklı bir şekilde kullanmaya devam etmenin mümkün olduğu anlamına gelir.

Tür Çıkarımı

Güvenli olmayan kodda tür çıkarım algoritmalarında aşağıdaki değişiklikler yapılır:

Giriş türleri

§12.6.3.4

Aşağıdakiler eklenir:

E bir adresli yöntem grubuysa ve T bir işlev işaretçisi türüyse, T'nin tüm parametre türleri, Ttüründe, E için giriş türleridir.

Çıkış türleri

§12.6.3.5

Aşağıdakiler eklenir:

E bir yöntem grubunun adresiyse ve T bir işlev işaretçisi türüyse, T'nin dönüş türü Ttüründe bir E çıkış türüdür.

Çıkış türü çıkarımları

§12.6.3.7

2 ile 3 numaralı maddeler arasına aşağıdaki madde eklenir:

  • E, bir yöntem grubunun adresiyse ve T, parametre türleri T1...Tk ve dönüş türü Tbolan bir işlev gösterici türüyse ve T1..Tk türleriyle E'ün aşırı yükleme çözümleme süreci, Udönüş türüne sahip tek bir yöntemi sağlıyorsa, U'dan Tb'a bir alt sınır çıkarımı yapılır.

İfadedeki daha iyi dönüşüm

§12.6.4.5

Madde işareti 2'ye vaka olarak aşağıdaki alt madde işareti eklenir:

  • V delegate*<V2..Vk, V1> bir işlev işaretçisi türüdür, Udelegate*<U2..Uk, U1>bir işlev işaretçisi türüdür ve V çağırma kuralı Uile aynıdır ve Vi başvuru durumu Uiile aynıdır.

Alt sınır tabanlı çıkarımlar

§12.6.3.10

Madde işareti 3'e aşağıdaki durum eklenir:

  • V, delegate*<V2..Vk, V1> bir işlev işaretçisi türüdür ve Udelegate*<U2..Uk, U1>ile özdeş ve V çağırma kuralı Uile aynıdır ve ViUiile aynıdır delegate*<U2..Uk, U1> işlev işaretçisi türü vardır.

Ui ile Vi arasındaki çıkarsamaların ilk madde işareti şu şekilde değiştirilmiştir:

  • U bir işlev işaretçisi türü değilse ve Ui'in başvuru türü olduğu bilinmiyorsa veya U bir işlev işaretçisi türüyse ve Ui'ün işlev işaretçisi türü ya da başvuru türü olduğu bilinmiyorsa, kesin çıkarım yapılır

Ardından, 3. çıkarım madde işaretinden sonra UiVieklendi:

  • Aksi takdirde, Vdelegate*<V2..Vk, V1> ise çıkarsama, delegate*<V2..Vk, V1>'nin i. parametresine bağlıdır.
    • V1 ise:
      • Eğer dönüş değer ile yapılırsa, alt sınır çıkarımı yapılır.
      • Referansla dönüş yapılıyorsa, tam çıkarım yapılır.
    • "Eğer V2..Vk ise:"
      • Parametre değere göre veriliyorsa, üst sınır çıkarımı yapılır.
      • Parametre başvuru olarak belirtilmişse, tam çıkarım yapılır.

Üst sınır tahminleri

§12.6.3.11

Madde işareti 2’ye aşağıdaki vaka eklenir:

  • U ve V, sırasıyla delegate*<U2..Uk, U1> ve delegate*<V2..Vk, V1>ile aynı olan işlev işaretçisi türleridir, U'ün çağırma kuralı Vile aynıdır ve Ui'nın başvuru durumu Viile aynıdır.

Ui ile Vi arasındaki ilk çıkarım maddesi şu şekilde değiştirilir:

  • U bir işlev işaretçisi türü değilse ve Ui başvuru türü olarak bilinmiyorsa veya U bir işlev işaretçisi türüyse ve Ui işlev işaretçisi türü veya başvuru türü olarak bilinmiyorsa, tam bir çıkarımı yapılır

Ardından, Ui'den Vi'a çıkarımın 3. madde işaretinden sonra eklenir:

  • Aksi takdirde, Udelegate*<U2..Uk, U1> ise, sonuç çıkarsama delegate*<U2..Uk, U1>'nin i. parametresine bağlıdır:
    • U1 ise:
      • Dönüş değere göreyse, bir üst sınır çıkarımı yapılır.
      • Referansla dönüş yapılırsa, tam çıkarım yapılır.
    • Eğer U2..Uk:
      • Parametre değere göreyse, alt sınır çıkarım yapılır.
      • Parametre başvuruya göreyse, tam çıkarım yapılır.

in, outve ref readonly parametrelerinin ve dönüş türlerinin meta veri gösterimi

İşlev işaretçisi imzalarının parametre bayrakları konumu yoktur, bu nedenle parametrelerin ve dönüş türünün modreqs kullanarak in, outveya ref readonly olup olmadığını kodlamamız gerekir.

in

System.Runtime.InteropServices.InAttribute'ı, bir parametre veya dönüş türündeki bağlaç tanımlayıcısına modreq olarak uygulayarak şu anlama gelmesi için yeniden kullanırız:

  • Bir parametre başvuru belirticisine uygulanırsa, bu parametre inolarak değerlendirilir.
  • Dönüş türü "ref" belirticisine uygulanırsa, dönüş türü ref readonlyolarak değerlendirilir.

out

Parametre türündeki ref belirticisine modreq olarak uygulanan System.Runtime.InteropServices.OutAttribute'ı, parametrenin bir out parametresi olduğunu belirtmek için kullanırız.

Hata

  • bir dönüş türüne modreq olarak OutAttribute uygulamak bir hatadır.
  • Parametre türüne modreq olarak hem InAttribute hem de OutAttribute uygulanması bir hatadır.
  • Modopt aracılığıyla herhangi biri belirtilirse, yoksayılır.

Çağırma Kurallarının Meta Veri Gösterimi

Çağırma kuralları, meta verilerdeki yöntem imzasında, imzanın içindeki CallKind bayrağı ve imzanın başındaki sıfır veya daha fazla modopt'in birleşimiyle kodlanır. ECMA-335 şu anda CallKind bayrağında aşağıdaki öğeleri bildirir:

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

Bunlardan, C# içindeki işlev işaretçileri varargsdışında tümünü destekleyecektir.

Ayrıca, çalışma süreci (ve en sonunda 335), yeni platformlara yeni bir CallKind eklemek üzere güncelleştirilecektir. Bu, şu anda resmi bir adı yoktur, ancak bu belge yeni genişletilebilir çağrı kuralı biçimi için yer tutucu olarak unmanaged ext kullanacaktır. modoptolmadan unmanaged ext, köşeli ayraçlar olmadan unmanaged platform varsayılan çağırma kuralıdır.

calling_convention_specifierCallKind'e eşleme işlemi

atlanmış veya managedolarak belirtilen bir calling_convention_specifier, defaultCallKindile eşler. Herhangi bir yöntemin UnmanagedCallersOnlyile ilişkilendirilmediğinde varsayılan durumu CallKind'dur.

C#, ECMA 335'te tanımlı ve belirli mevcut yönetilmeyen CallKind'lara eşlenen 4 özel tanımlayıcıyı tanır. Bu eşlemenin gerçekleşmesi için, bu tanımlayıcıların başka tanımlayıcı olmadan kendi başlarına belirtilmesi gerekir ve bu gereksinim unmanaged_calling_conventionbelirtimine kodlanır. Bu tanımlayıcılar sırasıyla unmanaged cdecl, unmanaged thiscall, unmanaged stdcallve unmanaged fastcallkarşılık gelen Cdecl, Thiscall, Stdcallve Fastcall' dır. Birden fazla identifer belirtilirse veya tek identifier özel olarak tanınan tanımlayıcılardan değilse, tanımlayıcıda aşağıdaki kurallarla özel ad araması yaparız:

  • CallConv dizisini identifier'nın önüne ekliyoruz
  • Yalnızca System.Runtime.CompilerServices ad alanında tanımlanan türlere bakıyoruz.
  • Yalnızca uygulamanın System.Object tanımlayan ve hiçbir bağımlılığı olmayan çekirdek kitaplığında tanımlanan türlere bakıyoruz.
  • Yalnızca genel türlere bakıyoruz.

Arama, bir unmanaged_calling_conventionbelirtilen tüm identifier'lerde başarılı olursa, CallKind'yi unmanaged extolarak kodlarız ve çözümlenen türlerin her birini işlev işaretçisi imzasının başındaki modopt'ler kümesinde kodlarız. Not olarak, bu kurallar kullanıcıların bu identifierCallConvile ön ek olarak kullanamayacağı, çünkü bunun CallConvCallConvVectorCallaramasıyla sonuçlanacağı anlamına gelir.

Meta verilerini yorumlarken ilk olarak CallKind'a bakarız. unmanaged extdışında bir şey durumunda, çağırma kurallarını belirlerken dönüş türündeki tüm modopt'leri yoksayarız ve yalnızca CallKindkullanırız. CallKind unmanaged extise, aşağıdaki gereksinimleri karşılayan tüm türlerin birleşimini alarak işlev işaretçisi türünün başında modopt'lara bakarız:

  • Tanım, diğer kitaplıklara referans vermeyen ve System.Object'ı tanımlayan bir kütüphane olan çekirdek kütüphanede tanımlanır.
  • Tür, System.Runtime.CompilerServices ad alanında tanımlanır.
  • Tür, CallConvön ekiyle başlar.
  • Türü geneldir.

Bunlar, kaynakta işlev işaretçisi türü tanımlarken bir unmanaged_calling_convention içinde bir identifierüzerinde yapılan aramada bulunması gereken türleri temsil eder.

Hedef çalışma zamanı özelliği desteklemiyorsa, CallKindunmanaged ext ile işlev işaretçisi kullanmaya çalışmak bir hatadır. Bu, System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind sabitinin varlığı aranarak belirlenir. Bu sabit varsa, çalışma zamanının özelliği desteklediği kabul edilir.

System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute

System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute, bir yöntemin belirli bir çağırma kuralıyla çağrılması gerektiğini belirtmek için CLR tarafından kullanılan bir özniteliktir. Bu nedenle özniteliğiyle çalışmak için aşağıdaki desteği sunacağız:

  • C# öğesinden bu öznitelikle açıklama eklenen bir yöntemi doğrudan çağırmak bir hatadır. Kullanıcıların yöntemine bir işlev işaretçisi alması ve ardından bu işaretçiyi çağırması gerekir.
  • Özniteliği sıradan bir statik yöntem veya sıradan statik yerel işlev dışında herhangi bir şeye uygulamak bir hatadır. C# derleyicisi bu öznitelikle meta verilerden içeri aktarılan statik olmayan veya statik sıradan olmayan yöntemleri dil tarafından desteklenmeyen olarak işaretler.
  • özniteliğiyle işaretlenmiş bir yöntemin unmanaged_typeolmayan bir parametreye veya dönüş türüne sahip olması hatadır.
  • özniteliğiyle işaretlenmiş bir yöntemin tür parametrelerine sahip olması, bu tür parametreleri unmanagedile kısıtlanmış olsa bile bir hatadır.
  • Genel türdeki bir yöntemin özniteliğiyle işaretlenmesi bir hatadır.
  • Özniteliğiyle işaretlenmiş bir yöntemi temsilci türüne dönüştürmek bir hatadır.
  • UnmanagedCallersOnly.CallConvs için meta verilerde modoptçağırma kuralı gereksinimlerini karşılamayan herhangi bir tür belirtmek bir hatadır.

Geçerli bir UnmanagedCallersOnly özniteliğiyle işaretlenmiş bir yöntemin çağırma kuralını belirlerken, derleyici CallConvs özelliğinde belirtilen türler üzerinde aşağıdaki denetimleri gerçekleştirerek, çağırma kuralını belirlemek için kullanılması gereken etkili CallKind ve modopt'leri belirler:

  • Hiçbir tür belirtilmezse, CallKindunmanaged extolarak değerlendirilir ve işlev işaretçisi türünün başında çağırma kuralı modoptyoktur.
  • Belirtilen bir tür varsa ve bu tür CallConvCdecl, CallConvThiscall, CallConvStdcallveya CallConvFastcallolarak adlandırılırsa, CallKind sırasıyla unmanaged cdecl, unmanaged thiscall, unmanaged stdcallveya unmanaged fastcallolarak değerlendirilir ve işlev işaretçisi türünün başında hiçbir çağırma kuralı modoptyoktur.
  • Birden çok tür belirtilirse veya tek tür yukarıdaki özel olarak çağrılan türlerden biri olarak adlandırılmazsa, CallKindunmanaged extolarak değerlendirilir ve belirtilen türlerin birleşimi işlev işaretçisi türünün başında modoptolarak değerlendirilir.

Derleyici daha sonra bu etkili CallKind ve modopt koleksiyonuna bakar ve işlev işaretçisi türünün son çağrı kuralını belirlemek için normal meta veri kurallarını kullanır.

Açık Sorular

unmanaged ext için çalışma zamanı desteğini algılama

https://github.com/dotnet/runtime/issues/38135 bu bayrağı eklemeyi izler. Gözden geçirmeden gelen geri bildirime bağlı olarak, ya sorunda belirtilen özelliği kullanacak ya da çalışma zamanlarının unmanaged ext'i destekleyip desteklemediğini belirlemek için bayrak olarak UnmanagedCallersOnlyAttribute'ın varlığını kullanacağız.

Hususlar

Örnek yöntemlerine izin ver

Teklif, EXPLICITTHIS CLI çağırma kuralından (C# kodunda instance adlı) yararlanarak örnek yöntemlerini destekleyecek şekilde genişletilebilir. BU CLI işlev işaretçileri biçimi, this parametresini işlev işaretçisi söz diziminin açık bir ilk parametresi olarak koyar.

unsafe class Instance {
    void Use() {
        delegate* instance<Instance, string> f = &ToString;
        f(this);
    }
}

Bu mantıklıdır ancak teklifi biraz daha karmaşık hale getirir. Çağrı kuralı instance ve managed nedeniyle farklı olan işlev işaretçileri, her ne kadar her iki durumda da aynı C# imzasına sahip yönetilen yöntemleri çağırmak için kullanılsa da uyumsuz olacaktır. Ayrıca, bunun değerli olacağı düşünülen her durumda basit bir alternatif vardı: bir static yerel işlevi kullanmak.

unsafe class Instance {
    void Use() {
        static string toString(Instance i) => i.ToString();
        delegate*<Instance, string> f = &toString;
        f(this);
    }
}

Deklarasyonda güvensizi zorunlu tutmayın

Bir delegate*her kullanımında unsafe gerektirmek yerine, yalnızca bir yöntem grubunun delegate*'ye dönüştürüldüğü noktada gerektirilsin. Çekirdek güvenlik sorunlarının devreye girdiği yer burasıdır (değer canlıyken içeren derlemenin kaldırılamayacağını bilerek). Diğer konumlarda unsafe'un zorunlu olarak tutulması aşırı olarak görülebilir.

Tasarım başlangıçta bu şekilde tasarlanmıştır. Ancak ortaya çıkan dil kuralları çok garip geldi. Bunun bir işaretçi değeri olduğu ve unsafe anahtar sözcüğü olmadan bile ortaya çıktığı gerçeğini gizlemek mümkün değildir. Örneğin object dönüştürmeye izin verilmiyor, classüyesi olamaz. C# tasarımı, tüm işaretçi kullanımları için unsafe gerektirir ve bu nedenle bu tasarım bunu izler.

Geliştiriciler, güvenli sarmalayıcıyı delegate* değerlerin üzerinde bugün normal işaretçi türlerinde olduğu gibi sunabilecek. Göz önünde bulundur:

unsafe struct Action {
    delegate*<void> _ptr;

    Action(delegate*<void> ptr) => _ptr = ptr;
    public void Invoke() => _ptr();
}

Temsilcileri kullanma

Yeni bir söz dizimi öğesi olan delegate*'ü kullanmak yerine, var olan delegate türlerini, türü izleyen bir * ile kullanın.

Func<object, object, bool>* ptr = &object.ReferenceEquals;

Çağırma kuralının işlenmesi, CallingConvention değeri belirten bir öznitelikle delegate türlerine ek açıklama eklenerek yapılabilir. Özniteliğin bulunmaması, yönetilen çağrı kuralını temsil eder.

Bunu IL'de kodlamak sorunludur. Temel alınan değerin bir işaretçi olarak temsil edilmesi gerekir, aynı zamanda aşağıdakilere de uymalıdır:

  1. Farklı işlev işaretçisi türlerine sahip aşırı yüklemelere izin vermek için benzersiz bir türe sahip olun.
  2. Bütünleştirilmiş kod sınırları boyunca OHI amaçlarına eşdeğer olmalıdır.

Son nokta özellikle sorunlu. Bu, Func<int>* kullanan her derlemenin, Func<int>* bir derlemede tanımlansa bile meta verilerde eşdeğer bir tür kodlaması gerektiği anlamına gelir ancak bunu denetlemez. Ayrıca, mscorlib olmayan bir derlemede System.Func<T> adıyla tanımlanan diğer tüm türlerin mscorlib'de tanımlanan sürümden farklı olması gerekir.

Keşfedilen seçeneklerden biri, mod_req(Func<int>) void*gibi bir işaretçi yaymaydı. Bir mod_req'ın bir TypeSpec'e bağlanamaması durumu, genel örneklemeleri hedef almadığı için işe yaramaz.

Adlandırılmış işlev işaretçileri

İşlev işaretçisi söz dizimi, özellikle iç içe yerleştirilmiş işlev işaretçileri gibi karmaşık durumlarda hantal olabilir. Geliştiricilerin her seferinde imzayı yazmak zorunda kalması yerine, dilin delegateile yapıldığı gibi işlev işaretçileri için adlandırılmış bildirimlere izin verebilmesi.

func* void Action();

unsafe class NamedExample {
    void M(Action a) {
        a();
    }
}

Buradaki sorunun bir kısmı, temel alınan CLI temel öğesinin adları olmamasıdır, bu nedenle bu yalnızca bir C# buluşu olur ve etkinleştirmek için biraz meta veri çalışması gerektirir. Bu yapılabilir bir işlemdir ancak işle ilgili önemli bir konudur. C# dilinin, yalnızca bu adlar için tür def tablosuna eşlik eden bir şeyinin olması gerektiğini gerektirir.

Ayrıca, isimlendirilmiş fonksiyon işaretçileri için argümanlar incelendiğinde, bunların birçok diğer senaryoya aynı derecede etkili bir şekilde uygulanabileceğini bulduk. Örneğin, tüm durumlarda tam imzayı yazma gereksinimini azaltmak için adlı tupları tanımlamak da kullanışlı olabilir.

(int x, int y) Point;

class NamedTupleExample {
    void M(Point p) {
        Console.WriteLine(p.x);
    }
}

Tartışmadan sonra delegate* türlerinin adlandırılmış bildirimine izin vermemeye karar verdik. Müşteri kullanımı geri bildirimlerine dayalı olarak buna önemli bir ihtiyaç olduğunu tespit ettiğimizde işlev işaretçileri, tanımlama grupları, genel değerler vb. için çalışan bir adlandırma çözümünü araştıracağız. Bu, büyük olasılıkla dildeki tam typedef desteği gibi diğer önerilere benzer olacaktır.

Gelecekte Dikkat Edilmesi Gerekenler

statik temsilciler

Yalnızca üyelerine başvurabilen türlerinin bildirilmesine izin verilmesini sağlayan öneri'in anlamına geldiğini belirtir. Bu tür delegate örneklerinin, bellek ayırmaya gerek kalmadan ve performansa duyarlı senaryolarda daha iyi performans göstermesinin avantajıdır.

Fonksiyon işaretleyicisi özelliği uygulanırsa, static delegate teklifi büyük olasılıkla kapanır. Bu özelliğin önerilen avantajı, bellek ayırımı gerektirmeyen yapısıdır. Ancak yakın zamanda yapılan araştırmalar, derleme bileşenlerinin kaldırılması nedeniyle bunun mümkün olmadığını buldu. Derlemenin kaldırılmasını önlemek için static delegate'ın başvurduğu yönteme güçlü bir işaretçi olmalıdır.

Her static delegate örneğini korumak için, teklifin hedeflerine ters düşen yeni bir tanıtıcı tahsis edilmesi gerekir. Ayırmanın çağrı sitesi başına tek bir ayırmaya amorti edilebileceği ancak biraz karmaşık olduğu ve takasa değer görünmediği bazı tasarımlar vardı.

Bu, geliştiricilerin temelde aşağıdaki ticari dezavantajlar arasında karar vermek zorunda olduğu anlamına gelir:

  1. Montajın boşaltılması karşısında güvenlik: Bu, kaynak tahsisi gerektirir ve bu nedenle delegate yeterli bir seçenektir.
  2. Montaj boşaltması karşısında güvenlik yok: delegate*kullanın. Bu, kodun geri kalanında bir unsafe bağlamı dışında kullanıma izin vermek için bir struct içinde sarmalanabilir.