Xamarin.iOS ve Xamarin.Mac'te Özel Durum Hazırlama
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, NSSetUncaughtExceptionHandler
Xamarin.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
@catch
bir işleyiciyi arayarak yığını gösterir (ancak geri sarmaz). - Çalışma Objective-C zamanı hiçbir
@catch
işleyici bulmaz, çağırırNSGetUncaughtExceptionHandler
ve 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
@catch
bir işleyiciyi arayarak yığını gösterir (ancak geri sarmaz). - Çalışma Objective-C zamanı bir
@catch
işleyici bulur, yığını açar ve işleyiciyi yürütmeye@catch
baş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'ninThrowObjectiveCException
işbirliği modunda (watchOS) veUnwindNativeCode
baş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 olarakUnwindNativeCode
davranı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'ninThrowManagedException
işbirliği modunda (watchOS) veUnwindManagedCode
baş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=
default
unwindnativecode
throwobjectivecexception
abort
disable
--marshal-objectivec-exceptions=
default
unwindmanagedcode
throwmanagedexception
abort
disable
disable
dışı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).