İş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
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Ö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):
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ı managed
olacaktı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ızcaunsafe
bağlamından çağrılabilir. -
object
olarak dönüştürülemez. - Genel bağımsız değişken olarak kullanılamaz.
-
delegate*
, örtük olarakvoid*
'e dönüştürülebilir. - açıkça
void*
'dendelegate*
dönüştürebilir.
Kısıtlama -ları:
- Özel öznitelikler bir
delegate*
veya öğelerinin herhangi birine uygulanamaz. -
delegate*
parametresiparams
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 managed
olur.
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_type
F0
'den funcptr_typeF1
'e, aşağıdakilerin tümü doğru olduğunda:-
F0
veF1
aynı sayıda parametreye sahiptir veF0
'teki her bir parametreD0n
,F1
'deki ilgili parametreD1n
ile aynıref
,out
veyain
değiştiricilerine sahiptir. - Her değer parametresi için (
ref
,out
veyain
değiştiricisi olmayan bir parametre),F0
içindeki parametre türündenF1
'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
,out
veyain
parametresi için,F0
içindeki parametre türü,F1
içindeki ilgili parametre türüyle aynıdır. - Dönüş türü değere göreyse (
ref
veyaref readonly
olmadan),F1
dönüş türündenF0
dö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
veyaref readonly
),F1
'ün dönüş türü veref
değiştiricileri,F0
'in dönüş türü veref
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
veF
aynı sayıda parametreye sahiptir veM
'daki her parametre,F
'de karşılık gelen parametreyle aynıref
,out
veyain
değiştiricilere sahiptir. - Her değer parametresi için (
ref
,out
veyain
değiştiricisi olmayan bir parametre),M
içindeki parametre türündenF
'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
,out
veyain
parametresi için,M
içindeki parametre türü,F
içindeki ilgili parametre türüyle aynıdır. - Dönüş türü değere dayalıysa (
ref
veyaref readonly
olmadan),F
dönüş türündenM
dö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
veyaref readonly
),F
dönüş türü veref
değiştiricileri,M
dönüş türü veref
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, F
parametre 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öntemM
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 veF
'ya karşılık gelen funcptr_parameter_list türü ve değiştiricisi (ref
,out
veyain
) 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.
- Bağımsız değişkenler listesi
- Aşırı yükleme çözümleme algoritması bir hata üretirse, derleme zamanı hatası oluşur. Aksi takdirde algoritma,
F
ile aynı sayıda parametreye sahipM
tek bir en iyi yöntem üretir ve dönüştürmenin mevcut olduğu kabul edilir. - Seçilen yöntem
M
F
iş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,
F
tü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
static
olarak 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:
&
işleci statik yöntemlerin adresini almak için kullanılabilir (Hedef metotların adresine izin ver)- İşaretçileri karşılaştırmak için
==
,!=
,<
,>
,<=
ve=>
işleçleri kullanılabilir (§23.6.8).
Ayrıca, Pointers in expressions
'da, Pointer comparison
ve The sizeof operator
haricindeki 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
Aşağıdakiler eklenir:
E
bir adresli yöntem grubuysa veT
bir işlev işaretçisi türüyse,T
'nin tüm parametre türleri,T
türünde,E
için giriş türleridir.
Çıkış türleri
Aşağıdakiler eklenir:
E
bir yöntem grubunun adresiyse veT
bir işlev işaretçisi türüyse,T
'nin dönüş türüT
türünde birE
çıkış türüdür.
Çıkış türü çıkarımları
2 ile 3 numaralı maddeler arasına aşağıdaki madde eklenir:
E
, bir yöntem grubunun adresiyse veT
, parametre türleriT1...Tk
ve dönüş türüTb
olan bir işlev gösterici türüyse veT1..Tk
türleriyleE
'ün aşırı yükleme çözümleme süreci,U
dönüş türüne sahip tek bir yöntemi sağlıyorsa,U
'danTb
'a bir alt sınır çıkarımı yapılır.
İfadedeki daha iyi dönüşüm
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,U
delegate*<U2..Uk, U1>
bir işlev işaretçisi türüdür veV
çağırma kuralıU
ile aynıdır veVi
başvuru durumuUi
ile aynıdır.
Alt sınır tabanlı çıkarımlar
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 veU
delegate*<U2..Uk, U1>
ile özdeş veV
çağırma kuralıU
ile aynıdır veVi
Ui
ile aynıdırdelegate*<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 veUi
'in başvuru türü olduğu bilinmiyorsa veyaU
bir işlev işaretçisi türüyse veUi
'ü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 Ui
Vi
eklendi:
- Aksi takdirde,
V
delegate*<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
Madde işareti 2’ye aşağıdaki vaka eklenir:
U
veV
, sırasıyladelegate*<U2..Uk, U1>
vedelegate*<V2..Vk, V1>
ile aynı olan işlev işaretçisi türleridir,U
'ün çağırma kuralıV
ile aynıdır veUi
'nın başvuru durumuVi
ile 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 veUi
başvuru türü olarak bilinmiyorsa veyaU
bir işlev işaretçisi türüyse veUi
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,
U
delegate*<U2..Uk, U1>
ise, sonuç çıkarsamadelegate*<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
, out
ve 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
, out
veya 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
in
olarak değerlendirilir. - Dönüş türü "ref" belirticisine uygulanırsa, dönüş türü
ref readonly
olarak 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 deOutAttribute
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 varargs
dışı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.
modopt
olmadan unmanaged ext
, köşeli ayraçlar olmadan unmanaged
platform varsayılan çağırma kuralıdır.
calling_convention_specifier
'ı CallKind
'e eşleme işlemi
atlanmış veya managed
olarak belirtilen bir calling_convention_specifier
, default
CallKind
ile eşler. Herhangi bir yöntemin UnmanagedCallersOnly
ile 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_convention
belirtimine kodlanır. Bu tanımlayıcılar sırasıyla unmanaged cdecl
, unmanaged thiscall
, unmanaged stdcall
ve unmanaged fastcall
karşılık gelen Cdecl
, Thiscall
, Stdcall
ve 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
dizisiniidentifier
'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_convention
belirtilen tüm identifier
'lerde başarılı olursa, CallKind
'yi unmanaged ext
olarak 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 identifier
'ı CallConv
ile ön ek olarak kullanamayacağı, çünkü bunun CallConvCallConvVectorCall
aramasıyla sonuçlanacağı anlamına gelir.
Meta verilerini yorumlarken ilk olarak CallKind
'a bakarız.
unmanaged ext
dışında bir şey durumunda, çağırma kurallarını belirlerken dönüş türündeki tüm modopt
'leri yoksayarız ve yalnızca CallKind
kullanırız.
CallKind
unmanaged ext
ise, 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, CallKind
unmanaged 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_type
olmayan 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
unmanaged
ile 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 verilerdemodopt
ç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,
CallKind
unmanaged ext
olarak değerlendirilir ve işlev işaretçisi türünün başında çağırma kuralımodopt
yoktur. - Belirtilen bir tür varsa ve bu tür
CallConvCdecl
,CallConvThiscall
,CallConvStdcall
veyaCallConvFastcall
olarak adlandırılırsa,CallKind
sırasıylaunmanaged cdecl
,unmanaged thiscall
,unmanaged stdcall
veyaunmanaged fastcall
olarak değerlendirilir ve işlev işaretçisi türünün başında hiçbir çağırma kuralımodopt
yoktur. - Birden çok tür belirtilirse veya tek tür yukarıdaki özel olarak çağrılan türlerden biri olarak adlandırılmazsa,
CallKind
unmanaged ext
olarak değerlendirilir ve belirtilen türlerin birleşimi işlev işaretçisi türünün başındamodopt
olarak 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:
- Farklı işlev işaretçisi türlerine sahip aşırı yüklemelere izin vermek için benzersiz bir türe sahip olun.
- 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 delegate
ile 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 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:
- Montajın boşaltılması karşısında güvenlik: Bu, kaynak tahsisi gerektirir ve bu nedenle
delegate
yeterli bir seçenektir. - Montaj boşaltması karşısında güvenlik yok:
delegate*
kullanın. Bu, kodun geri kalanında birunsafe
bağlamı dışında kullanıma izin vermek için birstruct
içinde sarmalanabilir.
C# feature specifications