3254 строки
104 KiB
Objective-C
3254 строки
104 KiB
Objective-C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* Authors: Rolf Bjarne Kvinge
|
|
*
|
|
* Copyright (C) 2014 Xamarin Inc. (www.xamarin.com)
|
|
*
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
#include <objc/runtime.h>
|
|
#include <sys/stat.h>
|
|
#include <dlfcn.h>
|
|
|
|
#include "product.h"
|
|
#include "shared.h"
|
|
#include "delegates.h"
|
|
#include "runtime-internal.h"
|
|
#include "xamarin/xamarin.h"
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
#include "xamarin/monovm-bridge.h"
|
|
#else
|
|
#include "xamarin/coreclr-bridge.h"
|
|
#endif
|
|
|
|
#if defined (DEBUG)
|
|
//extern BOOL NSZombieEnabled;
|
|
#endif
|
|
|
|
/*
|
|
* These are values that can be configured in xamarin_setup.
|
|
*
|
|
* The defaults values here have been chosen to minimize
|
|
* the configuration required for the simulator (in particular
|
|
* the simlauncher binaries).
|
|
*/
|
|
|
|
#if DEBUG
|
|
bool xamarin_gc_pump = false;
|
|
#endif
|
|
#if MONOMAC
|
|
// FIXME: implement release mode for monomac.
|
|
bool xamarin_debug_mode = true;
|
|
bool xamarin_mac_hybrid_aot = false;
|
|
bool xamarin_mac_modern = false;
|
|
#else
|
|
bool xamarin_debug_mode = false;
|
|
#endif
|
|
bool xamarin_disable_lldb_attach = false;
|
|
bool xamarin_disable_omit_fp = false;
|
|
#if DEBUG
|
|
bool xamarin_init_mono_debug = true;
|
|
#else
|
|
bool xamarin_init_mono_debug = false;
|
|
#endif
|
|
int xamarin_log_level = 0;
|
|
const char *xamarin_executable_name = NULL;
|
|
#if DOTNET
|
|
const char *xamarin_icu_dat_file_name = NULL;
|
|
#endif
|
|
#if MONOMAC || TARGET_OS_MACCATALYST
|
|
NSString * xamarin_custom_bundle_name = @"MonoBundle";
|
|
#endif
|
|
#if MONOMAC
|
|
bool xamarin_is_mkbundle = false;
|
|
char *xamarin_entry_assembly_path = NULL;
|
|
#endif
|
|
#if defined (__i386__)
|
|
const char *xamarin_arch_name = "i386";
|
|
#elif defined (__x86_64__)
|
|
const char *xamarin_arch_name = "x86_64";
|
|
#else
|
|
const char *xamarin_arch_name = NULL;
|
|
#endif
|
|
|
|
#if TARGET_OS_WATCH
|
|
bool xamarin_is_gc_coop = true;
|
|
#else
|
|
bool xamarin_is_gc_coop = false;
|
|
#endif
|
|
enum MarshalObjectiveCExceptionMode xamarin_marshal_objectivec_exception_mode = MarshalObjectiveCExceptionModeDefault;
|
|
enum MarshalManagedExceptionMode xamarin_marshal_managed_exception_mode = MarshalManagedExceptionModeDefault;
|
|
enum XamarinTriState xamarin_log_exceptions = XamarinTriStateNone;
|
|
enum XamarinLaunchMode xamarin_launch_mode = XamarinLaunchModeApp;
|
|
bool xamarin_supports_dynamic_registration = true;
|
|
const char *xamarin_runtime_configuration_name = NULL;
|
|
|
|
#if DOTNET
|
|
enum XamarinNativeLinkMode xamarin_libmono_native_link_mode = XamarinNativeLinkModeStaticObject;
|
|
const char **xamarin_runtime_libraries = NULL;
|
|
#endif
|
|
|
|
/* Callbacks */
|
|
|
|
xamarin_setup_callback xamarin_setup = NULL;
|
|
xamarin_register_module_callback xamarin_register_modules = NULL;
|
|
xamarin_register_assemblies_callback xamarin_register_assemblies = NULL;
|
|
xamarin_extension_main_callback xamarin_extension_main = NULL;
|
|
|
|
/* Local variable */
|
|
|
|
static pthread_mutex_t framework_peer_release_lock;
|
|
static MonoGHashTable *xamarin_wrapper_hash;
|
|
|
|
static bool initialize_started = FALSE;
|
|
|
|
#include "delegates.inc"
|
|
|
|
/* Keep Trampolines, InitializationFlags and InitializationOptions in sync with Runtime.cs */
|
|
|
|
struct Trampolines {
|
|
void* tramp;
|
|
void* stret_tramp;
|
|
void* fpret_single_tramp;
|
|
void* fpret_double_tramp;
|
|
void* release_tramp;
|
|
void* retain_tramp;
|
|
void* static_tramp;
|
|
void* ctor_tramp;
|
|
void* x86_double_abi_stret_tramp;
|
|
void* static_fpret_single_tramp;
|
|
void* static_fpret_double_tramp;
|
|
void* static_stret_tramp;
|
|
void* x86_double_abi_static_stret_tramp;
|
|
void* long_tramp;
|
|
void* static_long_tramp;
|
|
#if MONOMAC
|
|
void* copy_with_zone1;
|
|
void* copy_with_zone2;
|
|
#endif
|
|
void* get_gchandle_tramp;
|
|
void* set_gchandle_tramp;
|
|
void* get_flags_tramp;
|
|
void* set_flags_tramp;
|
|
};
|
|
|
|
enum InitializationFlags : int {
|
|
InitializationFlagsIsPartialStaticRegistrar = 0x01,
|
|
/* unused = 0x02,*/
|
|
/* unused = 0x04,*/
|
|
/* unused = 0x08,*/
|
|
InitializationFlagsIsSimulator = 0x10,
|
|
InitializationFlagsIsCoreCLR = 0x20,
|
|
};
|
|
|
|
struct InitializationOptions {
|
|
int size; // the size of this structure. This is used for version checking.
|
|
enum InitializationFlags flags;
|
|
struct Delegates* Delegates;
|
|
struct Trampolines* Trampolines;
|
|
struct MTRegistrationMap* RegistrationData;
|
|
enum MarshalObjectiveCExceptionMode MarshalObjectiveCExceptionMode;
|
|
enum MarshalManagedExceptionMode MarshalManagedExceptionMode;
|
|
#if MONOMAC
|
|
enum XamarinLaunchMode LaunchMode;
|
|
const char *EntryAssemblyPath;
|
|
#endif
|
|
struct AssemblyLocations* AssemblyLocations;
|
|
#if DOTNET
|
|
// This struct must be kept in sync with the corresponding struct in Runtime.cs, and since we use the same managed code for both MonoVM and CoreCLR,
|
|
// we can't restrict the following fields to CORECLR_RUNTIME only, we can only exclude it from legacy Xamarin.
|
|
void *xamarin_objc_msgsend;
|
|
void *xamarin_objc_msgsend_super;
|
|
void *xamarin_objc_msgsend_stret;
|
|
void *xamarin_objc_msgsend_super_stret;
|
|
void *unhandled_exception_handler;
|
|
void *reference_tracking_begin_end_callback;
|
|
void *reference_tracking_is_referenced_callback;
|
|
void *reference_tracking_tracked_object_entered_finalization;
|
|
#endif
|
|
};
|
|
|
|
static struct Trampolines trampolines = {
|
|
(void *) &xamarin_trampoline,
|
|
(void *) &xamarin_stret_trampoline,
|
|
(void *) &xamarin_fpret_single_trampoline,
|
|
(void *) &xamarin_fpret_double_trampoline,
|
|
(void *) &xamarin_release_trampoline,
|
|
(void *) &xamarin_retain_trampoline,
|
|
(void *) &xamarin_static_trampoline,
|
|
(void *) &xamarin_ctor_trampoline,
|
|
#if defined (__i386__)
|
|
(void *) &xamarin_x86_double_abi_stret_trampoline,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
(void *) &xamarin_static_fpret_single_trampoline,
|
|
(void *) &xamarin_static_fpret_double_trampoline,
|
|
(void *) &xamarin_static_stret_trampoline,
|
|
#if defined (__i386__)
|
|
(void *) &xamarin_static_x86_double_abi_stret_trampoline,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
(void *) &xamarin_longret_trampoline,
|
|
(void *) &xamarin_static_longret_trampoline,
|
|
#if MONOMAC
|
|
(void *) &xamarin_copyWithZone_trampoline1,
|
|
(void *) &xamarin_copyWithZone_trampoline2,
|
|
#endif
|
|
(void *) &xamarin_get_gchandle_trampoline,
|
|
(void *) &xamarin_set_gchandle_trampoline,
|
|
(void *) &xamarin_get_flags_trampoline,
|
|
(void *) &xamarin_set_flags_trampoline,
|
|
};
|
|
|
|
static struct InitializationOptions options = { 0 };
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
void
|
|
xamarin_add_internal_call (const char *name, const void *method)
|
|
{
|
|
/* COOP: With cooperative GC, icalls will run, like managed methods,
|
|
* in GC Unsafe mode, avoiding a thread state transition. In return
|
|
* the icalls must guarantee that they won't block, or run indefinitely
|
|
* without a safepoint, by manually performing a transition to GC Safe
|
|
* mode. With backward-compatible hybrid GC, icalls run in GC Safe
|
|
* mode and the Mono API functions take care of thread state
|
|
* transitions, so don't need to perform GC thread state transitions
|
|
* themselves.
|
|
*
|
|
*/
|
|
if (xamarin_is_gc_coop)
|
|
mono_dangerous_add_raw_internal_call (name, method);
|
|
else
|
|
mono_add_internal_call (name, method);
|
|
}
|
|
#endif // !CORECLR_RUNTIME
|
|
|
|
id
|
|
xamarin_get_nsobject_handle (MonoObject *obj)
|
|
{
|
|
// COOP: Reading managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
#if defined (CORECLR_RUNTIME)
|
|
id rv = xamarin_get_handle_for_inativeobject (obj);
|
|
LOG_CORECLR (stderr, "xamarin_get_nsobject_handle (%p) => %p\n", obj, rv);
|
|
return rv;
|
|
#else
|
|
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
|
|
return mobj->handle;
|
|
#endif
|
|
}
|
|
|
|
uint8_t
|
|
xamarin_get_nsobject_flags (MonoObject *obj)
|
|
{
|
|
// COOP: Reading managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
#if defined (CORECLR_RUNTIME)
|
|
return xamarin_get_flags_for_nsobject (obj->gchandle);
|
|
#else
|
|
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
|
|
return mobj->flags;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xamarin_set_nsobject_flags (MonoObject *obj, uint8_t flags)
|
|
{
|
|
// COOP: Writing managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
#if defined (CORECLR_RUNTIME)
|
|
xamarin_set_flags_for_nsobject (obj->gchandle, flags);
|
|
#else
|
|
struct Managed_NSObject *mobj = (struct Managed_NSObject *) obj;
|
|
mobj->flags = flags;
|
|
#endif
|
|
}
|
|
|
|
MonoType *
|
|
xamarin_get_parameter_type (MonoMethod *managed_method, int index)
|
|
{
|
|
// COOP: Reading managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoMethodSignature *msig = mono_method_signature (managed_method);
|
|
void *iter = NULL;
|
|
MonoType *p = NULL;
|
|
|
|
if (index == -1) {
|
|
p = mono_signature_get_return_type (msig);
|
|
} else {
|
|
for (int i = 0; i < index + 1; i++) {
|
|
xamarin_mono_object_release (&p);
|
|
p = mono_signature_get_params (msig, &iter);
|
|
}
|
|
}
|
|
|
|
xamarin_bridge_free_mono_signature (&msig);
|
|
|
|
return p;
|
|
}
|
|
|
|
MonoObject *
|
|
xamarin_get_nsobject_with_type_for_ptr (id self, bool owns, MonoType* type, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reading managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
int32_t created;
|
|
return xamarin_get_nsobject_with_type_for_ptr_created (self, owns, type, &created, exception_gchandle);
|
|
}
|
|
|
|
MonoObject *
|
|
xamarin_get_nsobject_with_type_for_ptr_created (id self, bool owns, MonoType *type, int32_t *created, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reading managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoObject *mobj = NULL;
|
|
GCHandle gchandle = INVALID_GCHANDLE;
|
|
|
|
*created = false;
|
|
|
|
if (self == NULL)
|
|
return NULL;
|
|
|
|
gchandle = xamarin_get_gchandle (self);
|
|
|
|
if (gchandle != INVALID_GCHANDLE) {
|
|
mobj = xamarin_gchandle_get_target (gchandle);
|
|
MonoClass *klass = mono_class_from_mono_type (type);
|
|
bool isinst = mono_object_isinst (mobj, klass) != NULL;
|
|
xamarin_mono_object_release (&klass);
|
|
|
|
if (isinst) {
|
|
return mobj;
|
|
} else {
|
|
xamarin_mono_object_release (&mobj);
|
|
}
|
|
}
|
|
|
|
MonoReflectionType *rtype = mono_type_get_object (mono_domain_get (), type);
|
|
MonoObject *rv = xamarin_get_nsobject_with_type (self, rtype, created, exception_gchandle);
|
|
xamarin_mono_object_release (&rtype);
|
|
return rv;
|
|
}
|
|
|
|
MonoObject *
|
|
xamarin_get_managed_object_for_ptr_fast (id self, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reading managed data, must be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoObject *mobj = NULL;
|
|
GCHandle gchandle = INVALID_GCHANDLE;
|
|
|
|
gchandle = xamarin_get_gchandle (self);
|
|
|
|
if (gchandle == INVALID_GCHANDLE) {
|
|
mobj = xamarin_try_get_or_construct_nsobject (self, exception_gchandle);
|
|
} else {
|
|
mobj = xamarin_gchandle_get_target (gchandle);
|
|
#if DEBUG
|
|
if (self != xamarin_get_nsobject_handle (mobj)) {
|
|
xamarin_assertion_message ("Internal consistency error, please file a bug (https://github.com/xamarin/xamarin-macios/issues/new). Additional data: found managed object %p=%p (%s) in native object %p (%s).\n",
|
|
mobj, xamarin_get_nsobject_handle (mobj), xamarin_class_get_full_name (mono_object_get_class (mobj), exception_gchandle), self, object_getClassName (self));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return mobj;
|
|
}
|
|
|
|
// See comments in the following methods to explain the logic here:
|
|
// xamarin_marshal_return_value_impl in trampolines.m
|
|
// xamarin_release_managed_ref in runtime.m
|
|
void xamarin_framework_peer_waypoint ()
|
|
{
|
|
// COOP: CHECK
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MONO_ENTER_GC_SAFE;
|
|
pthread_mutex_lock (&framework_peer_release_lock);
|
|
pthread_mutex_unlock (&framework_peer_release_lock);
|
|
MONO_EXIT_GC_SAFE;
|
|
}
|
|
|
|
// Same as xamarin_framework_peer_waypoint, except the current mode should be GC Safe.
|
|
void xamarin_framework_peer_waypoint_safe ()
|
|
{
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
pthread_mutex_lock (&framework_peer_release_lock);
|
|
pthread_mutex_unlock (&framework_peer_release_lock);
|
|
}
|
|
|
|
MonoObject *
|
|
xamarin_new_nsobject (id self, MonoClass *klass, GCHandle *exception_gchandle)
|
|
{
|
|
MonoType *type = mono_class_get_type (klass);
|
|
MonoReflectionType *rtype = mono_type_get_object (mono_domain_get (), type);
|
|
xamarin_mono_object_release (&type);
|
|
|
|
GCHandle obj = xamarin_create_nsobject (rtype, self, NSObjectFlagsNativeRef, exception_gchandle);
|
|
xamarin_mono_object_release (&rtype);
|
|
return xamarin_gchandle_unwrap (obj);
|
|
}
|
|
|
|
// Returns if a MonoClass is nullable.
|
|
// Will also return the element type (it the type is nullable, and if out pointer is not NULL).
|
|
bool
|
|
xamarin_is_class_nullable (MonoClass *cls, MonoClass **element_type, GCHandle *exception_gchandle)
|
|
{
|
|
#ifdef DYNAMIC_MONO_RUNTIME
|
|
// mono_class_is_nullable/mono_class_get_nullable_param are private
|
|
// functions, and as such we can't call find them in libmono.dylib. In
|
|
// this case we manually call a managed function to do the work for us (we
|
|
// don't use the normal delegate mechanism, because how it's currently
|
|
// implemented it would inflict size costs on all platforms, not just
|
|
// Xamarin.Mac).
|
|
if (!mono_class_is_nullable_exists () || !mono_class_get_nullable_param_exists ()) {
|
|
static MonoMethod *get_nullable_type = NULL;
|
|
|
|
if (get_nullable_type == NULL)
|
|
get_nullable_type = mono_class_get_method_from_name (xamarin_get_runtime_class (), "GetNullableType", 1);
|
|
|
|
GCHandle type_handle = xamarin_gchandle_new ((MonoObject *) mono_type_get_object (mono_domain_get (), mono_class_get_type (cls)), false);
|
|
void *args [1] { type_handle };
|
|
MonoObject *exc = NULL;
|
|
MonoObject *nullable_type_handle = mono_runtime_invoke (get_nullable_type, NULL, args, &exc);
|
|
xamarin_gchandle_free (type_handle);
|
|
if (exc != NULL) {
|
|
*exception_gchandle = xamarin_gchandle_new (exc, FALSE);
|
|
return false;
|
|
}
|
|
|
|
MonoReflectionType *nullable_type = (MonoReflectionType *) xamarin_gchandle_unwrap (nullable_type_handle);
|
|
|
|
if (element_type != NULL && nullable_type != NULL) {
|
|
MonoType *mono_type = mono_reflection_type_get_type (nullable_type);
|
|
*element_type = mono_class_from_mono_type (mono_type);
|
|
xamarin_mono_object_release (&mono_type);
|
|
}
|
|
|
|
bool is_nullable = nullable_type != NULL;
|
|
xamarin_mono_object_release (&nullable_type);
|
|
return is_nullable;
|
|
}
|
|
#endif
|
|
|
|
bool rv = mono_class_is_nullable (cls);
|
|
if (rv && element_type)
|
|
*element_type = mono_class_get_nullable_param (cls);
|
|
return rv;
|
|
}
|
|
|
|
MonoClass *
|
|
xamarin_get_nullable_type (MonoClass *cls, GCHandle *exception_gchandle)
|
|
{
|
|
MonoClass *rv = NULL;
|
|
xamarin_is_class_nullable (cls, &rv, exception_gchandle);
|
|
return rv;
|
|
}
|
|
|
|
// The XamarinExtendedObject protocol is just to avoid a
|
|
// compiler warning (no 'xamarinGetGChandle' selector found).
|
|
@protocol XamarinExtendedObject
|
|
-(GCHandle) xamarinGetGCHandle;
|
|
-(bool) xamarinSetGCHandle: (GCHandle) gc_handle flags: (enum XamarinGCHandleFlags) flags;
|
|
-(enum XamarinGCHandleFlags) xamarinGetFlags;
|
|
-(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
|
|
@end
|
|
|
|
static inline GCHandle
|
|
get_gchandle_safe (id self, enum XamarinGCHandleFlags *flags)
|
|
{
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
id<XamarinExtendedObject> xself = self;
|
|
GCHandle rv = [xself xamarinGetGCHandle];
|
|
if (flags)
|
|
*flags = [xself xamarinGetFlags];
|
|
return rv;
|
|
}
|
|
|
|
static inline bool
|
|
set_gchandle (id self, GCHandle gc_handle, enum XamarinGCHandleFlags flags)
|
|
{
|
|
bool rv;
|
|
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MONO_ENTER_GC_SAFE;
|
|
id<XamarinExtendedObject> xself = self;
|
|
rv = [xself xamarinSetGCHandle: gc_handle flags: flags];
|
|
MONO_EXIT_GC_SAFE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static inline bool
|
|
set_gchandle_safe (id self, GCHandle gc_handle, enum XamarinGCHandleFlags flags)
|
|
{
|
|
bool rv;
|
|
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
|
|
id<XamarinExtendedObject> xself = self;
|
|
rv = [xself xamarinSetGCHandle: gc_handle flags: flags];
|
|
|
|
return rv;
|
|
}
|
|
|
|
static inline GCHandle
|
|
get_gchandle_without_flags (id self)
|
|
{
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
GCHandle rv;
|
|
MONO_ENTER_GC_SAFE;
|
|
id<XamarinExtendedObject> xself = self;
|
|
rv = (GCHandle) [xself xamarinGetGCHandle];
|
|
MONO_EXIT_GC_SAFE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static inline GCHandle
|
|
get_gchandle_with_flags (id self, enum XamarinGCHandleFlags* flags)
|
|
{
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
GCHandle rv;
|
|
MONO_ENTER_GC_SAFE;
|
|
id<XamarinExtendedObject> xself = self;
|
|
rv = (GCHandle) [xself xamarinGetGCHandle];
|
|
if (flags != NULL)
|
|
*flags = [xself xamarinGetFlags];
|
|
MONO_EXIT_GC_SAFE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static inline enum XamarinGCHandleFlags
|
|
get_flags (id self)
|
|
{
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
enum XamarinGCHandleFlags rv;
|
|
MONO_ENTER_GC_SAFE;
|
|
id<XamarinExtendedObject> xself = self;
|
|
rv = [xself xamarinGetFlags];
|
|
MONO_EXIT_GC_SAFE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static inline void
|
|
set_flags_safe (id self, enum XamarinGCHandleFlags flags)
|
|
{
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
|
|
id<XamarinExtendedObject> xself = self;
|
|
[xself xamarinSetFlags: flags];
|
|
}
|
|
|
|
static inline enum XamarinGCHandleFlags
|
|
get_flags_safe (id self)
|
|
{
|
|
// COOP: we call a selector, and that must only be done in SAFE mode.
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
|
|
enum XamarinGCHandleFlags rv;
|
|
id<XamarinExtendedObject> xself = self;
|
|
rv = [xself xamarinGetFlags];
|
|
|
|
return rv;
|
|
}
|
|
|
|
GCHandle
|
|
xamarin_get_gchandle (id self)
|
|
{
|
|
// COOP: does not access managed memory: any mode
|
|
return get_gchandle_without_flags (self);
|
|
}
|
|
|
|
GCHandle
|
|
xamarin_get_gchandle_with_flags (id self, enum XamarinGCHandleFlags* flags)
|
|
{
|
|
// COOP: does not access managed memory: any mode
|
|
return get_gchandle_with_flags (self, flags);
|
|
}
|
|
|
|
bool
|
|
xamarin_has_managed_ref (id self)
|
|
{
|
|
// COOP: get_flags requires UNSAFE mode, so this function requires it too.
|
|
return (get_flags (self) & XamarinGCHandleFlags_HasManagedRef) == XamarinGCHandleFlags_HasManagedRef;
|
|
}
|
|
|
|
bool
|
|
xamarin_has_managed_ref_safe (id self)
|
|
{
|
|
// COOP: variation of xamarin_has_managed_ref for SAFE mode.
|
|
return (get_flags_safe (self) & XamarinGCHandleFlags_HasManagedRef) == XamarinGCHandleFlags_HasManagedRef;
|
|
}
|
|
|
|
MonoException *
|
|
xamarin_create_exception (const char *msg)
|
|
{
|
|
// COOP: calls mono, needs to be in UNSAFE mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
return xamarin_create_system_exception (msg);
|
|
}
|
|
|
|
MonoMethod *
|
|
xamarin_get_reflection_method_method (MonoReflectionMethod *method)
|
|
{
|
|
return xamarin_bridge_get_mono_method (method);
|
|
}
|
|
|
|
id
|
|
xamarin_get_handle (MonoObject *obj, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoClass *klass;
|
|
id rv = nil;
|
|
|
|
if (obj == NULL)
|
|
return nil;
|
|
|
|
klass = mono_object_get_class (obj);
|
|
|
|
if (xamarin_is_class_nsobject (klass)) {
|
|
rv = xamarin_get_nsobject_handle (obj);
|
|
} else if (xamarin_is_class_inativeobject (klass)) {
|
|
rv = xamarin_get_handle_for_inativeobject (obj, exception_gchandle);
|
|
} else {
|
|
char *msg = xamarin_strdup_printf ("Unable to marshal from %s.%s to an Objective-C object. "
|
|
"The managed class must either inherit from NSObject or implement INativeObject.",
|
|
mono_class_get_namespace (klass), mono_class_get_name (klass));
|
|
GCHandle ex_handle = xamarin_create_runtime_exception (8039, msg, exception_gchandle);
|
|
xamarin_free (msg);
|
|
if (*exception_gchandle == INVALID_GCHANDLE)
|
|
*exception_gchandle = ex_handle;
|
|
}
|
|
|
|
xamarin_mono_object_release (&klass);
|
|
|
|
return rv;
|
|
}
|
|
|
|
#if DEBUG
|
|
static void
|
|
verify_cast (MonoClass *to, MonoObject *obj, Class from_class, SEL sel, MonoMethod *method, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
if (!to)
|
|
return;
|
|
|
|
if (mono_object_isinst (obj, to) == NULL) {
|
|
MonoClass *from = mono_object_get_class (obj);
|
|
char *method_full_name = mono_method_full_name (method, TRUE);
|
|
char *from_name = xamarin_class_get_full_name (from, exception_gchandle);
|
|
char *to_name = xamarin_class_get_full_name (to, exception_gchandle);
|
|
char *msg = xamarin_strdup_printf ("Unable to cast object of type '%s' (Objective-C type: '%s') to type '%s'.\n"
|
|
"Additional information:\n"
|
|
"\tSelector: %s\n"
|
|
"\tMethod: %s\n", from_name, class_getName(from_class), to_name, sel_getName (sel), method_full_name);
|
|
MonoException *mono_ex = xamarin_create_system_invalid_cast_exception (msg);
|
|
mono_free (from_name);
|
|
mono_free (to_name);
|
|
xamarin_free (msg);
|
|
mono_free (method_full_name);
|
|
xamarin_mono_object_release (&from);
|
|
*exception_gchandle = xamarin_gchandle_new ((MonoObject *) mono_ex, FALSE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
xamarin_check_for_gced_object (MonoObject *obj, SEL sel, id self, MonoMethod *method, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
if (obj != NULL) {
|
|
#if DEBUG
|
|
MonoClass *declaring_type = mono_method_get_class (method);
|
|
verify_cast (declaring_type, obj, [self class], sel, method, exception_gchandle);
|
|
xamarin_mono_object_release (&declaring_type);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#if DOTNET
|
|
const char *m = "Failed to marshal the Objective-C object %p (type: %s). "
|
|
"Could not find an existing managed instance for this object, "
|
|
"nor was it possible to create a new managed instance "
|
|
"(because the type '%s' does not have a constructor that takes one NativeHandle argument).\n"
|
|
"Additional information:\n"
|
|
"\tSelector: %s\n"
|
|
"\tMethod: %s\n";
|
|
#else
|
|
const char *m = "Failed to marshal the Objective-C object %p (type: %s). "
|
|
"Could not find an existing managed instance for this object, "
|
|
"nor was it possible to create a new managed instance "
|
|
"(because the type '%s' does not have a constructor that takes one IntPtr argument).\n"
|
|
"Additional information:\n"
|
|
"\tSelector: %s\n"
|
|
"\tMethod: %s\n";
|
|
#endif
|
|
|
|
char *method_full_name = mono_method_full_name (method, TRUE);
|
|
char *type_name = xamarin_lookup_managed_type_name ([self class], exception_gchandle);
|
|
if (*exception_gchandle == INVALID_GCHANDLE) {
|
|
char *msg = xamarin_strdup_printf (m, self, object_getClassName (self), type_name, sel_getName (sel), method_full_name);
|
|
GCHandle ex_handle = xamarin_create_runtime_exception (8027, msg, exception_gchandle);
|
|
xamarin_free (msg);
|
|
if (*exception_gchandle == INVALID_GCHANDLE)
|
|
*exception_gchandle = ex_handle;
|
|
}
|
|
mono_free (type_name);
|
|
mono_free (method_full_name);
|
|
}
|
|
|
|
#if DEBUG
|
|
//
|
|
// We can't do the type-checks below correctly until we support multiple managed peers for each
|
|
// native objects. The problem is that Objective-C can fake multiple inheritance by
|
|
// overriding isKindOfClass: A test case can be seen in bug #23421: A parameter in a
|
|
// callback from Objective-C is typed as 'NSUrlSessionDownloadTask'. Objective-C may give us an instance
|
|
// of an internal class __NSCFBackgroundDownloadTask, which doesn't inherit from NSUrlSessionDownloadTask
|
|
// (it inherits from NSUrlSessionTask, which is a superclass of NSUrlSessionDownloadTask). In Objective-C
|
|
// the __NSCFBackgroundDownloadTask class gets away with this because it overrides isKindOfClass: to return
|
|
// YES for NSUrlSessionDownloadTask. We can't rely on isKindOfClass: when creating a managed peer for
|
|
// the native object, because we may already have an instance of the "correct" type (if we
|
|
// had support for multiple managed peers, we could just create a new instance of the expected type).
|
|
//
|
|
|
|
void
|
|
xamarin_verify_parameter (MonoObject *obj, SEL sel, id self, id arg, unsigned long index, MonoClass *expected, MonoMethod *method)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
// if (arg == NULL)
|
|
// return;
|
|
//
|
|
// if (obj != NULL) {
|
|
// verify_cast (expected, obj, [arg class], sel, method);
|
|
// return;
|
|
// }
|
|
//
|
|
// const char *m = "Failed to marshal the Objective-C object 0x%x (type: %s). "
|
|
// "Could not find an existing managed instance for this object, "
|
|
// "nor was it possible to create a new managed instance "
|
|
// "(because the type '%s' does not have a constructor that takes one IntPtr argument).\n"
|
|
// "Additional information:\n"
|
|
// "\tSelector: %s\n"
|
|
// "\tMethod: %s\n"
|
|
// "\tParameter: %i\n";
|
|
//
|
|
// char *method_full_name = mono_method_full_name (method, TRUE);
|
|
// char *type_name = xamarin_lookup_managed_type_name ([arg class]);
|
|
// char *msg = xamarin_strdup_printf (m, arg, object_getClassName (arg), type_name, sel, method_full_name, index);
|
|
// MonoException *mex = xamarin_create_exception (msg);
|
|
// xamarin_free (msg);
|
|
// mono_free (method_full_name);
|
|
// mono_free (type_name);
|
|
// mono_raise_exception (mex);
|
|
}
|
|
|
|
void
|
|
xamarin_check_objc_type (id obj, Class expected_class, SEL sel, id self, int index, MonoMethod *method)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
// if ([obj isKindOfClass:expected_class])
|
|
// return;
|
|
//
|
|
// const char *m = "Failed to marshal the Objective-C object 0x%x (type: %s), expected an object of type %s.\n"
|
|
// "Additional information:\n"
|
|
// "\tSelector: %s\n"
|
|
// "\tMethod: %s\n"
|
|
// "\tParameter: %i\n";
|
|
//
|
|
// char *method_full_name = mono_method_full_name (method, TRUE);
|
|
// char *msg = xamarin_strdup_printf (m, obj, object_getClassName (obj), class_getName (expected_class), sel, method_full_name, index);
|
|
// MonoException *mono_ex = xamarin_create_system_invalid_cast_exception (msg);
|
|
// xamarin_free (msg);
|
|
// mono_free (method_full_name);
|
|
// mono_raise_exception (mono_ex);
|
|
}
|
|
#endif
|
|
|
|
char *
|
|
xamarin_class_get_full_name (MonoClass *klass, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoType *type = mono_class_get_type (klass);
|
|
char * rv = xamarin_type_get_full_name (type, exception_gchandle);
|
|
xamarin_mono_object_release (&type);
|
|
|
|
return rv;
|
|
}
|
|
|
|
char *
|
|
xamarin_type_get_full_name (MonoType *type, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: Reads managed memory, needs to be in UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoReflectionType *rtype = mono_type_get_object (mono_domain_get (), type);
|
|
char *rv = xamarin_reflection_type_get_full_name (rtype, exception_gchandle);
|
|
xamarin_mono_object_release (&rtype);
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* ToggleRef support
|
|
*/
|
|
// #define DEBUG_TOGGLEREF 1
|
|
|
|
MonoToggleRefStatus
|
|
xamarin_gc_toggleref_callback (uint8_t flags, id handle, xamarin_get_handle_func get_handle, MonoObject *info)
|
|
{
|
|
// COOP: this is a callback called by the GC, so I assume the mode here doesn't matter
|
|
MonoToggleRefStatus res;
|
|
|
|
bool disposed = (flags & NSObjectFlagsDisposed) == NSObjectFlagsDisposed;
|
|
bool has_managed_ref = (flags & NSObjectFlagsHasManagedRef) == NSObjectFlagsHasManagedRef;
|
|
|
|
if (disposed || !has_managed_ref) {
|
|
res = MONO_TOGGLE_REF_DROP; /* Already disposed, we don't need the managed object around */
|
|
} else {
|
|
if (handle == NULL)
|
|
handle = get_handle (info);
|
|
|
|
if (handle == NULL) { /* This shouldn't really happen */
|
|
res = MONO_TOGGLE_REF_DROP;
|
|
} else {
|
|
if ([handle retainCount] == 1)
|
|
res = MONO_TOGGLE_REF_WEAK;
|
|
else
|
|
res = MONO_TOGGLE_REF_STRONG;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_TOGGLEREF
|
|
const char *rv;
|
|
if (res == MONO_TOGGLE_REF_DROP) {
|
|
rv = "DROP";
|
|
} else if (res == MONO_TOGGLE_REF_STRONG) {
|
|
rv = "STRONG";
|
|
} else if (res == MONO_TOGGLE_REF_WEAK) {
|
|
rv = "WEAK";
|
|
} else {
|
|
rv = "UNKNOWN";
|
|
}
|
|
const char *cn = NULL;
|
|
if (handle == NULL)
|
|
handle = get_handle (info);
|
|
cn = object_getClassName (handle);
|
|
PRINT ("\tinspecting %p handle:%p %s flags: %i RC %d -> %s\n", object, handle, cn, (int) flags, (int) (handle ? [handle retainCount] : 0), rv);
|
|
#endif
|
|
|
|
return res;
|
|
}
|
|
|
|
void
|
|
xamarin_gc_event (MonoGCEvent event)
|
|
{
|
|
// COOP: this is a callback called by the GC, I believe the mode here doesn't matter.
|
|
switch (event) {
|
|
case MONO_GC_EVENT_PRE_STOP_WORLD:
|
|
pthread_mutex_lock (&framework_peer_release_lock);
|
|
break;
|
|
|
|
case MONO_GC_EVENT_POST_START_WORLD:
|
|
pthread_mutex_unlock (&framework_peer_release_lock);
|
|
break;
|
|
|
|
default: // silences a compiler warning.
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
struct _MonoProfiler {
|
|
int dummy;
|
|
};
|
|
|
|
static void
|
|
xamarin_install_mono_profiler ()
|
|
{
|
|
static _MonoProfiler profiler = { 0 };
|
|
// This must be done before any other mono_profiler_install_* functions are called
|
|
// (currently xamarin_enable_new_refcount and xamarin_install_nsautoreleasepool_hooks).
|
|
mono_profiler_install (&profiler, NULL);
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
xamarin_file_exists (const char *path)
|
|
{
|
|
// COOP: no managed access: any mode
|
|
struct stat buffer;
|
|
return stat (path, &buffer) == 0;
|
|
}
|
|
|
|
static MonoAssembly *
|
|
xamarin_open_assembly_or_assert (const char *name)
|
|
{
|
|
MonoImageOpenStatus status = MONO_IMAGE_OK;
|
|
MonoAssembly *assembly = mono_assembly_open (name, &status);
|
|
if (assembly == NULL)
|
|
xamarin_assertion_message ("Failed to open the assembly '%s' from the app: %i (errno: %i). This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name, (int) status, errno);
|
|
return assembly;
|
|
}
|
|
|
|
// Returns a retained MonoObject. Caller must release.
|
|
MonoAssembly *
|
|
xamarin_open_assembly (const char *name)
|
|
{
|
|
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
|
|
char path [1024];
|
|
bool exists = false;
|
|
|
|
#if MONOMAC
|
|
if (xamarin_get_is_mkbundle ())
|
|
return xamarin_open_assembly_or_assert (name);
|
|
#endif
|
|
|
|
exists = xamarin_locate_assembly_resource (name, NULL, name, path, sizeof (path));
|
|
|
|
#if MONOMAC && DYLIB
|
|
if (!exists) {
|
|
// Check if we already have the assembly in memory
|
|
xamarin_get_assembly_name_without_extension (name, path, sizeof (path));
|
|
MonoAssemblyName *aname = mono_assembly_name_new (path);
|
|
MonoAssembly *assembly = mono_assembly_loaded (aname);
|
|
mono_assembly_name_free (aname);
|
|
if (assembly)
|
|
return assembly;
|
|
|
|
xamarin_assertion_message ("Could not find the assembly '%s' in the app nor as an already loaded assembly. This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name);
|
|
}
|
|
#endif
|
|
|
|
if (!exists)
|
|
xamarin_assertion_message ("Could not find the assembly '%s' in the app. This is usually fixed by cleaning and rebuilding your project; if that doesn't work, please file a bug report: https://github.com/xamarin/xamarin-macios/issues/new", name);
|
|
|
|
return xamarin_open_assembly_or_assert (path);
|
|
}
|
|
|
|
bool
|
|
xamarin_register_monoassembly (MonoAssembly *assembly, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
|
|
if (!xamarin_supports_dynamic_registration) {
|
|
#if defined (CORECLR_RUNTIME)
|
|
if (xamarin_log_level > 0) {
|
|
MonoReflectionAssembly *rassembly = mono_assembly_get_object (mono_domain_get (), assembly);
|
|
GCHandle assembly_gchandle = xamarin_gchandle_new ((MonoObject *) rassembly, false);
|
|
xamarin_mono_object_release (&rassembly);
|
|
|
|
char *assembly_name = xamarin_bridge_get_assembly_name (assembly_gchandle);
|
|
xamarin_gchandle_free (assembly_gchandle);
|
|
|
|
LOG (PRODUCT ": Skipping assembly registration for %s since it's not needed (dynamic registration is not supported)", assembly_name);
|
|
mono_free (assembly_name);
|
|
}
|
|
#else
|
|
LOG (PRODUCT ": Skipping assembly registration for %s since it's not needed (dynamic registration is not supported)", mono_assembly_name_get_name (mono_assembly_get_name (assembly)));
|
|
#endif
|
|
return true;
|
|
}
|
|
MonoReflectionAssembly *rassembly = mono_assembly_get_object (mono_domain_get (), assembly);
|
|
xamarin_register_assembly (rassembly, exception_gchandle);
|
|
xamarin_mono_object_release (&rassembly);
|
|
return *exception_gchandle == INVALID_GCHANDLE;
|
|
}
|
|
|
|
// Returns a retained MonoObject. Caller must release.
|
|
MonoAssembly *
|
|
xamarin_open_and_register (const char *aname, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
|
|
MonoAssembly *assembly;
|
|
|
|
assembly = xamarin_open_assembly (aname);
|
|
|
|
xamarin_register_monoassembly (assembly, exception_gchandle);
|
|
|
|
return assembly;
|
|
}
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
static gboolean
|
|
is_class_finalization_aware (MonoClass *cls)
|
|
{
|
|
// COOP: This is a callback called by the GC, I believe the mode here doesn't matter.
|
|
gboolean rv = false;
|
|
|
|
MonoClass *nsobject_class = xamarin_get_nsobject_class ();
|
|
if (nsobject_class)
|
|
rv = cls == nsobject_class || mono_class_is_assignable_from (nsobject_class, cls);
|
|
|
|
//PRINT ("IsClass %s.%s finalization aware: %i\n", mono_class_get_namespace (cls), mono_class_get_name (cls), rv);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void
|
|
object_queued_for_finalization (MonoObject *object)
|
|
{
|
|
// COOP: Although this is reading managed memory, it is a callback called by the GC, so I believe the mode here doesn't matter.
|
|
|
|
/* This is called with the GC lock held, so it can only use signal-safe code */
|
|
struct Managed_NSObject *obj = (struct Managed_NSObject *) object;
|
|
//PRINT ("In finalization response for %s.%s %p (handle: %p class_handle: %p flags: %i)\n",
|
|
obj->flags |= NSObjectFlagsInFinalizerQueue;
|
|
}
|
|
#endif // !defined (CORECLR_RUNTIME)
|
|
|
|
/*
|
|
* Registration map
|
|
*/
|
|
|
|
static int
|
|
compare_mtclassmap (const void *a, const void *b)
|
|
{
|
|
MTClassMap *mapa = (MTClassMap *) a;
|
|
MTClassMap *mapb = (MTClassMap *) b;
|
|
|
|
intptr_t diff = (intptr_t)mapa->handle - (intptr_t)mapb->handle;
|
|
const int shift = (sizeof(intptr_t) * 8) - 1;
|
|
|
|
return (diff >> shift) | !!diff;
|
|
}
|
|
|
|
void
|
|
xamarin_add_registration_map (struct MTRegistrationMap *map, bool partial)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
options.RegistrationData = map;
|
|
if (partial)
|
|
options.flags = (InitializationFlags) (options.flags | InitializationFlagsIsPartialStaticRegistrar);
|
|
|
|
// Sort the type map according to Class
|
|
qsort (map->map, (size_t) map->map_count, sizeof (MTClassMap), compare_mtclassmap);
|
|
}
|
|
|
|
/*
|
|
* Exception handling
|
|
*/
|
|
|
|
NSString *
|
|
xamarin_print_all_exceptions (GCHandle gchandle)
|
|
{
|
|
GCHandle exception_gchandle = INVALID_GCHANDLE;
|
|
|
|
char *msg = xamarin_print_all_exceptions_wrapper (gchandle, &exception_gchandle);
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
// Not much we can do here but returning a very generic message, since we failed to print one exception, it's reasonable to assume that printing another won't work either.
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
return [NSString stringWithFormat: @"An exception occurred while trying to get a string representation for the exception %p (%p)", gchandle, exception_gchandle];
|
|
}
|
|
|
|
NSString *rv = [NSString stringWithUTF8String: msg];
|
|
xamarin_free (msg);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
xamarin_ftnptr_exception_handler (GCHandle gchandle)
|
|
{
|
|
xamarin_process_managed_exception_gchandle (gchandle);
|
|
}
|
|
|
|
void
|
|
xamarin_process_fatal_exception_gchandle (GCHandle gchandle, const char *message)
|
|
{
|
|
if (gchandle == INVALID_GCHANDLE)
|
|
return;
|
|
|
|
NSString *fatal_message = [NSString stringWithFormat:@"%s\n%@", message, xamarin_print_all_exceptions (gchandle)];
|
|
NSLog (@PRODUCT ": %@", fatal_message);
|
|
xamarin_assertion_message ([fatal_message UTF8String]);
|
|
}
|
|
|
|
// Because this function won't always return, it will take ownership of the GCHandle and free it.
|
|
void
|
|
xamarin_process_managed_exception_gchandle (GCHandle gchandle)
|
|
{
|
|
if (gchandle == INVALID_GCHANDLE)
|
|
return;
|
|
|
|
MonoObject *exc = xamarin_gchandle_get_target (gchandle);
|
|
xamarin_gchandle_free (gchandle);
|
|
xamarin_process_managed_exception (exc);
|
|
}
|
|
|
|
void
|
|
xamarin_unhandled_exception_handler (MonoObject *exc, gpointer user_data)
|
|
{
|
|
GCHandle exception_gchandle = xamarin_gchandle_new (exc, false);
|
|
PRINT ("Unhandled managed exception: %@", xamarin_print_all_exceptions (exception_gchandle));
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
|
|
abort ();
|
|
}
|
|
|
|
static void
|
|
exception_handler (NSException *exc)
|
|
{
|
|
// COOP: We won't get here in coop-mode, because we don't set the uncaught objc exception handler in that case.
|
|
LOG (PRODUCT ": Received unhandled ObjectiveC exception: %@ %@", [exc name], [exc reason]);
|
|
|
|
if (xamarin_is_gc_coop) {
|
|
PRINT ("Uncaught Objective-C exception: %@", exc);
|
|
assert (false); // Re-throwing the Objective-C exception will probably just end up with infinite recursion
|
|
}
|
|
|
|
xamarin_throw_ns_exception (exc);
|
|
}
|
|
|
|
#if defined (DEBUG)
|
|
static void *
|
|
pump_gc (void *context)
|
|
{
|
|
// COOP: this runs on a separate thread, so I'm not sure what happens here.
|
|
// We can make sure we're in safe mode while sleeping though.
|
|
#if !defined (CORECLR_RUNTIME)
|
|
mono_thread_attach (mono_get_root_domain ());
|
|
#endif
|
|
|
|
while (xamarin_gc_pump) {
|
|
GCHandle exception_gchandle = INVALID_GCHANDLE;
|
|
xamarin_gc_collect (&exception_gchandle);
|
|
xamarin_process_fatal_exception_gchandle (exception_gchandle, "An exception occurred while running the GC in a loop");
|
|
MONO_ENTER_GC_SAFE;
|
|
usleep (1000000);
|
|
MONO_EXIT_GC_SAFE;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
static void
|
|
log_callback (const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data)
|
|
{
|
|
// COOP: Not accessing managed memory: any mode
|
|
PRINT ("%s: %s", log_level, message);
|
|
|
|
if (fatal)
|
|
abort ();
|
|
}
|
|
|
|
static void
|
|
print_callback (const char *string, mono_bool is_stdout)
|
|
{
|
|
// COOP: Not accessing managed memory: any mode
|
|
PRINT ("%s", string);
|
|
}
|
|
#endif // !defined (CORECLR_RUNTIME)
|
|
|
|
static int
|
|
xamarin_compare_ints (const void *a, const void *b)
|
|
{
|
|
uint32_t x = *(uint32_t *) a;
|
|
uint32_t y = *(uint32_t *) b;
|
|
return x < y ? -1 : (x == y ? 0 : 1);
|
|
}
|
|
|
|
uint32_t
|
|
xamarin_find_protocol_wrapper_type (uint32_t token_ref)
|
|
{
|
|
if (options.RegistrationData == NULL || options.RegistrationData->protocol_wrappers == NULL)
|
|
return INVALID_TOKEN_REF;
|
|
|
|
void* ptr = bsearch (&token_ref, options.RegistrationData->protocol_wrappers, (size_t) options.RegistrationData->protocol_wrapper_count, sizeof (MTProtocolWrapperMap), xamarin_compare_ints);
|
|
if (ptr == NULL)
|
|
return INVALID_TOKEN_REF;
|
|
|
|
MTProtocolWrapperMap *entry = (MTProtocolWrapperMap *) ptr;
|
|
return entry->wrapper_token;
|
|
}
|
|
|
|
void
|
|
xamarin_initialize_embedded ()
|
|
{
|
|
static bool initialized = false;
|
|
if (initialized)
|
|
return;
|
|
initialized = true;
|
|
|
|
char *argv[] = { NULL };
|
|
char *libname = NULL;
|
|
|
|
Dl_info info;
|
|
if (dladdr ((void *) xamarin_initialize_embedded, &info) != 0) {
|
|
const char *last_sep = strrchr (info.dli_fname, '/');
|
|
if (last_sep == NULL) {
|
|
libname = strdup (info.dli_fname);
|
|
} else {
|
|
libname = strdup (last_sep + 1);
|
|
}
|
|
argv [0] = libname;
|
|
}
|
|
|
|
if (argv [0] == NULL)
|
|
argv [0] = (char *) "embedded";
|
|
|
|
xamarin_main (1, argv, XamarinLaunchModeEmbedded);
|
|
|
|
if (libname != NULL)
|
|
free (libname);
|
|
}
|
|
|
|
/* Installs g_print/g_error handlers that will redirect output to the system Console */
|
|
void
|
|
xamarin_install_log_callbacks ()
|
|
{
|
|
#if !defined (CORECLR_RUNTIME)
|
|
mono_trace_set_log_handler (log_callback, NULL);
|
|
mono_trace_set_print_handler (print_callback);
|
|
mono_trace_set_printerr_handler (print_callback);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xamarin_initialize ()
|
|
{
|
|
// COOP: accessing managed memory: UNSAFE mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
GCHandle exception_gchandle = INVALID_GCHANDLE;
|
|
|
|
initialize_started = TRUE;
|
|
|
|
#ifdef DYNAMIC_MONO_RUNTIME
|
|
// We might be called from the managed Runtime.EnsureInitialized method,
|
|
// in which case xamarin_initialize_dynamic_runtime has not been called yet.
|
|
xamarin_initialize_dynamic_runtime (NULL);
|
|
#endif
|
|
|
|
#if !DOTNET
|
|
xamarin_insert_dllmap ();
|
|
#endif
|
|
|
|
MONO_ENTER_GC_UNSAFE;
|
|
|
|
xamarin_install_log_callbacks ();
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
MonoGCFinalizerCallbacks gc_callbacks;
|
|
gc_callbacks.version = MONO_GC_FINALIZER_EXTENSION_VERSION;
|
|
gc_callbacks.is_class_finalization_aware = is_class_finalization_aware;
|
|
gc_callbacks.object_queued_for_finalization = object_queued_for_finalization;
|
|
mono_gc_register_finalizer_callbacks (&gc_callbacks);
|
|
#endif
|
|
|
|
if (xamarin_is_gc_coop) {
|
|
// There should be no such thing as an unhandled ObjC exception
|
|
// when running the GC in cooperative mode, and if we run into it,
|
|
// it's a bug somewhere, in which case we must fix it.
|
|
} else {
|
|
NSSetUncaughtExceptionHandler (exception_handler);
|
|
}
|
|
|
|
options.size = sizeof (options);
|
|
#if TARGET_OS_SIMULATOR
|
|
options.flags = (enum InitializationFlags) (options.flags | InitializationFlagsIsSimulator);
|
|
#endif
|
|
|
|
#if defined (CORECLR_RUNTIME)
|
|
options.flags = (enum InitializationFlags) (options.flags | InitializationFlagsIsCoreCLR);
|
|
#endif
|
|
|
|
options.Delegates = &delegates;
|
|
options.Trampolines = &trampolines;
|
|
options.MarshalObjectiveCExceptionMode = xamarin_marshal_objectivec_exception_mode;
|
|
options.MarshalManagedExceptionMode = xamarin_marshal_managed_exception_mode;
|
|
#if MONOMAC
|
|
options.LaunchMode = xamarin_launch_mode;
|
|
options.EntryAssemblyPath = xamarin_entry_assembly_path;
|
|
#endif
|
|
|
|
#if defined (CORECLR_RUNTIME)
|
|
options.xamarin_objc_msgsend = (void *) xamarin_dyn_objc_msgSend;
|
|
options.xamarin_objc_msgsend_super = (void *) xamarin_dyn_objc_msgSendSuper;
|
|
#if !defined(__aarch64__)
|
|
options.xamarin_objc_msgsend_stret = (void *) xamarin_dyn_objc_msgSend_stret;
|
|
options.xamarin_objc_msgsend_super_stret = (void *) xamarin_dyn_objc_msgSendSuper_stret;
|
|
#endif // !defined(__aarch64__)
|
|
options.unhandled_exception_handler = (void *) &xamarin_coreclr_unhandled_exception_handler;
|
|
options.reference_tracking_begin_end_callback = (void *) &xamarin_coreclr_reference_tracking_begin_end_callback;
|
|
options.reference_tracking_is_referenced_callback = (void *) &xamarin_coreclr_reference_tracking_is_referenced_callback;
|
|
options.reference_tracking_tracked_object_entered_finalization = (void *) &xamarin_coreclr_reference_tracking_tracked_object_entered_finalization;
|
|
#endif // defined(CORECLR_RUNTIME)
|
|
|
|
xamarin_bridge_call_runtime_initialize (&options, &exception_gchandle);
|
|
xamarin_process_fatal_exception_gchandle (exception_gchandle, "An exception occurred while calling Runtime.Initialize");
|
|
|
|
xamarin_bridge_register_product_assembly (&exception_gchandle);
|
|
xamarin_process_fatal_exception_gchandle (exception_gchandle, "An exception occurred while registering the product assembly");
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
xamarin_install_mono_profiler (); // must be called before xamarin_install_nsautoreleasepool_hooks or xamarin_enable_new_refcount
|
|
#endif
|
|
|
|
xamarin_install_nsautoreleasepool_hooks ();
|
|
|
|
#if defined (DEBUG)
|
|
if (xamarin_gc_pump) {
|
|
pthread_t gc_thread;
|
|
pthread_create (&gc_thread, NULL, pump_gc, NULL);
|
|
}
|
|
#endif
|
|
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init (&attr);
|
|
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
pthread_mutex_init (&framework_peer_release_lock, &attr);
|
|
pthread_mutexattr_destroy (&attr);
|
|
|
|
xamarin_enable_new_refcount ();
|
|
|
|
MONO_EXIT_GC_UNSAFE;
|
|
}
|
|
|
|
static char *x_app_bundle_path = NULL;
|
|
const char *
|
|
xamarin_get_app_bundle_path ()
|
|
{
|
|
if (x_app_bundle_path != NULL)
|
|
return x_app_bundle_path;
|
|
|
|
NSBundle *main_bundle = [NSBundle mainBundle];
|
|
|
|
if (main_bundle == NULL)
|
|
xamarin_assertion_message ("Could not find the main bundle in the app ([NSBundle mainBundle] returned nil)");
|
|
|
|
x_app_bundle_path = strdup ([[[main_bundle bundlePath] stringByStandardizingPath] UTF8String]);
|
|
|
|
return x_app_bundle_path;
|
|
}
|
|
|
|
static char *x_bundle_path = NULL;
|
|
const char *
|
|
xamarin_get_bundle_path ()
|
|
{
|
|
// COOP: only called at startup, so I believe the mode doesn't matter
|
|
if (x_bundle_path != NULL)
|
|
return x_bundle_path;
|
|
|
|
NSBundle *main_bundle = [NSBundle mainBundle];
|
|
NSString *bundle_path;
|
|
|
|
if (main_bundle == NULL)
|
|
xamarin_assertion_message ("Could not find the main bundle in the app ([NSBundle mainBundle] returned nil)");
|
|
|
|
#if TARGET_OS_MACCATALYST || TARGET_OS_OSX
|
|
if (xamarin_launch_mode == XamarinLaunchModeEmbedded) {
|
|
bundle_path = [[[NSBundle bundleForClass: [XamarinAssociatedObject class]] bundlePath] stringByAppendingPathComponent: @"Versions/Current"];
|
|
} else {
|
|
bundle_path = [[main_bundle bundlePath] stringByAppendingPathComponent:@"Contents"];
|
|
}
|
|
bundle_path = [bundle_path stringByAppendingPathComponent: xamarin_custom_bundle_name];
|
|
#else
|
|
bundle_path = [main_bundle bundlePath];
|
|
#endif
|
|
|
|
x_bundle_path = strdup ([[bundle_path stringByStandardizingPath] UTF8String]);
|
|
|
|
return x_bundle_path;
|
|
}
|
|
|
|
void
|
|
xamarin_set_bundle_path (const char *path)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
free (x_bundle_path);
|
|
x_bundle_path = strdup (path);
|
|
}
|
|
|
|
void *
|
|
xamarin_calloc (size_t size)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
return calloc (size, 1);
|
|
}
|
|
|
|
void
|
|
xamarin_free (void *ptr)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
// We use this method to free memory returned by mono,
|
|
// which means we have to use the free function mono expects.
|
|
if (ptr)
|
|
free (ptr);
|
|
}
|
|
|
|
char *
|
|
xamarin_strdup_printf (const char *msg, ...)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
va_list args;
|
|
char *formatted = NULL;
|
|
|
|
va_start (args, msg);
|
|
vasprintf (&formatted, msg, args);
|
|
va_end (args);
|
|
|
|
return formatted;
|
|
}
|
|
|
|
void
|
|
xamarin_assertion_message (const char *msg, ...)
|
|
{
|
|
// COOP: no managed memory access: any mode.
|
|
va_list args;
|
|
char *formatted = NULL;
|
|
|
|
va_start (args, msg);
|
|
vasprintf (&formatted, msg, args);
|
|
if (formatted) {
|
|
PRINT ( PRODUCT ": %s", formatted);
|
|
free (formatted);
|
|
}
|
|
va_end (args);
|
|
abort ();
|
|
}
|
|
|
|
static const char *
|
|
objc_skip_type (const char *type)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
switch (type [0]) {
|
|
case _C_ID:
|
|
case _C_CLASS:
|
|
case _C_SEL:
|
|
case _C_CHR:
|
|
case _C_UCHR:
|
|
case _C_SHT:
|
|
case _C_USHT:
|
|
case _C_INT:
|
|
case _C_UINT:
|
|
case _C_LNG:
|
|
case _C_ULNG:
|
|
case _C_LNG_LNG:
|
|
case _C_ULNG_LNG:
|
|
case _C_FLT:
|
|
case _C_DBL:
|
|
case _C_CHARPTR:
|
|
case _C_BOOL:
|
|
case _C_VOID:
|
|
case _C_UNDEF:
|
|
return ++type;
|
|
case _C_PTR:
|
|
return objc_skip_type (++type);
|
|
case _C_BFLD:
|
|
type++;
|
|
while (*type && *type >= '0' && *type <= '9')
|
|
type++;
|
|
return type;
|
|
case _C_ATOM:
|
|
case _C_VECTOR:
|
|
case _C_CONST:
|
|
case _C_ARY_E:
|
|
case _C_UNION_E:
|
|
case _C_STRUCT_E:
|
|
xamarin_assertion_message ("Unhandled type encoding: %s", type);
|
|
break;
|
|
case _C_ARY_B: {
|
|
do {
|
|
type++;
|
|
} while (isdigit (*type));
|
|
|
|
type = objc_skip_type (type);
|
|
|
|
return ++type;
|
|
}
|
|
case _C_UNION_B: {
|
|
do {
|
|
type++;
|
|
} while (*type != '=');
|
|
|
|
type ++;
|
|
do {
|
|
type = objc_skip_type (type);
|
|
} while (*type != _C_UNION_E);
|
|
|
|
return ++type;
|
|
}
|
|
case _C_STRUCT_B: {
|
|
do {
|
|
type++;
|
|
} while (*type != '=');
|
|
|
|
type++;
|
|
|
|
do {
|
|
type = objc_skip_type (type);
|
|
} while (*type != _C_STRUCT_E);
|
|
|
|
return ++type;
|
|
}
|
|
default:
|
|
xamarin_assertion_message ("Unsupported type encoding: %s", type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsigned long
|
|
xamarin_objc_type_size (const char *type)
|
|
{
|
|
const char *original_type = type;
|
|
|
|
// COOP: no managed memory access: any mode
|
|
switch (type [0]) {
|
|
case _C_ID: return sizeof (id);
|
|
case _C_CLASS: return sizeof (Class);
|
|
case _C_SEL: return sizeof (SEL);
|
|
case _C_CHR: return sizeof (char);
|
|
case _C_UCHR: return sizeof (unsigned char);
|
|
case _C_SHT: return sizeof (short);
|
|
case _C_USHT: return sizeof (unsigned short);
|
|
case _C_INT: return sizeof (int);
|
|
case _C_UINT: return sizeof (unsigned int);
|
|
case _C_LNG: return sizeof (long);
|
|
case _C_ULNG: return sizeof (unsigned long);
|
|
case _C_LNG_LNG: return sizeof (long long);
|
|
case _C_ULNG_LNG: return sizeof (unsigned long long);
|
|
case _C_FLT: return sizeof (float);
|
|
case _C_DBL: return sizeof (double);
|
|
case _C_BOOL: return sizeof (BOOL);
|
|
case _C_VOID: return 0;
|
|
case _C_PTR: return sizeof (void *);
|
|
case _C_CHARPTR: return sizeof (char *);
|
|
case _C_BFLD: {
|
|
// Example: [NSDecimalNumberPlaceholder initWithDecimal:] = @28@0:4{?=b8b4b1b1b18[8S]}8
|
|
unsigned long bits = 0;
|
|
int bc = 1;
|
|
while (type [bc] >= '0' && type [bc] <= '9') {
|
|
bits = bits * 10ul + (unsigned long) (type [bc] - '0');
|
|
bc++;
|
|
}
|
|
if (bits % sizeof (void *) == 0)
|
|
return bits / sizeof (void *);
|
|
return 1 + (bits / sizeof (void *));
|
|
}
|
|
case _C_UNDEF:
|
|
case _C_ATOM:
|
|
case _C_VECTOR:
|
|
xamarin_assertion_message ("Unhandled type encoding: %s", type);
|
|
break;
|
|
case _C_ARY_B: {
|
|
unsigned long size = 0;
|
|
unsigned long len = (unsigned long) atol (type+1);
|
|
do {
|
|
type++;
|
|
} while (isdigit (*type));
|
|
|
|
size = xamarin_objc_type_size (type);
|
|
|
|
size = (size + (sizeof (void *) - 1)) & ~((sizeof (void *) - 1));
|
|
|
|
return len * size;
|
|
}
|
|
case _C_UNION_B: {
|
|
unsigned long size = 0;
|
|
|
|
do {
|
|
type++;
|
|
|
|
if (*type == 0)
|
|
xamarin_assertion_message ("Unsupported union type: %s", original_type);
|
|
} while (*type != '=');
|
|
|
|
++type;
|
|
|
|
do {
|
|
if (*type == 0)
|
|
xamarin_assertion_message ("Unsupported union type: %s", original_type);
|
|
|
|
unsigned long tsize = xamarin_objc_type_size (type);
|
|
type = objc_skip_type (type);
|
|
|
|
tsize = (tsize + (sizeof (void *) - 1)) & ~((sizeof (void *) - 1));
|
|
size = size > tsize ? size : tsize;
|
|
} while (*type != _C_UNION_E);
|
|
|
|
return size;
|
|
}
|
|
case _C_STRUCT_B: {
|
|
unsigned long size = 0;
|
|
|
|
do {
|
|
type++;
|
|
|
|
if (*type == 0)
|
|
xamarin_assertion_message ("Unsupported struct type: %s", original_type);
|
|
} while (*type != '=');
|
|
|
|
type++;
|
|
|
|
while (*type != _C_STRUCT_E) {
|
|
if (*type == 0)
|
|
xamarin_assertion_message ("Unsupported struct type: %s", original_type);
|
|
unsigned long item_size = xamarin_objc_type_size (type);
|
|
|
|
size += (item_size + (sizeof (void *) - 1)) & ~((sizeof (void *) - 1));
|
|
|
|
type = objc_skip_type (type);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
// The following are from table 6-2 here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
|
|
case 'r': // _C_CONST
|
|
case 'n':
|
|
case 'N':
|
|
case 'o':
|
|
case 'O':
|
|
case 'R':
|
|
case 'V':
|
|
return xamarin_objc_type_size (type + 1);
|
|
}
|
|
|
|
xamarin_assertion_message ("Unsupported type encoding: %s", original_type);
|
|
}
|
|
|
|
/*
|
|
* Reference counting
|
|
* ==================
|
|
*
|
|
* There are two types of managed types:
|
|
* - Wrapper types for existing ObjectiveC types (such as UIView).
|
|
* - Other types derived from wrapper types (henceforth called User types),
|
|
* that do not have a corresponding ObjectiveC type.
|
|
*
|
|
* Wrapper types
|
|
* -------------
|
|
*
|
|
* They are simple. The managed peer's lifetime is not linked to the native object's lifetime.
|
|
* The managed peer can be freed by the GC whenever it so determines, and if a managed object
|
|
* is needed at a later stage we just recreate it.
|
|
*
|
|
* User types
|
|
* ----------
|
|
* These are not that simple. We can't free managed objects at will, since they may contain
|
|
* user state. Therefore we must ensure the managed object stays alive as long as needed
|
|
* (this is done by using a strong GCHandle) - the problem is determining when it's no longer
|
|
* needed.
|
|
*
|
|
* Historically we've had two cases:
|
|
* 1) User calls Dispose, we free the managed ref and break the link between the native and
|
|
* managed object, thus allowing the GC to free it (if nothing else is keeping it alive
|
|
* of course).
|
|
* 2) Refcount reaches 1, in which case we know that the managed ref is the only ref and we
|
|
* can safely assume that native code will not use the object again. We break the link,
|
|
* release the native object (which will be freed now) and allow the GC to free the
|
|
* managed object.
|
|
*
|
|
* Problem arises in case 1), when users call Dispose and then native code tries to use
|
|
* that object for whatever reason. MonoTouch will detect that no managed peer exists,
|
|
* and try to (re)create one. This may fail, and an exception is thrown which may kill
|
|
* the process (there may be no managed frame / exception handler on the stack at this point).
|
|
*
|
|
* This solution will do a couple of things when user calls Dispose:
|
|
* - Free the managed ref.
|
|
* - Not break the link between the native and managed object until the native object's
|
|
* refcount reaches 0.
|
|
* This will allow us to still lookup the managed object as long as the native object is
|
|
* alive.
|
|
*
|
|
* Implementation details (for user types only)
|
|
* ============================================
|
|
*
|
|
* Managed code releases its ref when either of the following conditions occur:
|
|
* - Dispose is called (manually) on the object.
|
|
* - The Handle property on the managed object changes.
|
|
* - The GC frees the object. This can only happen if refCount == 1 and that ref is a managed ref
|
|
* (since otherwise there will be a strong gchandle to the managed object preventing the GC from freeing it).
|
|
*
|
|
* Objects are removed from the native<->managed dictionary when either of the following conditions occur:
|
|
* - The native object is dealloc'ed (refcount reaches 0).
|
|
* - The managed object's Handle property changes (the link between the previous Handle
|
|
* value and the managed object is then removed from the dictionary).
|
|
*
|
|
* We need to keep track of two pieces of information in native land:
|
|
* - The GCHandle to the managed object.
|
|
* - If there is a managed ref or not.
|
|
* We already have an ObjectiveC ivar to store the GCHandle, so to not create another ivar
|
|
* we use one bit of the GCHandle to store whether there is a managed ref or not (MANAGED_REF_BIT).
|
|
*
|
|
*/
|
|
//#define DEBUG_REF_COUNTING
|
|
void
|
|
xamarin_create_gchandle (id self, void *managed_object, enum XamarinGCHandleFlags flags, bool force_weak)
|
|
{
|
|
// COOP: reads managed memory: unsafe mode
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
// force_weak is to avoid calling retainCount unless needed, since some classes (UIWebView in iOS 5)
|
|
// will crash if retainCount is called before init. See bug #9261.
|
|
bool weak = force_weak || ([self retainCount] == 1);
|
|
GCHandle gchandle;
|
|
|
|
if (weak) {
|
|
gchandle = xamarin_gchandle_new_weakref ((MonoObject *) managed_object, TRUE);
|
|
flags = (enum XamarinGCHandleFlags) (flags | XamarinGCHandleFlags_WeakGCHandle);
|
|
} else {
|
|
gchandle = xamarin_gchandle_new ((MonoObject *) managed_object, FALSE);
|
|
flags = (enum XamarinGCHandleFlags) (flags & ~XamarinGCHandleFlags_WeakGCHandle);
|
|
}
|
|
set_gchandle (self, gchandle, flags);
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("\tGCHandle created for %p: %d (flags: %p) = %s managed object: %p\n", self, gchandle, GINT_TO_POINTER (flags), weak ? "weak" : "strong", managed_object);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xamarin_switch_gchandle (id self, bool to_weak)
|
|
{
|
|
// COOP: reads managed memory: unsafe mode
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
|
|
GCHandle new_gchandle;
|
|
GCHandle old_gchandle;
|
|
MonoObject *managed_object;
|
|
enum XamarinGCHandleFlags flags = XamarinGCHandleFlags_None;
|
|
|
|
old_gchandle = get_gchandle_safe (self, &flags);
|
|
if (old_gchandle) {
|
|
bool is_weak = (flags & XamarinGCHandleFlags_WeakGCHandle) == XamarinGCHandleFlags_WeakGCHandle;
|
|
if (to_weak == is_weak) {
|
|
// we already have the GCHandle we need
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("Object %p already has a %s GCHandle = %d\n", self, to_weak ? "weak" : "strong", old_gchandle);
|
|
#endif
|
|
return;
|
|
}
|
|
} else {
|
|
// We don't have a GCHandle. This means there's no managed instance for this
|
|
// native object.
|
|
// If to_weak is true, then there's obviously nothing to do
|
|
// (why create a managed object which can immediately be freed by the GC?).
|
|
// If to_weak is false, the question is if we want to create the
|
|
// managed object. Bug #30420 says no (previously we didn't, and
|
|
// if we do, managed ctors end up being executed at a different moment,
|
|
// which breaks implicit assumptions in people's code.)
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("Object %p has no managed object to create a %s GCHandle for\n", self, to_weak ? "weak" : "strong");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
|
|
MONO_THREAD_ATTACH; // COOP: will switch to GC_UNSAFE
|
|
|
|
managed_object = xamarin_gchandle_get_target (old_gchandle);
|
|
|
|
if (to_weak) {
|
|
new_gchandle = xamarin_gchandle_new_weakref (managed_object, TRUE);
|
|
flags = (enum XamarinGCHandleFlags) (flags | XamarinGCHandleFlags_WeakGCHandle);
|
|
} else {
|
|
new_gchandle = xamarin_gchandle_new (managed_object, FALSE);
|
|
flags = (enum XamarinGCHandleFlags) (flags & ~XamarinGCHandleFlags_WeakGCHandle);
|
|
}
|
|
|
|
xamarin_gchandle_free (old_gchandle);
|
|
|
|
if (managed_object) {
|
|
// It's possible to not have a managed object if:
|
|
// 1. Objective-C holds a weak reference to the native object (and no other strong references)
|
|
// - in which case the original (old) gchandle would be a weak one.
|
|
// 2. Managed code does not reference the managed object.
|
|
// 3. The GC ran and collected the managed object, but the main thread has not gotten
|
|
// around to release the native object yet.
|
|
// If all these conditions hold, then the original gchandle will point to
|
|
// null, because the target would be collected.
|
|
xamarin_set_nsobject_flags (managed_object, xamarin_get_nsobject_flags (managed_object) | NSObjectFlagsHasManagedRef);
|
|
}
|
|
set_gchandle (self, new_gchandle, flags);
|
|
|
|
MONO_THREAD_DETACH; // COOP: this will switch to GC_SAFE
|
|
|
|
xamarin_mono_object_release (&managed_object);
|
|
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("Switched object %p to %s GCHandle = %d managed object = %p\n", self, to_weak ? "weak" : "strong", new_gchandle, managed_object);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xamarin_free_gchandle (id self, GCHandle gchandle)
|
|
{
|
|
// COOP: no managed memory access, but calls mono function mono_gc_handle_free. Assuming that function can be called with any mode: this function can be called with any mode as well
|
|
if (gchandle) {
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("\tGCHandle %i destroyed for object %p\n", gchandle, self);
|
|
#endif
|
|
xamarin_gchandle_free (gchandle);
|
|
|
|
set_gchandle (self, INVALID_GCHANDLE, XamarinGCHandleFlags_None);
|
|
} else {
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("\tNo GCHandle for the object %p\n", self);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void
|
|
xamarin_clear_gchandle (id self)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
set_gchandle (self, INVALID_GCHANDLE, XamarinGCHandleFlags_None);
|
|
}
|
|
|
|
bool
|
|
xamarin_set_gchandle_with_flags (id self, GCHandle gchandle, enum XamarinGCHandleFlags flags)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
return set_gchandle (self, gchandle, flags);
|
|
}
|
|
|
|
bool
|
|
xamarin_set_gchandle_with_flags_safe (id self, GCHandle gchandle, enum XamarinGCHandleFlags flags)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
return set_gchandle_safe (self, gchandle, flags);
|
|
}
|
|
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
int
|
|
get_safe_retainCount (id self)
|
|
{
|
|
// COOP: no managed memory access: any mode
|
|
if ([self isKindOfClass: [NSCalendar class]] ||
|
|
[self isKindOfClass: [NSInputStream class]] ||
|
|
[self isKindOfClass: [NSOutputStream class]]) {
|
|
// NSCalendar/NSInputStream may end up with a stack overflow where CFGetRetainCount calls itself
|
|
return 666;
|
|
} else {
|
|
return (int) [self retainCount];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// It's fairly frequent (due to various types of coding errors) to have the
|
|
// call to '[self release]' in xamarin_release_managed_ref crash. These
|
|
// crashes are typically very hard to diagnose, because it can be hard to
|
|
// figure out which object caused the crash. So here we store the native
|
|
// object in a static variable, so that it can be read using lldb from a core
|
|
// dump. The variable is declared as volatile so that the compiler doesn't
|
|
// optimize away anything (we want both writes in
|
|
// xamarin_release_managed_ref), and it's attributed with 'unused' because
|
|
// otherwise the compiler complains that the variable is never read.
|
|
//
|
|
// Admittedly the variable should also be thread-local, but that's not
|
|
// supported on all platforms we build for (in particular it requires min
|
|
// iOS 10), and it's also slower than a straight forward write to a static
|
|
// variable. Also while xamarin_release_managed_ref can be called on
|
|
// multiple threads, the vast majority of the calls occur on the main
|
|
// thread, so let's keep the variable global for now and if it turns out
|
|
// to be a problem we can make it thread-local later.
|
|
extern "C" {
|
|
volatile id xamarin_handle_to_be_released __attribute__((unused));
|
|
}
|
|
|
|
void
|
|
xamarin_release_managed_ref (id self, bool user_type)
|
|
{
|
|
// COOP: This is a P/Invoke, so at entry we're in safe mode.
|
|
MONO_ASSERT_GC_SAFE_OR_DETACHED;
|
|
|
|
#if defined(DEBUG_REF_COUNTING)
|
|
PRINT ("monotouch_release_managed_ref (%s Handle=%p) retainCount=%d; HasManagedRef=%i GCHandle=%p IsUserType=%i managed_obj=%p\n",
|
|
class_getName (object_getClass (self)), self, (int32_t) [self retainCount], user_type ? xamarin_has_managed_ref (self) : 666, user_type ? get_gchandle_without_flags (self) : (void*) 666, user_type, managed_obj);
|
|
#endif
|
|
|
|
if (user_type) {
|
|
/* clear MANAGED_REF_BIT */
|
|
set_flags_safe (self, (enum XamarinGCHandleFlags) (get_flags_safe (self) & ~XamarinGCHandleFlags_HasManagedRef));
|
|
} else {
|
|
//
|
|
// This waypoint (lock+unlock) is needed so that we can safely call retainCount in the
|
|
// toggleref callback.
|
|
//
|
|
// The race is between the following actions (given a managed object Z):
|
|
//
|
|
// a1) Thread A nulls out the handle for Z
|
|
// a2) Thread A calls release on Z's original handle
|
|
// b1) Thread B fetches the handle for Z
|
|
// b2) Thread B calls retainCount on Z's handle
|
|
//
|
|
// Possible execution orders:
|
|
//
|
|
// 1) a1-*: all such orders are safe, because b1 will read NULL and
|
|
// b2 won't do anything
|
|
// 2) b1-b2-a1-a2: retainCount before release is safe.
|
|
// 3) b1-a1-b2-a2: retainCount before release is safe.
|
|
// 4) b1-a1-a2-b2: unsafe; this tries to call retainCount after
|
|
// release.
|
|
//
|
|
// Order 4 would look like this:
|
|
//
|
|
// * Thread B runs a GC, and starts calling toggleref callbacks.
|
|
// * Thread B fetches the handle (H) for object Z in a toggleref
|
|
// callback.
|
|
// * Thread A calls xamarin_release_managed_ref for object Z, and
|
|
// calls release on H, deallocating it.
|
|
// * Thread B tries to call retainCount on H, which is now a
|
|
// deallocated object.
|
|
//
|
|
// Solution: lock/unlock the framework peer lock here. This looks
|
|
// weird (since nothing happens inside the lock), but it works:
|
|
//
|
|
// * Thread B runs a GC, locks the framework peer lock, and starts
|
|
// calling toggleref callbacks.
|
|
// * Thread B fetches the handle (H) for object Z in a toggleref
|
|
// callback.
|
|
// * Thread A calls xamarin_release_managed_ref for object Z (and
|
|
// also nulls out the handle for Z)
|
|
// * Thread A tries to lock the framework peer lock, and blocks
|
|
// (before calling release on H)
|
|
// * Thread B successfully calls retainCount on H
|
|
// * Thread B finishes processing all toggleref callbacks, completes
|
|
// the GC, and unlocks the framework peer lock.
|
|
// * Thread A wakes up, and calls release on H.
|
|
//
|
|
// An alternative phrasing would be to say that the lock prevents both
|
|
// a1 and a2 from happening between b1 and b2 from above, thus making
|
|
// order 4 impossible.
|
|
//
|
|
// Q) Why not just unlock after calling release, to avoid the strange-
|
|
// looking empty lock?
|
|
// A) Because calling release on an object might end up calling
|
|
// managed code (the native object can override dealloc and do all
|
|
// sorts of strange things, any of which may end up invoking
|
|
// managed code), and we can deadlock:
|
|
// 1) Thread T calls release on a native object.
|
|
// 2) Thread T executes managed code, which blocks on something
|
|
// that's supposed to happen on another thread U.
|
|
// 3) Thread U causes a garbage collection.
|
|
// 4) Thread U tries to lock the framework peer lock before running
|
|
// the GC, and deadlocks because thread T already has the
|
|
// framework peer lock.
|
|
//
|
|
// This is https://github.com/xamarin/xamarin-macios/issues/3943
|
|
//
|
|
// See also comment in xamarin_marshal_return_value_impl
|
|
xamarin_framework_peer_waypoint_safe ();
|
|
}
|
|
|
|
xamarin_handle_to_be_released = self;
|
|
|
|
[self release];
|
|
|
|
xamarin_handle_to_be_released = NULL;
|
|
}
|
|
|
|
/*
|
|
* Block support
|
|
*/
|
|
|
|
typedef struct {
|
|
MonoMethod *method;
|
|
int par;
|
|
} MethodAndPar;
|
|
|
|
static gboolean
|
|
method_and_par_compare (gconstpointer l, gconstpointer r)
|
|
{
|
|
MethodAndPar *f = (MethodAndPar *)l;
|
|
MethodAndPar *g = (MethodAndPar *) r;
|
|
|
|
return f->method == g->method && f->par == g->par;
|
|
}
|
|
|
|
static unsigned int
|
|
method_and_par_hash (gconstpointer l)
|
|
{
|
|
MethodAndPar *x = (MethodAndPar *) l;
|
|
|
|
return (unsigned int) (intptr_t) x->method;
|
|
}
|
|
|
|
static pthread_mutex_t wrapper_hash_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static MonoReferenceQueue *block_wrapper_queue;
|
|
/*
|
|
* Given a MonoMethod and a parameter, lookup the MethodInfo (MonoReflectionMethod)
|
|
* that can be used to create a new delegate, this returns the method that can
|
|
* create the method
|
|
*/
|
|
static GCHandle
|
|
get_method_block_wrapper_creator (MonoMethod *method, int par, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: accesses managed memory: unsafe mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
GCHandle rv = INVALID_GCHANDLE;
|
|
MonoObject *res = NULL;
|
|
MethodAndPar mp, *nmp;
|
|
mp.method = method;
|
|
mp.par = par;
|
|
|
|
// PRINT ("Looking up method and par (%x and %d)", (int) method, par);
|
|
MONO_ENTER_GC_SAFE;
|
|
pthread_mutex_lock (&wrapper_hash_lock);
|
|
MONO_EXIT_GC_SAFE;
|
|
|
|
if (xamarin_wrapper_hash == NULL) {
|
|
xamarin_wrapper_hash = mono_g_hash_table_new_type (method_and_par_hash, method_and_par_compare, MONO_HASH_VALUE_GC);
|
|
}
|
|
|
|
res = (MonoObject *) mono_g_hash_table_lookup (xamarin_wrapper_hash, &mp);
|
|
pthread_mutex_unlock (&wrapper_hash_lock);
|
|
if (res != NULL){
|
|
rv = xamarin_gchandle_new (res, false);
|
|
xamarin_mono_object_release (&res);
|
|
// PRINT ("Found match: %x", (int) res);
|
|
return rv;
|
|
}
|
|
|
|
MonoReflectionMethod *reflection_method = mono_method_get_object (mono_domain_get (), method, NULL);
|
|
res = xamarin_get_block_wrapper_creator (reflection_method, (int) par, exception_gchandle);
|
|
xamarin_mono_object_release (&reflection_method);
|
|
|
|
if (*exception_gchandle != INVALID_GCHANDLE)
|
|
return INVALID_GCHANDLE;
|
|
// PRINT ("New value: %x", (int) res);
|
|
|
|
nmp = (MethodAndPar *) malloc (sizeof (MethodAndPar));
|
|
*nmp = mp;
|
|
|
|
MONO_ENTER_GC_SAFE;
|
|
pthread_mutex_lock (&wrapper_hash_lock);
|
|
MONO_EXIT_GC_SAFE;
|
|
mono_g_hash_table_insert (xamarin_wrapper_hash, nmp, res);
|
|
pthread_mutex_unlock (&wrapper_hash_lock);
|
|
|
|
rv = xamarin_gchandle_new (res, false);
|
|
xamarin_mono_object_release (&res);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
xamarin_release_block_on_main_thread (void *obj)
|
|
{
|
|
if ([NSThread isMainThread]) {
|
|
_Block_release (obj);
|
|
} else {
|
|
dispatch_async_f (dispatch_get_main_queue (), obj, (dispatch_function_t) _Block_release);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Creates a System.Delegate to wrap an Objective-C proxy when surfacing parameters from Objective-C to C#.
|
|
* @method: method where the parameter is found
|
|
* @par: index of the parameter that is a delegate
|
|
*
|
|
* Given a method, and a parameter index that we have previously probed to be a Delegate,
|
|
* this method returns a strongly typed System.Delegate that wraps the underlying
|
|
* Objective-C block.
|
|
*
|
|
* This works by enlisting the help of the C# runtime to find a [BlockProxy] attrbute
|
|
* on the parameter of the function, or in one of the base definitions. That attribute
|
|
* contains a link to a proxy type that can create the delegate, which we in turn invoke
|
|
*
|
|
* Returns: the instantiated delegate.
|
|
*/
|
|
MonoObject *
|
|
xamarin_get_delegate_for_block_parameter (MonoMethod *method, guint32 token_ref, int par, void *nativeBlock, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: accesses managed memory: unsafe mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoObject *delegate = NULL;
|
|
GCHandle obj_handle = INVALID_GCHANDLE;
|
|
|
|
if (nativeBlock == NULL)
|
|
return NULL;
|
|
|
|
if (token_ref != INVALID_TOKEN_REF) {
|
|
obj_handle = xamarin_get_method_from_token (token_ref, exception_gchandle);
|
|
} else {
|
|
obj_handle = get_method_block_wrapper_creator (method, par, exception_gchandle);
|
|
}
|
|
if (*exception_gchandle != INVALID_GCHANDLE)
|
|
goto cleanup;
|
|
|
|
/* retain or copy (if it's a stack block) the block */
|
|
nativeBlock = _Block_copy (nativeBlock);
|
|
|
|
delegate = xamarin_create_block_proxy (obj_handle, nativeBlock, exception_gchandle);
|
|
if (*exception_gchandle != INVALID_GCHANDLE) {
|
|
_Block_release (nativeBlock);
|
|
delegate = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
MONO_ENTER_GC_SAFE;
|
|
pthread_mutex_lock (&wrapper_hash_lock);
|
|
MONO_EXIT_GC_SAFE;
|
|
|
|
if (block_wrapper_queue == NULL)
|
|
block_wrapper_queue = mono_gc_reference_queue_new (xamarin_release_block_on_main_thread);
|
|
|
|
mono_gc_reference_queue_add (block_wrapper_queue, delegate, nativeBlock);
|
|
pthread_mutex_unlock (&wrapper_hash_lock);
|
|
|
|
cleanup:
|
|
xamarin_gchandle_free (obj_handle);
|
|
return delegate;
|
|
}
|
|
|
|
id
|
|
xamarin_get_block_for_delegate (MonoMethod *method, MonoObject *delegate, const char *signature, guint32 token_ref, GCHandle *exception_gchandle)
|
|
{
|
|
// COOP: accesses managed memory: unsafe mode.
|
|
MonoReflectionMethod *reflection_method = mono_method_get_object (mono_domain_get (), method, NULL);
|
|
id rv = xamarin_create_delegate_proxy (reflection_method, delegate, signature, token_ref, exception_gchandle);
|
|
xamarin_mono_object_release (&reflection_method);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
xamarin_release_static_dictionaries ()
|
|
{
|
|
#if defined (CORECLR_RUNTIME)
|
|
// Release static dictionaries of cached objects. If we end up trying to
|
|
// add objects to these dictionaries after this point (on a background
|
|
// thread), the dictionaries will be re-created (and leak) - which
|
|
// shouldn't be a problem, because at this point the process is about to
|
|
// exit anyway.
|
|
pthread_mutex_lock (&wrapper_hash_lock);
|
|
xamarin_mono_object_release (&block_wrapper_queue);
|
|
xamarin_mono_object_release (&xamarin_wrapper_hash);
|
|
pthread_mutex_unlock (&wrapper_hash_lock);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xamarin_set_use_sgen (bool value)
|
|
{
|
|
}
|
|
|
|
bool
|
|
xamarin_get_use_sgen ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
xamarin_set_gc_pump_enabled (bool value)
|
|
{
|
|
#if DEBUG
|
|
// COOP: no managed memory access: any mode.
|
|
xamarin_gc_pump = value;
|
|
#endif
|
|
}
|
|
|
|
const char *
|
|
xamarin_skip_encoding_flags (const char *encoding)
|
|
{
|
|
// COOP: no managed memory access: any mode.
|
|
while (true) {
|
|
switch (*encoding) {
|
|
case 'r': // const
|
|
case 'n': // in
|
|
case 'N': // inout
|
|
case 'o': // out
|
|
case 'O': // bycopy
|
|
case 'R': // byref
|
|
case 'V': // oneway
|
|
encoding++;
|
|
continue;
|
|
default:
|
|
return encoding;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
xamarin_log_marshalled_exceptions ()
|
|
{
|
|
if (xamarin_log_exceptions == XamarinTriStateNone) {
|
|
const char *var = getenv ("XAMARIN_LOG_MARSHALLED_EXCEPTIONS");
|
|
xamarin_log_exceptions = (var && *var) ? XamarinTriStateEnabled : XamarinTriStateDisabled;
|
|
}
|
|
return xamarin_log_exceptions == XamarinTriStateEnabled;
|
|
}
|
|
|
|
void
|
|
xamarin_log_managed_exception (MonoObject *exception, MarshalManagedExceptionMode mode)
|
|
{
|
|
if (!xamarin_log_marshalled_exceptions ())
|
|
return;
|
|
|
|
GCHandle handle = xamarin_gchandle_new (exception, false);
|
|
NSLog (@PRODUCT ": Processing managed exception for exception marshalling (mode: %i):\n%@", mode, xamarin_print_all_exceptions (handle));
|
|
xamarin_gchandle_free (handle);
|
|
}
|
|
|
|
void
|
|
xamarin_log_objectivec_exception (NSException *exception, MarshalObjectiveCExceptionMode mode)
|
|
{
|
|
if (!xamarin_log_marshalled_exceptions ())
|
|
return;
|
|
|
|
NSLog (@PRODUCT ": Processing Objective-C exception for exception marshalling (mode: %i):\n%@", mode, [exception debugDescription]);
|
|
}
|
|
|
|
void
|
|
xamarin_process_nsexception (NSException *ns_exception)
|
|
{
|
|
xamarin_process_nsexception_using_mode (ns_exception, false, NULL);
|
|
}
|
|
|
|
void
|
|
xamarin_process_nsexception_using_mode (NSException *ns_exception, bool throwManagedAsDefault, GCHandle *output_exception)
|
|
{
|
|
XamarinGCHandle *exc_handle;
|
|
GCHandle exception_gchandle = INVALID_GCHANDLE;
|
|
MarshalObjectiveCExceptionMode mode;
|
|
|
|
mode = xamarin_on_marshal_objectivec_exception (ns_exception, throwManagedAsDefault, &exception_gchandle);
|
|
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Got an exception while executing the MarshalObjectiveCException event (this exception will be ignored):");
|
|
PRINT ("%@", xamarin_print_all_exceptions (exception_gchandle));
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
}
|
|
|
|
if (mode == MarshalObjectiveCExceptionModeDefault)
|
|
#if DOTNET
|
|
mode = MarshalObjectiveCExceptionModeThrowManagedException;
|
|
#else
|
|
mode = xamarin_is_gc_coop ? MarshalObjectiveCExceptionModeThrowManagedException : MarshalObjectiveCExceptionModeUnwindManagedCode;
|
|
#endif
|
|
|
|
xamarin_log_objectivec_exception (ns_exception, mode);
|
|
|
|
switch (mode) {
|
|
case MarshalObjectiveCExceptionModeUnwindManagedCode:
|
|
if (xamarin_is_gc_coop)
|
|
xamarin_assertion_message ("Cannot unwind managed frames for Objective-C exceptions when the GC is in cooperative mode.");
|
|
@throw ns_exception;
|
|
break;
|
|
case MarshalObjectiveCExceptionModeThrowManagedException:
|
|
exc_handle = [[ns_exception userInfo] objectForKey: @"XamarinManagedExceptionHandle"];
|
|
GCHandle handle;
|
|
if (exc_handle != NULL) {
|
|
GCHandle e_handle = [exc_handle getHandle];
|
|
MONO_ENTER_GC_UNSAFE;
|
|
MonoObject *exc = xamarin_gchandle_get_target (e_handle);
|
|
handle = xamarin_gchandle_new (exc, false);
|
|
xamarin_mono_object_release (&exc);
|
|
MONO_EXIT_GC_UNSAFE;
|
|
} else {
|
|
handle = xamarin_create_ns_exception (ns_exception, &exception_gchandle);
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Got an exception while creating a managed NSException wrapper (will throw this exception instead):");
|
|
PRINT ("%@", xamarin_print_all_exceptions (exception_gchandle));
|
|
handle = exception_gchandle;
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
}
|
|
}
|
|
|
|
if (output_exception == NULL) {
|
|
MONO_ENTER_GC_UNSAFE;
|
|
MonoObject *exc = xamarin_gchandle_get_target (handle);
|
|
mono_runtime_set_pending_exception ((MonoException *) exc, false);
|
|
xamarin_mono_object_release (&exc);
|
|
xamarin_gchandle_free (handle);
|
|
MONO_EXIT_GC_UNSAFE;
|
|
} else {
|
|
*output_exception = handle;
|
|
}
|
|
break;
|
|
case MarshalObjectiveCExceptionModeAbort:
|
|
default:
|
|
xamarin_assertion_message ("Aborting due to unhandled Objective-C exception:\n%s\n", [[ns_exception description] UTF8String]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Since this method may not return, it will release the given exception object.
|
|
void
|
|
xamarin_process_managed_exception (MonoObject *exception)
|
|
{
|
|
if (exception == NULL)
|
|
return;
|
|
|
|
MarshalManagedExceptionMode mode;
|
|
GCHandle exception_gchandle = INVALID_GCHANDLE;
|
|
|
|
GCHandle handle = xamarin_gchandle_new (exception, false);
|
|
mode = xamarin_on_marshal_managed_exception (handle, &exception_gchandle);
|
|
xamarin_gchandle_free (handle);
|
|
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Got an exception while executing the MarshalManagedException event (this exception will be ignored):");
|
|
PRINT ("%@", xamarin_print_all_exceptions (exception_gchandle));
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
mode = MarshalManagedExceptionModeDefault;
|
|
}
|
|
|
|
if (mode == MarshalManagedExceptionModeDefault) {
|
|
#if DOTNET
|
|
mode = MarshalManagedExceptionModeThrowObjectiveCException;
|
|
#else
|
|
mode = xamarin_is_gc_coop ? MarshalManagedExceptionModeThrowObjectiveCException : MarshalManagedExceptionModeUnwindNativeCode;
|
|
#endif
|
|
}
|
|
|
|
xamarin_log_managed_exception (exception, mode);
|
|
|
|
switch (mode) {
|
|
#if !defined (CORECLR_RUNTIME) // CoreCLR won't unwind through native frames, so we'll have to abort (in the default case statement)
|
|
case MarshalManagedExceptionModeDisable:
|
|
case MarshalManagedExceptionModeUnwindNativeCode:
|
|
if (xamarin_is_gc_coop)
|
|
xamarin_assertion_message ("Cannot unwind native frames for managed exceptions when the GC is in cooperative mode.");
|
|
|
|
//
|
|
// We want to maintain the original stack trace of the exception, but unfortunately
|
|
// calling mono_raise_exception directly with the original exception will overwrite
|
|
// the original stack trace.
|
|
//
|
|
// The good news is that the managed ExceptionDispatchInfo class is able to capture
|
|
// a stack trace for an exception and show it later.
|
|
//
|
|
// The xamarin_rethrow_managed_exception method will use ExceptionDispatchInfo
|
|
// to throw an exception that contains the original stack trace.
|
|
//
|
|
|
|
handle = xamarin_gchandle_new (exception, false);
|
|
xamarin_rethrow_managed_exception (handle, &exception_gchandle);
|
|
xamarin_gchandle_free (handle);
|
|
|
|
if (exception_gchandle == INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Did not get a rethrow exception, will throw the original exception. The original stack trace will be lost.");
|
|
} else {
|
|
xamarin_mono_object_release (&exception);
|
|
exception = xamarin_gchandle_get_target (exception_gchandle);
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
}
|
|
|
|
mono_raise_exception ((MonoException *) exception);
|
|
#endif
|
|
break;
|
|
case MarshalManagedExceptionModeThrowObjectiveCException: {
|
|
GCHandle handle = xamarin_gchandle_new (exception, false);
|
|
NSException *ns_exc = xamarin_unwrap_ns_exception (handle, &exception_gchandle);
|
|
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Got an exception while unwrapping a managed NSException wrapper (this exception will be ignored):");
|
|
PRINT ("%@", xamarin_print_all_exceptions (exception_gchandle));
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
ns_exc = NULL;
|
|
}
|
|
|
|
if (ns_exc != NULL) {
|
|
xamarin_gchandle_free (handle);
|
|
} else {
|
|
// Strangely enough the thread might be detached, if xamarin_process_managed_exception was called from
|
|
// xamarin_ftnptr_exception_handler for an exception that occurred in a reverse delegate that
|
|
// was called on a detached thread, since in that case the native-to-managed wrapper will have
|
|
// returned the thread to a detached state after calling the managed function.
|
|
NSString *name;
|
|
NSString *reason;
|
|
NSDictionary *userInfo;
|
|
char *fullname;
|
|
MONO_THREAD_ATTACH; // COOP: will switch to GC_UNSAFE
|
|
|
|
fullname = xamarin_get_object_type_fullname (handle, &exception_gchandle);
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Got an exception when trying to get the typename for an exception (this exception will be ignored):");
|
|
PRINT ("%@", xamarin_print_all_exceptions (exception_gchandle));
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
name = @"Unknown type";
|
|
} else {
|
|
name = [NSString stringWithUTF8String: fullname];
|
|
xamarin_free (fullname);
|
|
}
|
|
|
|
reason = xamarin_print_all_exceptions (handle);
|
|
if (exception_gchandle != INVALID_GCHANDLE) {
|
|
PRINT (PRODUCT ": Got an exception when trying to get the message for an exception (this exception will be ignored):");
|
|
PRINT ("%@", xamarin_print_all_exceptions (exception_gchandle));
|
|
xamarin_gchandle_free (exception_gchandle);
|
|
exception_gchandle = INVALID_GCHANDLE;
|
|
reason = @"Unknown message";
|
|
}
|
|
|
|
userInfo = [NSDictionary dictionaryWithObject: [XamarinGCHandle createWithHandle: handle] forKey: @"XamarinManagedExceptionHandle"];
|
|
|
|
MONO_THREAD_DETACH; // COOP: this will switch to GC_SAFE
|
|
|
|
ns_exc = [[NSException alloc] initWithName: name reason: reason userInfo: userInfo];
|
|
}
|
|
|
|
xamarin_mono_object_release (&exception);
|
|
|
|
@throw ns_exc;
|
|
}
|
|
#if defined (CORECLR_RUNTIME)
|
|
case MarshalManagedExceptionModeDisable:
|
|
case MarshalManagedExceptionModeUnwindNativeCode:
|
|
#endif
|
|
case MarshalManagedExceptionModeAbort:
|
|
default:
|
|
handle = xamarin_gchandle_new (exception, false);
|
|
const char *msg = [xamarin_print_all_exceptions (handle) UTF8String];
|
|
xamarin_gchandle_free (handle);
|
|
xamarin_assertion_message ("Aborting due to trying to marshal managed exception:\n%s\n", msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
xamarin_throw_product_exception (int code, const char *message)
|
|
{
|
|
xamarin_process_managed_exception_gchandle (xamarin_create_product_exception (code, message));
|
|
}
|
|
|
|
GCHandle
|
|
xamarin_create_product_exception (int code, const char *message)
|
|
{
|
|
return xamarin_create_product_exception_with_inner_exception (code, 0, message);
|
|
}
|
|
|
|
GCHandle
|
|
xamarin_create_product_exception_with_inner_exception (int code, GCHandle inner_exception_gchandle, const char *message)
|
|
{
|
|
GCHandle exception_gchandle = INVALID_GCHANDLE;
|
|
GCHandle handle = xamarin_create_product_exception_for_error (code, inner_exception_gchandle, message, &exception_gchandle);
|
|
if (exception_gchandle != INVALID_GCHANDLE)
|
|
return exception_gchandle;
|
|
return handle;
|
|
}
|
|
|
|
#if !DOTNET
|
|
void
|
|
xamarin_insert_dllmap ()
|
|
{
|
|
#if defined (OBJC_ZEROCOST_EXCEPTIONS) && (defined (__i386__) || defined (__x86_64__) || defined (__arm64__))
|
|
if (xamarin_marshal_objectivec_exception_mode == MarshalObjectiveCExceptionModeDisable)
|
|
return;
|
|
#if DYLIB
|
|
const char *lib = "libxammac.dylib";
|
|
#else
|
|
const char *lib = "__Internal";
|
|
#endif
|
|
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSend", lib, "xamarin_dyn_objc_msgSend");
|
|
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSendSuper", lib, "xamarin_dyn_objc_msgSendSuper");
|
|
#if !defined (__arm64__)
|
|
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSend_stret", lib, "xamarin_dyn_objc_msgSend_stret");
|
|
mono_dllmap_insert (NULL, "/usr/lib/libobjc.dylib", "objc_msgSendSuper_stret", lib, "xamarin_dyn_objc_msgSendSuper_stret");
|
|
#endif
|
|
LOG (PRODUCT ": Added dllmap for objc_msgSend");
|
|
#endif // defined (__i386__) || defined (__x86_64__) || defined (__arm64__)
|
|
}
|
|
#endif // !DOTNET
|
|
|
|
#if DOTNET
|
|
|
|
// List all the assemblies that we can find in the app bundle in:
|
|
// - The bundle directory
|
|
// - The runtimeidentifier-specific subdirectory
|
|
// Caller must free the return value using xamarin_free.
|
|
char *
|
|
xamarin_compute_trusted_platform_assemblies ()
|
|
{
|
|
const char *bundle_path = xamarin_get_bundle_path ();
|
|
|
|
NSMutableArray<NSString *> *files = [NSMutableArray array];
|
|
NSMutableArray<NSString *> *exes = [NSMutableArray array];
|
|
NSMutableArray<NSString *> *directories = [NSMutableArray array];
|
|
[directories addObject: [NSString stringWithUTF8String: bundle_path]];
|
|
[directories addObject: [NSString stringWithFormat: @"%s/.xamarin/%s", bundle_path, RUNTIMEIDENTIFIER]];
|
|
|
|
NSFileManager *manager = [NSFileManager defaultManager];
|
|
for (NSString *dir in directories) {
|
|
NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:[NSURL fileURLWithPath: dir]
|
|
includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey]
|
|
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
|
|
errorHandler:nil];
|
|
for (NSURL *file in enumerator) {
|
|
// skip subdirectories
|
|
NSNumber *isDirectory = nil;
|
|
if (![file getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil] || [isDirectory boolValue])
|
|
continue;
|
|
|
|
NSString *name = nil;
|
|
if (![file getResourceValue:&name forKey:NSURLNameKey error:nil])
|
|
continue;
|
|
|
|
if ([name length] < 4)
|
|
continue;
|
|
|
|
// We want dlls and exes
|
|
if ([name compare: @".dll" options: NSCaseInsensitiveSearch range: NSMakeRange ([name length] - 4, 4)] == NSOrderedSame) {
|
|
[files addObject: [dir stringByAppendingPathComponent: name]];
|
|
} else if ([name compare: @".exe" options: NSCaseInsensitiveSearch range: NSMakeRange ([name length] - 4, 4)] == NSOrderedSame) {
|
|
[exes addObject: [dir stringByAppendingPathComponent: name]];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Any .exe files must be at the end, due to https://github.com/dotnet/runtime/issues/62735
|
|
[files addObjectsFromArray: exes];
|
|
|
|
// Join them all together with a colon separating them
|
|
NSString *joined = [files componentsJoinedByString: @":"];
|
|
char *rv = xamarin_strdup_printf ("%s", [joined UTF8String]);
|
|
return rv;
|
|
}
|
|
|
|
char *
|
|
xamarin_compute_native_dll_search_directories ()
|
|
{
|
|
const char *bundle_path = xamarin_get_bundle_path ();
|
|
|
|
NSMutableArray<NSString *> *directories = [NSMutableArray array];
|
|
|
|
// Native libraries might be in the app bundle
|
|
[directories addObject: [NSString stringWithUTF8String: bundle_path]];
|
|
// They won't be in the runtimeidentifier-specific directory (because they get lipo'ed into a fat file instead)
|
|
// However, might also be in the Resources/lib directory
|
|
[directories addObject: [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: @"lib"]];
|
|
|
|
// Missing:
|
|
// * The parent app bundle if launched from an app extension. This requires adding app extension tests, which we currently don't have.
|
|
|
|
// Remove the ones that don't exist
|
|
NSFileManager *manager = [NSFileManager defaultManager];
|
|
for (int i = (int) [directories count] - 1; i >= 0; i--) {
|
|
NSString *dir = [directories objectAtIndex: (NSUInteger) i];
|
|
BOOL isDirectory;
|
|
if ([manager fileExistsAtPath: dir isDirectory: &isDirectory] && isDirectory)
|
|
continue;
|
|
|
|
[directories removeObjectAtIndex: (NSUInteger) i];
|
|
}
|
|
|
|
// Join them all together with a colon separating them
|
|
NSString *joined = [directories componentsJoinedByString: @":"];
|
|
char *rv = xamarin_strdup_printf ("%s", [joined UTF8String]);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
xamarin_vm_initialize ()
|
|
{
|
|
char *pinvokeOverride = xamarin_strdup_printf ("%p", &xamarin_pinvoke_override);
|
|
char *icu_dat_file_path = NULL;
|
|
int subtractPropertyCount = 0;
|
|
|
|
if (xamarin_icu_dat_file_name != NULL && *xamarin_icu_dat_file_name != 0) {
|
|
char path [1024];
|
|
if (!xamarin_locate_app_resource (xamarin_icu_dat_file_name, path, sizeof (path))) {
|
|
LOG (PRODUCT ": Could not locate the ICU data file '%s' in the app bundle.\n", xamarin_icu_dat_file_name);
|
|
} else {
|
|
icu_dat_file_path = strdup (path);
|
|
}
|
|
} else {
|
|
subtractPropertyCount++;
|
|
}
|
|
|
|
char *trusted_platform_assemblies = xamarin_compute_trusted_platform_assemblies ();
|
|
char *native_dll_search_directories = xamarin_compute_native_dll_search_directories ();
|
|
|
|
// All the properties we pass here must also be listed in the _RuntimeConfigReservedProperties item group
|
|
// for the _CreateRuntimeConfiguration target in dotnet/targets/Xamarin.Shared.Sdk.targets.
|
|
const char *propertyKeys[] = {
|
|
"APP_CONTEXT_BASE_DIRECTORY", // path to where the managed assemblies are (usually at least - RID-specific assemblies will be in subfolders)
|
|
"APP_PATHS",
|
|
"PINVOKE_OVERRIDE",
|
|
"TRUSTED_PLATFORM_ASSEMBLIES",
|
|
"NATIVE_DLL_SEARCH_DIRECTORIES",
|
|
"RUNTIME_IDENTIFIER",
|
|
"ICU_DAT_FILE_PATH", // Must be last.
|
|
};
|
|
const char *propertyValues[] = {
|
|
xamarin_get_bundle_path (),
|
|
xamarin_get_bundle_path (),
|
|
pinvokeOverride,
|
|
trusted_platform_assemblies,
|
|
native_dll_search_directories,
|
|
RUNTIMEIDENTIFIER,
|
|
icu_dat_file_path, // might be NULL, if so we say we're passing one property less that what we really are (to skip this last one). This also means that this property must be the last one
|
|
};
|
|
static_assert (sizeof (propertyKeys) == sizeof (propertyValues), "The number of keys and values must be the same.");
|
|
|
|
int propertyCount = (int) (sizeof (propertyValues) / sizeof (propertyValues [0])) - subtractPropertyCount;
|
|
bool rv = xamarin_bridge_vm_initialize (propertyCount, propertyKeys, propertyValues);
|
|
xamarin_free (pinvokeOverride);
|
|
|
|
xamarin_free (trusted_platform_assemblies);
|
|
xamarin_free (native_dll_search_directories);
|
|
|
|
if (icu_dat_file_path != NULL)
|
|
free (icu_dat_file_path);
|
|
|
|
if (!rv)
|
|
xamarin_assertion_message ("Failed to initialize the VM");
|
|
}
|
|
|
|
static bool
|
|
xamarin_is_native_library (const char *libraryName)
|
|
{
|
|
if (xamarin_runtime_libraries == NULL)
|
|
return false;
|
|
|
|
size_t libraryNameLength = strlen (libraryName);
|
|
// The libraries in xamarin_runtime_libraries are extension-less, so we need to
|
|
// remove any .dylib extension for the library name we're comparing with too.
|
|
if (libraryNameLength > 6 && strcmp (libraryName + libraryNameLength - 6, ".dylib") == 0)
|
|
libraryNameLength -= 6;
|
|
|
|
bool rv = false;
|
|
for (int i = 0; xamarin_runtime_libraries [i] != NULL; i++) {
|
|
// Check if the start of the current xamarin_runtime_libraries entry matches libraryName
|
|
if (!strncmp (xamarin_runtime_libraries [i], libraryName, libraryNameLength)) {
|
|
// The start matches, now check if that's all there is
|
|
if (xamarin_runtime_libraries [i] [libraryNameLength] == 0) {
|
|
// If so, we've got a match
|
|
rv = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void*
|
|
xamarin_pinvoke_override (const char *libraryName, const char *entrypointName)
|
|
{
|
|
|
|
void* symbol = NULL;
|
|
|
|
if (!strcmp (libraryName, "__Internal")) {
|
|
symbol = dlsym (RTLD_DEFAULT, entrypointName);
|
|
#if !defined (CORECLR_RUNTIME) // we're intercepting objc_msgSend calls using the managed System.Runtime.InteropServices.ObjectiveC.Bridge.SetMessageSendCallback instead.
|
|
#if defined (__i386__) || defined (__x86_64__) || defined (__arm64__)
|
|
} else if (!strcmp (libraryName, "/usr/lib/libobjc.dylib")) {
|
|
if (xamarin_marshal_objectivec_exception_mode != MarshalObjectiveCExceptionModeDisable) {
|
|
if (!strcmp (entrypointName, "objc_msgSend")) {
|
|
symbol = (void *) &xamarin_dyn_objc_msgSend;
|
|
} else if (!strcmp (entrypointName, "objc_msgSendSuper")) {
|
|
symbol = (void *) &xamarin_dyn_objc_msgSendSuper;
|
|
#if !defined (__arm64__)
|
|
} else if (!strcmp (entrypointName, "objc_msgSend_stret")) {
|
|
symbol = (void *) &xamarin_dyn_objc_msgSend_stret;
|
|
} else if (!strcmp (entrypointName, "objc_msgSendSuper_stret")) {
|
|
symbol = (void *) &xamarin_dyn_objc_msgSendSuper_stret;
|
|
#endif // !defined (__arm64__)
|
|
} else {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
return NULL;
|
|
}
|
|
#endif // defined (__i386__) || defined (__x86_64__) || defined (__arm64__)
|
|
#endif // !defined (CORECLR_RUNTIME)
|
|
} else if (xamarin_is_native_library (libraryName)) {
|
|
switch (xamarin_libmono_native_link_mode) {
|
|
case XamarinNativeLinkModeStaticObject:
|
|
// lookup the symbol in loaded memory, like __Internal does.
|
|
symbol = dlsym (RTLD_DEFAULT, entrypointName);
|
|
break;
|
|
case XamarinNativeLinkModeDynamicLibrary:
|
|
// if we're not linking statically, then don't do anything at all, let mono handle whatever needs to be done
|
|
return NULL;
|
|
case XamarinNativeLinkModeFramework:
|
|
default:
|
|
// handle this as "DynamicLibrary" for now - do nothing.
|
|
LOG (PRODUCT ": Unhandled libmono link mode: %i when looking up %s in %s", xamarin_libmono_native_link_mode, entrypointName, libraryName);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
if (symbol == NULL) {
|
|
LOG (PRODUCT ": Unable to resolve P/Invoke '%s' in the library '%s'", entrypointName, libraryName);
|
|
}
|
|
|
|
return symbol;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
xamarin_printf (const char *format, ...)
|
|
{
|
|
va_list list;
|
|
va_start (list, format);
|
|
xamarin_vprintf (format, list);
|
|
va_end (list);
|
|
}
|
|
|
|
void
|
|
xamarin_vprintf (const char *format, va_list args)
|
|
{
|
|
NSString *message = [[NSString alloc] initWithFormat: [NSString stringWithUTF8String: format] arguments: args];
|
|
|
|
#if TARGET_OS_WATCH && defined (__arm__) // maybe make this configurable somehow?
|
|
const char *msg = [message UTF8String];
|
|
NSUInteger len = [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; // does not include NULL
|
|
fwrite (msg, 1, len, stdout);
|
|
if (len == 0 || msg [len - 1] != '\n')
|
|
fwrite ("\n", 1, 1, stdout);
|
|
fflush (stdout);
|
|
#else
|
|
NSLog (@"%@", message);
|
|
#endif
|
|
|
|
[message release];
|
|
}
|
|
|
|
/*
|
|
* File/resource lookup for assemblies
|
|
*
|
|
* Assemblies can be found in several locations:
|
|
* 1. If the assembly is compiled to a framework, in the framework's MonoBundle directory.
|
|
* For extensions the framework may be in the containing app's Frameworks directory.
|
|
* If an extension is sharing code with the main app, then the assemblies whose build target isn't 'framework' will be located in the container app's root directory.
|
|
* A framework may contain multiple assemblies, so it's not possible to deduce the framework name from the assembly name.
|
|
* 2. If the assembly is not a framework, in the app's root directory.
|
|
*
|
|
* The platform assembly (Xamarin.[iOS|TVOS|WatchOS].dll) and any assemblies
|
|
* the platform assembly references (mscorlib.dll, System.dll) may be in a
|
|
* pointer-size subdirectory (ARCH_SUBDIR), or an RID-specific subdirectory.
|
|
*
|
|
* AOT data files will have an arch-specific infix.
|
|
*/
|
|
|
|
void
|
|
xamarin_get_assembly_name_without_extension (const char *aname, char *name, size_t namelen)
|
|
{
|
|
size_t len = strnlen (aname, namelen);
|
|
if (len == namelen)
|
|
return;
|
|
strlcpy (name, aname, namelen);
|
|
if (namelen <= 4 || len <= 4)
|
|
return;
|
|
const char *ext = name + (len - 4);
|
|
if (!strncmp (".exe", ext, 4) || !strncmp (".dll", ext, 4))
|
|
name [len - 4] = 0; // strip off any extensions.
|
|
}
|
|
|
|
bool
|
|
xamarin_locate_app_resource (const char *resource, char *path, size_t pathlen)
|
|
{
|
|
const char *app_path = xamarin_get_bundle_path ();
|
|
return xamarin_locate_assembly_resource_for_root (app_path, NULL, resource, path, pathlen);
|
|
}
|
|
|
|
static bool
|
|
xamarin_locate_assembly_resource_for_root (const char *root, const char *culture, const char *resource, char *path, size_t pathlen)
|
|
{
|
|
if (culture != NULL && *culture != 0) {
|
|
// culture-specific directory
|
|
if (snprintf (path, pathlen, "%s/%s/%s", root, culture, resource) < 0) {
|
|
LOG (PRODUCT ": Failed to construct path for assembly resource (root directory: '%s', culture: '%s', resource: '%s'): %s", root, culture, resource, strerror (errno));
|
|
return false;
|
|
} else if (xamarin_file_exists (path)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// arch-specific extension
|
|
if (snprintf (path, pathlen, "%s/%s.%s", root, resource, xamarin_arch_name) < 0) {
|
|
LOG (PRODUCT ": Failed to construct path for resource: %s (4): %s", resource, strerror (errno));
|
|
return false;
|
|
} else if (xamarin_file_exists (path)) {
|
|
return true;
|
|
}
|
|
|
|
#if !MONOMAC
|
|
// pointer-size subdirectory with arch-specific extension
|
|
if (snprintf (path, pathlen, "%s/%s/%s.%s", root, ARCH_SUBDIR, resource, xamarin_arch_name) < 0) {
|
|
LOG (PRODUCT ": Failed to construct path for resource: %s (5): %s", resource, strerror (errno));
|
|
return false;
|
|
} else if (xamarin_file_exists (path)) {
|
|
return true;
|
|
}
|
|
|
|
// pointer-size subdirectory
|
|
if (snprintf (path, pathlen, "%s/%s/%s", root, ARCH_SUBDIR, resource) < 0) {
|
|
LOG (PRODUCT ": Failed to construct path for resource: %s (5): %s", resource, strerror (errno));
|
|
return false;
|
|
} else if (xamarin_file_exists (path)) {
|
|
return true;
|
|
}
|
|
#endif // !MONOMAC
|
|
|
|
#if DOTNET
|
|
// RID-specific subdirectory
|
|
if (snprintf (path, pathlen, "%s/.xamarin/%s/%s", root, RUNTIMEIDENTIFIER, resource) < 0) {
|
|
LOG (PRODUCT ": Failed to construct path for resource: %s (5): %s", resource, strerror (errno));
|
|
return false;
|
|
} else if (xamarin_file_exists (path)) {
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
// just the file, no extensions, etc.
|
|
if (snprintf (path, pathlen, "%s/%s", root, resource) < 0) {
|
|
LOG (PRODUCT ": Failed to construct path for resource: %s (6): %s", resource, strerror (errno));
|
|
return false;
|
|
} else if (xamarin_file_exists (path)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if !defined (CORECLR_RUNTIME)
|
|
bool
|
|
xamarin_locate_assembly_resource_for_name (MonoAssemblyName *assembly_name, const char *resource, char *path, size_t pathlen)
|
|
{
|
|
const char *culture = mono_assembly_name_get_culture (assembly_name);
|
|
const char *aname = mono_assembly_name_get_name (assembly_name);
|
|
return xamarin_locate_assembly_resource (aname, culture, resource, path, pathlen);
|
|
}
|
|
#endif
|
|
|
|
// #define LOG_RESOURCELOOKUP(...) do { NSLog (@ __VA_ARGS__); } while (0);
|
|
#define LOG_RESOURCELOOKUP(...)
|
|
|
|
|
|
bool
|
|
xamarin_locate_assembly_resource (const char *assembly_name, const char *culture, const char *resource, char *path, size_t pathlen)
|
|
{
|
|
char root [1024];
|
|
char aname [256];
|
|
const char *app_path;
|
|
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Locating the resource '%s' for the assembly '%s' (culture: '%s').", resource, assembly_name, culture);
|
|
|
|
app_path = xamarin_get_bundle_path ();
|
|
|
|
xamarin_get_assembly_name_without_extension (assembly_name, aname, sizeof (aname));
|
|
|
|
// First check if the directory is explicitly set. This directory is relative to the app bundle.
|
|
// For extensions this might be a relative path that points to container app (../../Frameworks/...).
|
|
const char *explicit_location = xamarin_find_assembly_directory (aname);
|
|
if (explicit_location) {
|
|
snprintf (root, sizeof (root), "%s/%s", app_path, explicit_location);
|
|
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from explicit path '%s': %s\n", resource, explicit_location, path);
|
|
return true;
|
|
}
|
|
// If we have an explicit location, then that's where the assembly must be.
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Could not find the resource '%s' for the assembly '%s' (culture: '%s') in the explicit path '%s'.", resource, assembly_name, culture, explicit_location);
|
|
return false;
|
|
}
|
|
|
|
// The root app directory
|
|
if (xamarin_locate_assembly_resource_for_root (app_path, culture, resource, path, pathlen)) {
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from app bundle: %s\n", resource, path);
|
|
return true;
|
|
}
|
|
|
|
#if !MONOMAC && (defined(__i386__) || defined (__x86_64__))
|
|
// In the simulator we also check in a 'simulator' subdirectory. This is
|
|
// so that we can create a framework that works for both simulator and
|
|
// device, without affecting device builds in any way (device-specific
|
|
// files just go in the MonoBundle directory)
|
|
snprintf (root, sizeof (root), "%s/Frameworks/%s.framework/MonoBundle/simulator", app_path, aname);
|
|
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from framework '%s': %s\n", resource, aname, path);
|
|
return true;
|
|
}
|
|
#endif // !MONOMAC
|
|
|
|
// Then in a framework named as the assembly
|
|
snprintf (root, sizeof (root), "%s/Frameworks/%s.framework/MonoBundle", app_path, aname);
|
|
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from framework '%s': %s\n", resource, aname, path);
|
|
return true;
|
|
}
|
|
|
|
// Then in the container app's root directory (for extensions)
|
|
if (xamarin_launch_mode == XamarinLaunchModeExtension) {
|
|
snprintf (root, sizeof (root), "../..");
|
|
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from container app bundle '%s': %s\n", resource, aname, path);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#if TARGET_OS_MACCATALYST
|
|
snprintf (root, sizeof (root), "%s/Contents/%s", app_path, [xamarin_custom_bundle_name UTF8String]);
|
|
if (xamarin_locate_assembly_resource_for_root (root, culture, resource, path, pathlen)) {
|
|
LOG_RESOURCELOOKUP (PRODUCT ": Located resource '%s' from macOS content bundle '%s': %s\n", resource, aname, path);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
xamarin_set_assembly_directories (struct AssemblyLocations *directories)
|
|
{
|
|
if (options.AssemblyLocations)
|
|
xamarin_assertion_message ("Assembly directories already set.");
|
|
|
|
options.AssemblyLocations = directories;
|
|
}
|
|
|
|
static int
|
|
compare_assembly_location (const void *key, const void *value)
|
|
{
|
|
return strcmp ((const char *) key, ((struct AssemblyLocation *) value)->assembly_name);
|
|
}
|
|
|
|
const char *
|
|
xamarin_find_assembly_directory (const char *assembly_name)
|
|
{
|
|
if (options.AssemblyLocations == NULL)
|
|
return NULL;
|
|
|
|
struct AssemblyLocation *entry;
|
|
|
|
entry = (struct AssemblyLocation *) bsearch (assembly_name, options.AssemblyLocations->locations, options.AssemblyLocations->length, sizeof (struct AssemblyLocation), compare_assembly_location);
|
|
|
|
return entry ? entry->location : NULL;
|
|
}
|
|
|
|
MonoMethod *
|
|
xamarin_get_managed_method_for_token (guint32 token_ref, GCHandle *exception_gchandle)
|
|
{
|
|
MonoReflectionMethod *reflection_method;
|
|
|
|
reflection_method = (MonoReflectionMethod *) xamarin_gchandle_unwrap (xamarin_get_method_from_token (token_ref, exception_gchandle));
|
|
if (*exception_gchandle != INVALID_GCHANDLE) return NULL;
|
|
|
|
MonoMethod *rv = xamarin_get_reflection_method_method (reflection_method);
|
|
xamarin_mono_object_release (&reflection_method);
|
|
return rv;
|
|
}
|
|
|
|
GCHandle
|
|
xamarin_gchandle_new (MonoObject *obj, bool pinned)
|
|
{
|
|
#if defined (CORECLR_RUNTIME)
|
|
return xamarin_bridge_create_gchandle (obj == NULL ? INVALID_GCHANDLE : obj->gchandle, pinned ? XamarinGCHandleTypePinned : XamarinGCHandleTypeNormal);
|
|
#else
|
|
return GINT_TO_POINTER (mono_gchandle_new (obj, pinned));
|
|
#endif
|
|
}
|
|
|
|
GCHandle
|
|
xamarin_gchandle_new_weakref (MonoObject *obj, bool track_resurrection)
|
|
{
|
|
#if defined (CORECLR_RUNTIME)
|
|
return xamarin_bridge_create_gchandle (obj == NULL ? INVALID_GCHANDLE : obj->gchandle, track_resurrection ? XamarinGCHandleTypeWeakTrackResurrection : XamarinGCHandleTypeWeak);
|
|
#else
|
|
return GINT_TO_POINTER (mono_gchandle_new_weakref (obj, track_resurrection));
|
|
#endif
|
|
}
|
|
|
|
MonoObject *
|
|
xamarin_gchandle_get_target (GCHandle handle)
|
|
{
|
|
if (handle == INVALID_GCHANDLE)
|
|
return NULL;
|
|
|
|
#if defined (CORECLR_RUNTIME)
|
|
return xamarin_bridge_get_monoobject (handle);
|
|
#else
|
|
return mono_gchandle_get_target (GPOINTER_TO_UINT (handle));
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xamarin_gchandle_free (GCHandle handle)
|
|
{
|
|
if (handle == INVALID_GCHANDLE)
|
|
return;
|
|
#if defined (CORECLR_RUNTIME)
|
|
xamarin_bridge_free_gchandle (handle);
|
|
#else
|
|
mono_gchandle_free (GPOINTER_TO_UINT (handle));
|
|
#endif
|
|
}
|
|
|
|
MonoObject *
|
|
xamarin_gchandle_unwrap (GCHandle handle)
|
|
{
|
|
if (handle == INVALID_GCHANDLE)
|
|
return NULL;
|
|
MonoObject *rv = xamarin_gchandle_get_target (handle);
|
|
xamarin_gchandle_free (handle);
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Object unregistration:
|
|
*
|
|
* This is a difficult problem, because we need to be able
|
|
* to lookup the managed object (given the native object)
|
|
* from inside the dealloc implementation in super classes.
|
|
*
|
|
* Example:
|
|
*
|
|
* MyManagedObject : MyNativeObject : NSObject
|
|
*
|
|
* -[MyNativeObject dealloc] may call selectors, and in
|
|
* that case we need to be able to look up the managed
|
|
* object to invoke the corresponding managed methods.
|
|
*
|
|
* See 02474ac1dd and bug #24609.
|
|
*
|
|
* Note: it is not recommmended to invoke selectors from the
|
|
* dealloc method, but iOS still does it (and third-party
|
|
* libraries as well), so we have to cope with it.
|
|
*
|
|
* At the same time we must also not unregister after
|
|
* the native memory has been freed, because then there's
|
|
* a race condition where another thread can create a
|
|
* new native object with the same pointer and end up in
|
|
* managed code, in which case we might find the managed
|
|
* object for the first native object (which has been freed,
|
|
* but not unregistered yet), and random InvalidCastExceptions
|
|
* occur.
|
|
*
|
|
* See bug #29801.
|
|
*
|
|
* Solution:
|
|
*
|
|
* Fortunately the complete deallocation sequence is documented:
|
|
|
|
01 // The documented Deallocation Timeline (WWDC 2011, Session 322, 36:22).
|
|
02 // 1. -release to zero
|
|
03 // * Object is now deallocating and will die.
|
|
04 // * New __weak references are not allowed, and will get nil.
|
|
05 // * [self dealloc] is called
|
|
06 // 2. Subclass -dealloc
|
|
07 // * bottom-most subclass -dealloc is called
|
|
08 // * Non-ARC code manually releases iVars
|
|
09 // * Walk the super-class chain calling -dealloc
|
|
10 // 3. NSObject -dealloc
|
|
11 // * Simply calls the ObjC runtime object_dispose()
|
|
12 // 4. object_dispose()
|
|
13 // * Call destructors for C++ iVars
|
|
14 // * Call -release for ARC iVars
|
|
15 // * Erase associated references
|
|
16 // * Erase __weak references
|
|
17 // * Call free()
|
|
|
|
* and our solution to the problem is:
|
|
*
|
|
* a) For the static registrar, we execute code at line #13 above, by
|
|
* adding a C++ iVar (with a destructor). This is the XamarinObject
|
|
* type.
|
|
*
|
|
* b) For the dynamic registrar we have to use another solution, because
|
|
* it's not possible to add C++ iVars dynamically. Instead we create
|
|
* a new native object (XamarinAssociatedObject), which we associate
|
|
* with the native object we're tracking, and which will be deallocated
|
|
* when the tracked native object is erasing its associated references
|
|
* (line #15) from above.
|
|
*
|
|
* Associated references is a lot slower and more memory hungry than the C++
|
|
* iVar, which is why we're not using associated references in all cases.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* XamarinObject
|
|
*
|
|
* With the static registrar we add a C++ ivar, and
|
|
* in the destructor we unregister the managed instance.
|
|
*/
|
|
|
|
XamarinObject::~XamarinObject ()
|
|
{
|
|
// COOP: no managed memory access: any mode.
|
|
@try {
|
|
xamarin_notify_dealloc (native_object, gc_handle);
|
|
} @catch (NSException *ex) {
|
|
NSLog (@"%@", ex);
|
|
}
|
|
native_object = NULL;
|
|
gc_handle = INVALID_GCHANDLE;
|
|
}
|
|
|
|
/*
|
|
* XamarinAssociatedObject
|
|
*
|
|
* With the dynamic registrar we associate an instance
|
|
* of this object to every user type, and in this object's
|
|
* dealloc method we unregister the managed instance.
|
|
*/
|
|
|
|
@implementation XamarinAssociatedObject
|
|
-(void) dealloc
|
|
{
|
|
// COOP: no managed memory access: any mode.
|
|
xamarin_notify_dealloc (native_object, gc_handle);
|
|
native_object = NULL;
|
|
gc_handle = INVALID_GCHANDLE;
|
|
[super dealloc];
|
|
}
|
|
@end
|
|
|
|
/*
|
|
* NonXamarinObject category
|
|
*
|
|
* Inject a default xamarinGetGCHandle implementation into every object,
|
|
* so that we don't have to check if a type is a user type before
|
|
* calling xamarinGetGCHandle. TODO: verify if this is really faster than
|
|
* checking the type first.
|
|
*
|
|
* Do not add a xamarinSetGCHandle:flags: method, since we use the presence
|
|
* of it to detect whether a particular type is a user type or not
|
|
* (see Runtime.IsUserType).
|
|
*/
|
|
|
|
@implementation NSObject (NonXamarinObject)
|
|
-(GCHandle) xamarinGetGCHandle
|
|
{
|
|
// COOP: no managed memory access: any mode.
|
|
return INVALID_GCHANDLE;
|
|
}
|
|
|
|
-(enum XamarinGCHandleFlags) xamarinGetFlags
|
|
{
|
|
// COOP: no managed memory access: any mode.
|
|
return XamarinGCHandleFlags_None;
|
|
}
|
|
@end
|
|
|
|
#if MONOMAC
|
|
void
|
|
xamarin_set_is_mkbundle (bool value)
|
|
{
|
|
if (initialize_started)
|
|
xamarin_assertion_message ("Fatal error: xamarin_set_is_mkbundle called after xamarin_initialize.\n");
|
|
|
|
xamarin_is_mkbundle = value;
|
|
}
|
|
|
|
bool
|
|
xamarin_get_is_mkbundle ()
|
|
{
|
|
return xamarin_is_mkbundle;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
xamarin_set_is_debug (bool value)
|
|
{
|
|
if (initialize_started)
|
|
xamarin_assertion_message ("Fatal error: xamarin_set_is_debug called after xamarin_initialize.\n");
|
|
|
|
xamarin_debug_mode = value;
|
|
}
|
|
|
|
bool
|
|
xamarin_get_is_debug ()
|
|
{
|
|
return xamarin_debug_mode;
|
|
}
|
|
|
|
bool
|
|
xamarin_is_managed_exception_marshaling_disabled ()
|
|
{
|
|
#if defined (CORECLR_RUNTIME)
|
|
return false; // never disable exception marshalling for CoreCLR.
|
|
#elif DEBUG
|
|
if (xamarin_is_gc_coop)
|
|
return false;
|
|
|
|
switch (xamarin_marshal_managed_exception_mode) {
|
|
case MarshalManagedExceptionModeDefault:
|
|
// If all of the following are true:
|
|
// * In debug mode
|
|
// * Using the default exception marshaling mode
|
|
// * The debugger is attached
|
|
// Then disable managed exception marshaling.
|
|
return mono_is_debugger_attached ();
|
|
case MarshalManagedExceptionModeDisable:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#if DOTNET && (TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH)
|
|
int
|
|
xamarin_get_runtime_arch ()
|
|
{
|
|
#if TARGET_OS_SIMULATOR
|
|
return 1;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
#endif // DOTNET && (TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH)
|
|
|
|
/*
|
|
* XamarinGCHandle
|
|
*/
|
|
@implementation XamarinGCHandle
|
|
+(XamarinGCHandle *) createWithHandle: (GCHandle) h
|
|
{
|
|
XamarinGCHandle *rv = [[XamarinGCHandle alloc] init];
|
|
rv->handle = h;
|
|
[rv autorelease];
|
|
return rv;
|
|
}
|
|
|
|
-(void) dealloc
|
|
{
|
|
xamarin_gchandle_free (handle);
|
|
[super dealloc];
|
|
}
|
|
|
|
-(GCHandle) getHandle
|
|
{
|
|
return handle;
|
|
}
|
|
@end
|