xamarin-macios/runtime/trampolines.m

584 строки
18 KiB
Mathematica
Исходник Обычный вид История

2016-04-21 15:19:32 +03:00
/* -*- 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 <stdio.h>
#include <objc/objc.h>
#include <objc/runtime.h>
#include <objc/message.h>
#include <Foundation/Foundation.h>
#include <pthread.h>
#include "product.h"
2016-04-21 15:19:32 +03:00
#include "delegates.h"
#include "xamarin/xamarin.h"
#include "slinked-list.h"
#include "trampolines-internal.h"
#include "runtime-internal.h"
2016-04-21 15:19:32 +03:00
//#define DEBUG_REF_COUNTING
static pthread_mutex_t refcount_mutex;
static void
x_init_mutex () __attribute__ ((constructor));
static void
x_init_mutex ()
{
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&refcount_mutex, &attr);
}
void *
xamarin_marshal_return_value (MonoType *mtype, const char *type, MonoObject *retval, bool retain, MonoMethod *method)
{
// COOP: accesses managed memory: unsafe mode.
MONO_ASSERT_GC_UNSAFE;
2016-04-21 15:19:32 +03:00
/* Any changes in this method probably need to be reflected in the static registrar as well */
switch (type [0]) {
case _C_CLASS:
return xamarin_get_class_handle (retval);
case _C_SEL:
return xamarin_get_selector_handle (retval);
case _C_PTR: {
MonoClass *klass = mono_class_from_mono_type (mtype);
if (mono_class_is_delegate (klass)) {
return xamarin_get_block_for_delegate (method, retval);
} else {
return *(void **) mono_object_unbox (retval);
}
}
case _C_ID: {
MonoClass *r_klass = mono_object_get_class ((MonoObject *) retval);
if (r_klass == mono_get_string_class ()) {
char *str = mono_string_to_utf8 ((MonoString *) retval);
NSString *rv = [[NSString alloc] initWithUTF8String:str];
if (!retain)
[rv autorelease];
mono_free (str);
return (void *) rv;
} else if (xamarin_is_class_array (r_klass)) {
MonoClass *e_klass = mono_class_get_element_class (r_klass);
bool is_string = e_klass == mono_get_string_class ();
MonoArray *m_arr = (MonoArray *) retval;
int length = mono_array_length (m_arr);
id *buf = (id *) malloc (sizeof (void *) * length);
NSArray *arr;
int i;
id v;
for (i = 0; i < length; i++) {
MonoObject *value = mono_array_get (m_arr, MonoObject *, i);
if (is_string) {
char *str = mono_string_to_utf8 ((MonoString *) value);
NSString *sv = [[NSString alloc] initWithUTF8String:str];
[sv autorelease];
mono_free (str);
v = sv;
} else {
v = xamarin_get_handle (value);
}
buf[i] = v;
}
arr = [[NSArray alloc] initWithObjects: buf count: length];
free (buf);
if (!retain)
[arr autorelease];
return (void *) arr;
} else if (xamarin_is_class_nsobject (r_klass)) {
id i = xamarin_get_handle (retval);
xamarin_framework_peer_lock ();
[i retain];
xamarin_framework_peer_unlock ();
if (!retain)
[i autorelease];
mt_dummy_use (retval);
return i;
} else if (xamarin_is_class_inativeobject (r_klass)) {
return xamarin_get_handle_for_inativeobject (retval);
} else {
xamarin_assertion_message ("Don't know how to marshal a return value of type '%s.%s'. Please file a bug with a test case at http://bugzilla.xamarin.com\n", mono_class_get_namespace (r_klass), mono_class_get_name (r_klass));
}
}
case _C_CHARPTR:
return (void *) mono_string_to_utf8 ((MonoString *) retval);
case _C_VOID:
return (void *) 0x0;
default:
return *(void **) mono_object_unbox (retval);
}
}
static const char *
get_method_description (Class cls, SEL sel)
{
Protocol **protocols;
unsigned int p_count;
Class p_cls = cls;
struct objc_method_description desc;
while (p_cls) {
protocols = class_copyProtocolList (p_cls, &p_count);
for (unsigned int i = 0; i < p_count; i++) {
desc = protocol_getMethodDescription (protocols [i], sel, YES, !class_isMetaClass (p_cls));
if (desc.types != NULL) {
free (protocols);
return desc.types;
}
}
free (protocols);
p_cls = class_getSuperclass (p_cls);
}
Method method = class_getInstanceMethod (cls, sel);
if (!method)
return NULL;
struct objc_method_description* m_desc;
m_desc = method_getDescription (method);
return m_desc ? m_desc->types : NULL;
}
static int
count_until (const char *desc, char start, char end)
{
// Counts the number of characters until a certain character is found: 'end'
// If the 'start' character is found, nesting is assumed, and an additional
// 'end' character must be found before the function returns.
int i = 1;
int sub = 0;
while (*desc) {
if (start == *desc) {
sub++;
} else if (end == *desc) {
sub--;
if (sub == 0)
return i;
}
i++;
// This is not multi-byte safe...
desc++;
}
fprintf (stderr, PRODUCT ": Unexpected type encoding, did not find end character '%c' in '%s'.", end, desc);
return i;
}
static int
get_type_description_length (const char *desc)
{
int length = 0;
// This function returns the length of the first encoded type string in desc.
switch (desc [0]) {
case _C_ID:
if (desc [1] == '?') {
// Example: [AVAssetImageGenerator generateCGImagesAsynchronouslyForTimes:completionHandler:] = 'v16@0:4@8@?12'
length = 2;
} else {
length = 1;
}
break;
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_BOOL:
case _C_VOID:
case _C_CHARPTR:
length = 1;
break;
case _C_PTR:
length = 1;
// handle broken encoding where simd types don't show up at all
// Example: [GKPath pathWithPoints:count:radius:cyclical:] = '@24@0:4^8L12f16c20'
// Here we assume that we're pointing to a simd type if we find
// a number (i.e. only get the size of what we're pointing to
// if the next character isn't a number).
if (desc [1] < '0' || desc [1] > '9')
length += get_type_description_length (desc + 1);
break;
case _C_ARY_B:
length = count_until (desc, _C_ARY_B, _C_ARY_E);
break;
case _C_UNION_B:
length = count_until (desc, _C_UNION_B, _C_UNION_E);
break;
case _C_STRUCT_B:
length = count_until (desc, _C_STRUCT_B, _C_STRUCT_E);
break;
// 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':
length = 1 + get_type_description_length (desc + 1);
break;
case _C_BFLD:
length = 1;
break;
case _C_UNDEF:
case _C_ATOM:
case _C_VECTOR:
xamarin_assertion_message ("Unhandled type encoding: %s", desc);
break;
default:
xamarin_assertion_message ("Unsupported type encoding: %s", desc);
break;
}
// Every type encoding _may_ be followed by the stack frame offset for that type
while (desc [length] >= '0' && desc [length] <= '9')
length++;
return length;
}
2016-04-21 15:19:32 +03:00
int
xamarin_get_frame_length (id self, SEL sel)
{
if (self == NULL)
return sizeof (void *) * 3; // we might be in objc_msgStret, in which case we'll need to copy three arguments.
// [NSDecimalNumber initWithDecimal:] has this descriptor: "@36@0:8{?=b8b4b1b1b18[8S]}16"
// which NSMethodSignature chokes on: NSInvalidArgumentException Reason: +[NSMethodSignature signatureWithObjCTypes:]: unsupported type encoding spec '{?}'
// So instead parse the description ourselves.
int length = 0;
Class cls = object_getClass (self);
const char *method_description = get_method_description (cls, sel);
const char *desc = method_description;
if (desc == NULL) {
// This happens with [[UITableViewCell appearance] backgroundColor]
@try {
NSMethodSignature *sig = [self methodSignatureForSelector: sel];
length = [sig frameLength];
} @catch (NSException *ex) {
length = sizeof (void *) * 64; // some high-ish number.
fprintf (stderr, PRODUCT ": Failed to calculate the frame size for the method [%s %s] (%s). Using a value of %i instead.\n", class_getName (cls), sel_getName (sel), [[ex description] UTF8String], length);
}
} else {
// The format of the method type encoding is described here: http://stackoverflow.com/a/11492151/183422
// the return type might have a number after it, which is the size of the argument frame
// first get this number (if it's there), and use it as a minimum value for the frame length
int rvlength = get_type_description_length (desc);
int min_length = 0;
if (rvlength > 0) {
const char *min_start = desc + rvlength;
// the number is at the end of the return type encoding, so find any numbers
// at the end of the type encoding.
while (min_start > desc && min_start [-1] >= '0' && min_start [-1] <= '9')
min_start--;
if (min_start < desc + rvlength) {
for (int i = 0; i < desc + rvlength - min_start; i++)
min_length = min_length * 10 + (min_start [i] - '0');
}
}
// fprintf (stderr, "Found desc '%s' for [%s %s] with min frame length %i\n", desc, class_getName (cls), sel_getName (sel), min_length);
// skip the return value.
desc += rvlength;
while (*desc) {
int tl = xamarin_objc_type_size (desc);
// round up to pointer size
if (tl % sizeof (void *) != 0)
tl += sizeof (void *) - (tl % sizeof (void *));
length += tl;
// fprintf (stderr, " argument=%s length=%i totallength=%i\n", desc, tl, length);
desc += get_type_description_length (desc);
}
if (min_length > length) {
// this might happen for methods that take simd types, since those arguments don't show up in the
// method signature encoding at all, but they're still added to the frame size.
// fprintf (stderr, " min length: %i is higher than calculated length: %i for [%s %s] with description %s\n", min_length, length, class_getName (cls), sel_getName (sel), method_description);
length = min_length;
}
}
// we can't detect varargs, so just add 16 more pointer sized arguments to be on the safe-ish side.
length += sizeof (void *) * 16;
2016-04-21 15:19:32 +03:00
return length;
2016-04-21 15:19:32 +03:00
}
static inline void
find_objc_method_implementation (struct objc_super *sup, id self, SEL sel, IMP xamarin_impl)
{
// COOP: does not access managed memory: any mode
2016-04-21 15:19:32 +03:00
Class klass = object_getClass (self);
Class sklass = class_getSuperclass (klass);
IMP imp = class_getMethodImplementation (klass, sel);
IMP simp = class_getMethodImplementation (sklass, sel);
while (imp == simp || simp == xamarin_impl) {
sklass = class_getSuperclass (sklass);
simp = class_getMethodImplementation (sklass, sel);
}
sup->receiver = self;
#if !defined(__cplusplus) && !__OBJC2__
sup->class = sklass;
#else
sup->super_class = sklass;
#endif
}
id
xamarin_invoke_objc_method_implementation (id self, SEL sel, IMP xamarin_impl)
{
// COOP: does not access managed memory: any mode
2016-04-21 15:19:32 +03:00
struct objc_super sup;
find_objc_method_implementation (&sup, self, sel, xamarin_impl);
return objc_msgSendSuper (&sup, sel);
}
#if MONOMAC
id
xamarin_copyWithZone_trampoline1 (id self, SEL sel, NSZone *zone)
{
// COOP: does not access managed memory: any mode
2016-04-21 15:19:32 +03:00
// This is for subclasses that themselves do not implement Copy (NSZone)
id rv;
int gchandle;
struct objc_super sup;
#if defined (DEBUG_REF_COUNTING)
NSLog (@"xamarin_copyWithZone_trampoline1 (%p, %s, %p)\n", self, sel_getName (sel), zone);
#endif
// Clear out our own GCHandle
gchandle = xamarin_get_gchandle_with_flags (self);
if (gchandle != 0)
xamarin_set_gchandle (self, 0);
// Call the base class implementation
id (*invoke) (struct objc_super *, SEL, NSZone*) = (id (*)(struct objc_super *, SEL, NSZone*)) objc_msgSendSuper;
find_objc_method_implementation (&sup, self, sel, (IMP) xamarin_copyWithZone_trampoline1);
rv = invoke (&sup, sel, zone);
// Restore our GCHandle
if (gchandle != 0)
xamarin_set_gchandle (self, gchandle);
return rv;
}
id
xamarin_copyWithZone_trampoline2 (id self, SEL sel, NSZone *zone)
{
// COOP: does not access managed memory: any mode
2016-04-21 15:19:32 +03:00
// This is for subclasses that already implement Copy (NSZone)
id rv;
int gchandle;
#if defined (DEBUG_REF_COUNTING)
NSLog (@"xamarin_copyWithZone_trampoline2 (%p, %s, %p)\n", self, sel_getName (sel), zone);
#endif
// Clear out our own GCHandle
gchandle = xamarin_get_gchandle_with_flags (self);
if (gchandle != 0)
xamarin_set_gchandle (self, 0);
// Call the managed implementation
id (*invoke) (id, SEL, NSZone*) = (id (*)(id, SEL, NSZone*)) xamarin_trampoline;
rv = invoke (self, sel, zone);
// Restore our GCHandle
if (gchandle != 0)
xamarin_set_gchandle (self, gchandle);
return rv;
}
#endif
void
xamarin_release_trampoline (id self, SEL sel)
{
// COOP: does not access managed memory: any mode, but it assumes safe mode upon entry (it takes locks, and doesn't switch to safe mode).
MONO_ASSERT_GC_SAFE;
2016-04-21 15:19:32 +03:00
int ref_count;
bool detach = false;
pthread_mutex_lock (&refcount_mutex);
ref_count = [self retainCount];
#if defined(DEBUG_REF_COUNTING)
NSLog (@"xamarin_release_trampoline (%s Handle=%p) retainCount=%d; HasManagedRef=%i GCHandle=%i\n",
class_getName ([self class]), self, ref_count, xamarin_has_managed_ref (self), xamarin_get_gchandle (self));
#endif
/*
* We need to decide if the gchandle should become a weak one.
* This happens if managed code will end up holding the only ref.
*/
if (ref_count == 2 && xamarin_has_managed_ref_safe (self)) {
2016-04-21 15:19:32 +03:00
xamarin_switch_gchandle (self, true /* weak */);
detach = true;
}
pthread_mutex_unlock (&refcount_mutex);
/* Invoke the real retain method */
xamarin_invoke_objc_method_implementation (self, sel, (IMP) xamarin_release_trampoline);
if (detach)
mono_thread_detach_if_exiting ();
}
void
xamarin_notify_dealloc (id self, int gchandle)
{
// COOP: safe mode upon entry, switches to unsafe when acccessing managed memory.
MONO_ASSERT_GC_SAFE_OR_DETACHED;
/* This is needed because we call into managed code below (xamarin_unregister_nsobject) */
MONO_THREAD_ATTACH; // COOP: This will swith to GC_UNSAFE
2016-04-21 15:19:32 +03:00
/* Object is about to die. Unregister it and free any gchandles we may have */
MonoObject *mobj = mono_gchandle_get_target (gchandle);
#if defined(DEBUG_REF_COUNTING)
NSLog (@"xamarin_notify_dealloc (%p, %i) target: %p\n", self, gchandle, mobj);
#endif
xamarin_free_gchandle (self, gchandle);
xamarin_unregister_nsobject (self, mobj);
MONO_THREAD_DETACH; // COOP: This will switch to GC_SAFE
2016-04-21 15:19:32 +03:00
mono_thread_detach_if_exiting ();
}
id
xamarin_retain_trampoline (id self, SEL sel)
{
// COOP: safe mode upon entry, switches to unsafe when acccessing managed memory.
MONO_ASSERT_GC_SAFE;
2016-04-21 15:19:32 +03:00
pthread_mutex_lock (&refcount_mutex);
#if defined(DEBUG_REF_COUNTING)
int ref_count = [self retainCount];
bool had_managed_ref = xamarin_has_managed_ref (self);
int pre_gchandle = xamarin_get_gchandle (self);
#endif
/*
* We need to make sure we have a strong GCHandle.
* We can not rely the retainCount changing from 1 to 2, since
* we can not monitor all retains (see bug #26532).
* So just always make sure we have a strong GCHandle after a retain.
*/
xamarin_switch_gchandle (self, false /* strong */);
pthread_mutex_unlock (&refcount_mutex);
/* Invoke the real retain method */
self = xamarin_invoke_objc_method_implementation (self, sel, (IMP) xamarin_retain_trampoline);
#if defined(DEBUG_REF_COUNTING)
NSLog (@"xamarin_retain_trampoline (%s Handle=%p) initial retainCount=%d; new retainCount=%d HadManagedRef=%i HasManagedRef=%i old GCHandle=%i new GCHandle=%i\n",
class_getName ([self class]), self, ref_count, (int) [self retainCount], had_managed_ref, xamarin_has_managed_ref (self), pre_gchandle, xamarin_get_gchandle (self));
#endif
return self;
}
// We try to use the associated object API as little as possible, because the API does
// not like recursion (see bug #35017), and it calls retain/release, which we might
// have overridden with our own code that calls these functions. So in addition to
// keeping the gchandle inside the associated object, we also keep it in a hash
// table, so that xamarin_get_gchandle_trampoline does not have to call any
// associated object API to get the gchandle.
static CFMutableDictionaryRef gchandle_hash = NULL;
static pthread_mutex_t gchandle_hash_lock = PTHREAD_MUTEX_INITIALIZER;
static const char *associated_key = "x"; // the string value doesn't matter, only the pointer value.
void
xamarin_set_gchandle_trampoline (id self, SEL sel, int gc_handle)
{
// COOP: Called by ObjC (when the setGCHandle: selector is called on an object).
// COOP: Safe mode upon entry, and doesn't access managed memory, so no need to change.
MONO_ASSERT_GC_SAFE;
2016-04-21 15:19:32 +03:00
/* This is for types registered using the dynamic registrar */
XamarinAssociatedObject *obj;
obj = objc_getAssociatedObject (self, associated_key);
if (obj == NULL && gc_handle != 0) {
obj = [[XamarinAssociatedObject alloc] init];
obj->gc_handle = gc_handle;
obj->native_object = self;
objc_setAssociatedObject (self, associated_key, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[obj release];
}
if (obj != NULL)
obj->gc_handle = gc_handle;
pthread_mutex_lock (&gchandle_hash_lock);
if (gchandle_hash == NULL)
gchandle_hash = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, NULL, NULL);
if (gc_handle == 0) {
CFDictionaryRemoveValue (gchandle_hash, self);
} else {
CFDictionarySetValue (gchandle_hash, self, GINT_TO_POINTER (gc_handle));
}
pthread_mutex_unlock (&gchandle_hash_lock);
}
int
xamarin_get_gchandle_trampoline (id self, SEL sel)
{
// COOP: Called by ObjC (when the getGCHandle selector is called on an object).
// COOP: Safe mode upon entry, and doesn't access managed memory, so no need to switch.
MONO_ASSERT_GC_SAFE;
2016-04-21 15:19:32 +03:00
/* This is for types registered using the dynamic registrar */
int gc_handle = 0;
pthread_mutex_lock (&gchandle_hash_lock);
if (gchandle_hash != NULL)
gc_handle = GPOINTER_TO_INT (CFDictionaryGetValue (gchandle_hash, self));
pthread_mutex_unlock (&gchandle_hash_lock);
return gc_handle;
}