Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Hem yönetilen kod hem Objective-C de çalışma zamanı özel durumları (try/catch/finally yan tümceleri) için desteğe sahiptir.
Ancak, uygulamaları farklıdır; bu da çalışma zamanı kitaplıklarının (Mono çalışma zamanı veya CoreCLR ve Objective-C çalışma zamanı kitaplıkları) özel durumları işlemesi ve ardından başka dillerde yazılmış kodu çalıştırması gerektiğinde sorunlarla karşılaştığı anlamına gelir.
Bu belgede oluşabilecek sorunlar ve olası çözümler açıklanmaktadır.
Ayrıca, farklı senaryoları ve çözümlerini test etmek için kullanılabilecek özel durum hazırlama adlı örnek bir proje içerir.
Sorun
Sorun, bir özel durum oluşturulduğunda ve yığın geri sarma sırasında oluşturulan özel durum türüyle eşleşmeyen bir çerçeveyle karşılaşıldığında oluşur.
Bu sorunun tipik bir örneği, yerel bir API'nin bir Objective-C özel durum oluşturması ve ardından yığın geri sarma işlemi yönetilen bir çerçeveye ulaştığında bu Objective-C özel durumun bir şekilde işlenmesi gerektiğidir.
Eski Xamarin projeleri (pre-.NET) için varsayılan eylem hiçbir şey yapmamaktır.
Yukarıdaki örnek için bu, çalışma zamanının yönetilen çerçeveleri bırakmasına Objective-C izin vermek anlamına gelir. Çalışma zamanı yönetilen çerçevelerin Objective-C nasıl geri sarıldığını bilmediğinden, bu eylem sorunludur; örneğin, bu çerçevede herhangi bir catch veya finally yan tümcesi yürütmez.
Bozuk kod
Aşağıdaki kod örneğini inceleyin:
var dict = new NSMutableDictionary ();
dict.LowlevelSetObject (IntPtr.Zero, IntPtr.Zero);
Bu kod yerel kodda bir Objective-C NSInvalidArgumentException oluşturur:
NSInvalidArgumentException *** setObjectForKey: key cannot be nil
Yığın izlemesi aşağıdakine benzer olacaktır:
0 CoreFoundation __exceptionPreprocess + 194
1 libobjc.A.dylib objc_exception_throw + 52
2 CoreFoundation -[__NSDictionaryM setObject:forKey:] + 1015
3 libobjc.A.dylib objc_msgSend + 102
4 TestApp ObjCRuntime.Messaging.void_objc_msgSend_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
5 TestApp Foundation.NSMutableDictionary.LowlevelSetObject (intptr,intptr)
6 TestApp ExceptionMarshaling.Exceptions.ThrowObjectiveCException ()
0-3 arası çerçeveler yerel çerçevelerdir ve çalışma zamanındaki Objective-C yığın geri sarmalayıcı bu çerçeveleri geri alabilir . Özellikle, herhangi bir Objective-C@catch veya @finally yan tümcesini yürütür.
Ancak, Objective-C yığın geri sarmalayıcısı yönetilen çerçeveleri (4-6 arası çerçeveler) düzgün bir şekilde çözemez : Objective-C yığın geri sarmalayıcı yönetilen çerçeveleri geri alır, ancak yönetilen özel durum mantığı (veya 'finally yan tümceleri gibi catch ) yürütmez.
Bu da genellikle bu özel durumları aşağıdaki şekilde yakalamanın mümkün olmadığı anlamına gelir:
try {
var dict = new NSMutableDictionary ();
dict.LowLevelSetObject (IntPtr.Zero, IntPtr.Zero);
} catch (Exception ex) {
Console.WriteLine (ex);
} finally {
Console.WriteLine ("finally");
}
Bunun nedeni, yığın sarmalayıcının Objective-C yönetilen catch yan tümceyi bilmemesi ve yan tümcesinin finally yürütülmemesidir.
Yukarıdaki kod örneği etkili olduğunda, bunun nedeni işlenmeyen Objective-C özel durumlar konusunda bilgilendirilme yöntemine sahip olmasıdır. Bunun nedeniObjective-C, NSSetUncaughtExceptionHandlerXamarin.iOS ve Xamarin.Mac'in kullandığı ve bu noktada özel Objective-C durumları yönetilen özel durumlara dönüştürmeye çalışmasıdır.
Senaryolar
Senaryo 1 - yönetilen catch işleyicisi ile özel durumları yakalama Objective-C
Aşağıdaki senaryoda, yönetilen catch işleyicileri kullanarak özel durumları yakalamak Objective-C mümkündür:
- Bir Objective-C özel durum oluşturulur.
- Çalışma Objective-C zamanı, özel durumu işleyebilen yerel
@catchbir işleyiciyi arayarak yığını gösterir (ancak geri sarmaz). - Çalışma Objective-C zamanı hiçbir
@catchişleyici bulmaz, çağırırNSGetUncaughtExceptionHandlerve Xamarin.iOS/Xamarin.Mac tarafından yüklenen işleyiciyi çağırır. - Xamarin.iOS/Xamarin.Mac'in işleyicisi özel durumu yönetilen özel duruma dönüştürür Objective-C ve oluşturur. Objective-C Çalışma zamanı yığını geri almadığından (yalnızca yürüdü), geçerli çerçeve özel durumun oluşturulduğu yerle Objective-C aynıdır.
Mono çalışma zamanı çerçeveleri düzgün bir şekilde nasıl geri Objective-C saracaklarını bilmediğinden burada başka bir sorun oluşur.
Xamarin.iOS'un yakalanmamış Objective-C özel durum geri çağırması çağrıldığında yığın şöyledir:
0 libxamarin-debug.dylib exception_handler(exc=name: "NSInvalidArgumentException" - reason: "*** setObjectForKey: key cannot be nil")
1 CoreFoundation __handleUncaughtException + 809
2 libobjc.A.dylib _objc_terminate() + 100
3 libc++abi.dylib std::__terminate(void (*)()) + 14
4 libc++abi.dylib __cxa_throw + 122
5 libobjc.A.dylib objc_exception_throw + 337
6 CoreFoundation -[__NSDictionaryM setObject:forKey:] + 1015
7 libxamarin-debug.dylib xamarin_dyn_objc_msgSend + 102
8 TestApp ObjCRuntime.Messaging.void_objc_msgSend_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
9 TestApp Foundation.NSMutableDictionary.LowlevelSetObject (intptr,intptr) [0x00000]
10 TestApp ExceptionMarshaling.Exceptions.ThrowObjectiveCException () [0x00013]
Burada tek yönetilen çerçeveler 8-10 arası çerçevelerdir, ancak yönetilen özel durum 0 çerçevesinde oluşturulur. Bu, Mono çalışma zamanının 0-7 arası yerel çerçeveleri geri alması gerektiği anlamına gelir ve bu da yukarıda açıklanan soruna eşdeğer bir soruna neden olur: Mono çalışma zamanı yerel çerçeveleri çözse de, herhangi bir Objective-C@catch veya @finally yan tümcesi yürütmez.
Kod örneği:
-(id) setObject: (id) object forKey: (id) key
{
@try {
if (key == nil)
[NSException raise: @"NSInvalidArgumentException"];
} @finally {
NSLog (@"This won't be executed");
}
}
@finally Yan tümcesi yürütülmeyecek çünkü bu çerçeveyi çözen Mono çalışma zamanı bunu bilmiyor.
Bunun bir çeşitlemesi, yönetilen kodda yönetilen bir özel durum oluşturmak ve ardından ilk yönetilen catch yan tümcesine ulaşmak için yerel çerçeveler arasında geri sarma yapmaktır:
class AppDelegate : UIApplicationDelegate {
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
throw new Exception ("An exception");
}
static void Main (string [] args)
{
try {
UIApplication.Main (args, null, typeof (AppDelegate));
} catch (Exception ex) {
Console.WriteLine ("Managed exception caught.");
}
}
}
Yönetilen UIApplication:Main yöntem yerel UIApplicationMain yöntemi çağırır ve ardından iOS yönetilen yöntemi çağırmadan önce çok sayıda yerel kod yürütmesi yapar ve yönetilen AppDelegate:FinishedLaunching özel durum oluştuğunda yığında hala çok sayıda yerel çerçeve bulunur:
0: TestApp ExceptionMarshaling.IOS.AppDelegate:FinishedLaunching (UIKit.UIApplication,Foundation.NSDictionary)
1: TestApp (wrapper runtime-invoke) <Module>:runtime_invoke_bool__this___object_object (object,intptr,intptr,intptr)
2: libmonosgen-2.0.dylib mono_jit_runtime_invoke(method=<unavailable>, obj=<unavailable>, params=<unavailable>, exc=<unavailable>, error=<unavailable>)
3: libmonosgen-2.0.dylib do_runtime_invoke(method=<unavailable>, obj=<unavailable>, params=<unavailable>, exc=<unavailable>, error=<unavailable>)
4: libmonosgen-2.0.dylib mono_runtime_invoke [inlined] mono_runtime_invoke_checked(method=<unavailable>, obj=<unavailable>, params=<unavailable>, error=0xbff45758)
5: libmonosgen-2.0.dylib mono_runtime_invoke(method=<unavailable>, obj=<unavailable>, params=<unavailable>, exc=<unavailable>)
6: libxamarin-debug.dylib xamarin_invoke_trampoline(type=<unavailable>, self=<unavailable>, sel="application:didFinishLaunchingWithOptions:", iterator=<unavailable>), context=<unavailable>)
7: libxamarin-debug.dylib xamarin_arch_trampoline(state=0xbff45ad4)
8: libxamarin-debug.dylib xamarin_i386_common_trampoline
9: UIKit -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]
10: UIKit -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:]
11: UIKit -[UIApplication _runWithMainScene:transitionContext:completion:]
12: UIKit __84-[UIApplication _handleApplicationActivationWithScene:transitionContext:completion:]_block_invoke.3124
13: UIKit -[UIApplication workspaceDidEndTransaction:]
14: FrontBoardServices __37-[FBSWorkspace clientEndTransaction:]_block_invoke_2
15: FrontBoardServices __40-[FBSWorkspace _performDelegateCallOut:]_block_invoke
16: FrontBoardServices __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__
17: FrontBoardServices -[FBSSerialQueue _performNext]
18: FrontBoardServices -[FBSSerialQueue _performNextFromRunLoopSource]
19: FrontBoardServices FBSSerialQueueRunLoopSourceHandler
20: CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
21: CoreFoundation __CFRunLoopDoSources0
22: CoreFoundation __CFRunLoopRun
23: CoreFoundation CFRunLoopRunSpecific
24: CoreFoundation CFRunLoopRunInMode
25: UIKit -[UIApplication _run]
26: UIKit UIApplicationMain
27: TestApp (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
28: TestApp UIKit.UIApplication:Main (string[],intptr,intptr)
29: TestApp UIKit.UIApplication:Main (string[],string,string)
30: TestApp ExceptionMarshaling.IOS.Application:Main (string[])
0-1 ve 27-30 çerçeveleri yönetilirken, aradaki tüm çerçeveler yereldir.
Mono bu çerçeveler arasında gevşerse, or Objective-C@catch @finally yan tümceleri yürütülemez.
Senaryo 2 - özel durumlar yakalanamıyor Objective-C
Aşağıdaki senaryoda, özel durum başka bir şekilde işlendiği için yönetilen catch işleyicileri Objective-C kullanarak özel durumları yakalamak Objective-C mümkün değildir:
- Bir Objective-C özel durum oluşturulur.
- Çalışma Objective-C zamanı, özel durumu işleyebilen yerel
@catchbir işleyiciyi arayarak yığını gösterir (ancak geri sarmaz). - Çalışma Objective-C zamanı bir
@catchişleyici bulur, yığını açar ve işleyiciyi yürütmeye@catchbaşlar.
Bu senaryo genellikle Xamarin.iOS uygulamalarında bulunur çünkü ana iş parçacığında genellikle aşağıdaki gibi kodlar bulunur:
void UIApplicationMain ()
{
@try {
while (true) {
ExecuteRunLoop ();
}
} @catch (NSException *ex) {
NSLog (@"An unhandled exception occured: %@", exc);
abort ();
}
}
Bu, ana iş parçacığında hiçbir zaman işlenmemiş Objective-C bir özel durum olmadığı ve bu nedenle özel durumları yönetilen özel durumlara Objective-C dönüştüren geri çağırmamızın hiçbir zaman çağrılamayacağı anlamına gelir.
Bu, Xamarin.Mac'in desteklediğinden önceki bir macOS sürümünde Xamarin.Mac uygulamalarında hata ayıklama sırasında da yaygındır çünkü hata ayıklayıcıdaki kullanıcı arabirimi nesnelerinin çoğu incelendiğinde yürütme platformunda var olmayan seçicilere karşılık gelen özellikleri getirmeye çalışır (çünkü Xamarin.Mac daha yüksek bir macOS sürümü için destek içerir). Bu tür seçicilerin çağrılması, sonunda işlemin kilitlenmesine neden olan bir NSInvalidArgumentException ("...'ye gönderilen tanınmayan seçici") oluşturur.
Özetlemek gerekirse, işlenmek üzere programlanmamış çalışma zamanının Objective-C veya Mono çalışma zamanının geri sarma çerçevelerini açması kilitlenmeler, bellek sızıntıları ve diğer öngörülemeyen (yanlış) davranış türleri gibi tanımsız davranışlara yol açabilir.
Çözüm
Xamarin.iOS 10 ve Xamarin.Mac 2.10'da, yönetilen-yerel sınırlarda hem yönetilen Objective-C hem de özel durumları yakalama ve bu özel durumu diğer türe dönüştürme desteği ekledik.
Sahte kodda şuna benzer:
[DllImport (Constants.ObjectiveCLibrary)]
static extern void objc_msgSend (IntPtr handle, IntPtr selector);
static void DoSomething (NSObject obj)
{
objc_msgSend (obj.Handle, Selector.GetHandle ("doSomething"));
}
objc_msgSend için P/Invoke durduruldu ve bunun yerine bu kod çağrılır:
void
xamarin_dyn_objc_msgSend (id obj, SEL sel)
{
@try {
objc_msgSend (obj, sel);
} @catch (NSException *ex) {
convert_to_and_throw_managed_exception (ex);
}
}
Ters durum için de benzer bir işlem yapılır (yönetilen özel durumları özel durumlara Objective-C göre hazırlama).
Yönetilen yerel sınırda özel durumları yakalamak maliyetsiz değildir, bu nedenle eski Xamarin projeleri (pre-.NET) için varsayılan olarak her zaman etkinleştirilmez:
- Xamarin.iOS/tvOS: Özel durumların Objective-C kesilmesi simülatörde etkinleştirilir.
- Xamarin.watchOS: Çalışma zamanının Objective-C yönetilen çerçeveleri geri sarmasına izin vermek çöp toplayıcının kafasını karıştıracağından ve yanıt vermemeye veya kilitlenmeye neden olacağı için kesme işlemi her durumda zorlanır.
- Xamarin.Mac: Hata ayıklama derlemeleri için özel durumların kesilmesi Objective-C etkinleştirildi.
.NET'te, yönetilen özel durumları özel durumlara Objective-C göre sıralama varsayılan olarak her zaman etkindir.
Derleme zamanı bayrakları bölümünde, kesme noktasının varsayılan olarak etkinleştirilmediğinde nasıl etkinleştirileceği açıklanır (veya varsayılan olduğunda kesmeyi devre dışı bırakabilirsiniz).
Ekinlikler
Bir özel durum durdurulduktan sonra tetiklenen iki olay vardır: Runtime.MarshalManagedException ve Runtime.MarshalObjectiveCException.
Her iki olay da, oluşan özgün özel durumu (Exceptionözelliği) içeren bir EventArgs nesneye ve özel durumun nasıl sıralanması gerektiğini tanımlayan bir ExceptionMode özelliğe geçirilir.
özelliği, ExceptionMode işleyicide yapılan herhangi bir özel işlemeye göre davranışı değiştirmek için olay işleyicisinde değiştirilebilir. Bir örnek, belirli bir özel durum oluşursa işlemi durdurmak olabilir.
özelliğinin ExceptionMode değiştirilmesi tek olay için geçerlidir, gelecekte kesilen özel durumları etkilemez.
Yönetilen özel durumlar yerel koda sıralandığında aşağıdaki modlar kullanılabilir:
Default: Varsayılan değer platforma göre değişir. Her zamanThrowObjectiveCException.NET'tedir. Eski Xamarin projeleri için, GC'ninThrowObjectiveCExceptionişbirliği modunda (watchOS) veUnwindNativeCodebaşka bir durumda (iOS / watchOS / macOS) olması gerekir. Varsayılan değer gelecekte değişebilir.UnwindNativeCode: Bu, önceki (tanımlanmamış) davranıştır. Bu, GC'yi işbirliği modunda kullanırken (watchOS'taki tek seçenek budur; bu nedenle watchOS'ta geçerli bir seçenek değildir) veya CoreCLR kullanılırken kullanılamaz, ancak eski Xamarin projelerindeki diğer tüm platformlar için varsayılan seçenektir.ThrowObjectiveCException: Yönetilen özel durumu özel Objective-C duruma dönüştürün ve özel durumu atın Objective-C . Bu, eski Xamarin projelerinde .NET ve watchOS'ta varsayılan değerdir.Abort: İşlemi durdurun.Disable: Özel durum kesmeyi devre dışı bırakır, bu nedenle olay işleyicisinde bu değeri ayarlamak mantıklı değildir, ancak olay tetiklendiğinde devre dışı bırakmak için çok geç olur. Her durumda, ayarlanırsa olarakUnwindNativeCodedavranır.
Yönetilen koda özel durumlar sıralandığında Objective-C aşağıdaki modlar kullanılabilir:
Default: Varsayılan değer platforma göre değişir. Her zamanThrowManagedException.NET'tedir. Eski Xamarin projeleri için, GC'ninThrowManagedExceptionişbirliği modunda (watchOS) veUnwindManagedCodebaşka bir durumda (iOS / tvOS / macOS) olması gerekir. Varsayılan değer gelecekte değişebilir.UnwindManagedCode: Bu, önceki (tanımlanmamış) davranıştır. Bu, GC'yi işbirliği modunda kullanırken (watchOS'taki tek geçerli GC modudur; bu nedenle watchOS'ta geçerli bir seçenek değildir) veya CoreCLR kullanılırken kullanılamaz, ancak eski Xamarin projelerindeki diğer tüm platformlar için varsayılan seçenektir.ThrowManagedException: Özel durumu yönetilen özel duruma dönüştürün Objective-C ve yönetilen özel durumu atın. Bu, eski Xamarin projelerinde .NET ve watchOS'ta varsayılan değerdir.Abort: İşlemi durdurun.Disable: Özel durum kesmeyi devre dışı bırakır, bu nedenle olay işleyicisinde bu değeri ayarlamak mantıklı değildir, ancak olay tetiklendiğinde devre dışı bırakmak için çok geç olur. Her durumda ayarlanırsa işlemi durdurur.
Bu nedenle, her özel durum sıralandığında bunu görebilirsiniz:
Runtime.MarshalManagedException += (object sender, MarshalManagedExceptionEventArgs args) =>
{
Console.WriteLine ("Marshaling managed exception");
Console.WriteLine (" Exception: {0}", args.Exception);
Console.WriteLine (" Mode: {0}", args.ExceptionMode);
};
Runtime.MarshalObjectiveCException += (object sender, MarshalObjectiveCExceptionEventArgs args) =>
{
Console.WriteLine ("Marshaling Objective-C exception");
Console.WriteLine (" Exception: {0}", args.Exception);
Console.WriteLine (" Mode: {0}", args.ExceptionMode);
};
Derleme Zamanı Bayrakları
Özel durum kesme özelliğinin etkinleştirilip etkinleştirilmediğini belirleyen ve gerçekleşmesi gereken varsayılan eylemi ayarlayan mtouch (Xamarin.iOS uygulamaları için) ve mmp'ye (Xamarin.Mac uygulamaları için) aşağıdaki seçenekleri geçirmek mümkündür:
--marshal-managed-exceptions=defaultunwindnativecodethrowobjectivecexceptionabortdisable
--marshal-objectivec-exceptions=defaultunwindmanagedcodethrowmanagedexceptionabortdisable
disabledışında, bu değerler ve MarshalObjectiveCException olaylarına ExceptionMode geçirilen MarshalManagedException değerlerle aynıdır.
Bu disable seçenek çoğunlukla kesmeyi devre dışı bırakır, ancak yürütme ek yükü eklemediği zaman özel durumları kesmeye devam ederiz. Hazırlama olayları yine de bu özel durumlar için oluşturulur ve varsayılan mod, yürütülen platform için varsayılan mod olur.
Sınırlamalar
Özel durumları yakalamaya objc_msgSend Objective-C çalışırken yalnızca işlev ailesine yönelik P/Invoke'ları yakalarız. Bu, daha sonra herhangi Objective-C bir özel durum oluşturan başka bir C işlevine P/Invoke işlevinin eski ve tanımsız davranışlarla çalışmaya devam edeceği anlamına gelir (bu gelecekte geliştirilebilir).