Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2931 lines (2422 sloc) 68.988 kB
/*
* Copyright (c) 2008, Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "ruby/ruby.h"
#include "ruby/node.h"
#include "ruby/encoding.h"
#include <unistd.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <Foundation/Foundation.h>
#if HAVE_BRIDGESUPPORT_FRAMEWORK
# include <BridgeSupport/BridgeSupport.h>
#else
# include "bs.h"
#endif
#include "vm_core.h"
#include "vm.h"
typedef struct {
bs_element_type_t type;
void *value;
VALUE klass;
ffi_type *ffi_type;
} bs_element_boxed_t;
typedef struct {
char *name;
struct st_table *cmethods;
struct st_table *imethods;
} bs_element_indexed_class_t;
static VALUE rb_cBoxed;
static ID rb_ivar_type;
static VALUE bs_const_magic_cookie = Qnil;
static VALUE rb_objc_class_magic_cookie = Qnil;
static struct st_table *bs_constants;
static struct st_table *bs_functions;
static struct st_table *bs_function_syms;
static struct st_table *bs_boxeds;
static struct st_table *bs_classes;
static struct st_table *bs_inf_prot_cmethods;
static struct st_table *bs_inf_prot_imethods;
static struct st_table *bs_cftypes;
#if 0
static char *
rb_objc_sel_to_mid(SEL selector, char *buffer, unsigned buffer_len)
{
size_t s;
char *p;
s = strlcpy(buffer, (const char *)selector, buffer_len);
p = buffer + s - 1;
if (*p == ':')
*p = '\0';
p = buffer;
while ((p = strchr(p, ':')) != NULL) {
*p = '_';
p++;
}
return buffer;
}
#endif
static inline const char *
rb_objc_skip_octype_modifiers(const char *octype)
{
while (true) {
switch (*octype) {
case _C_CONST:
case 'O': /* bycopy */
case 'n': /* in */
case 'o': /* out */
case 'N': /* inout */
case 'V': /* oneway */
octype++;
break;
default:
return octype;
}
}
}
static inline const char *
__iterate_until(const char *type, char end)
{
char begin;
unsigned nested;
begin = *type;
nested = 0;
do {
type++;
if (*type == begin) {
nested++;
}
else if (*type == end) {
if (nested == 0)
return type;
nested--;
}
}
while (YES);
return NULL;
}
static const char *
rb_objc_get_first_type(const char *type, char *buf, size_t buf_len)
{
const char *orig_type;
const char *p;
orig_type = type;
type = rb_objc_skip_octype_modifiers(type);
switch (*type) {
case '\0':
return NULL;
case _C_ARY_B:
type = __iterate_until(type, _C_ARY_E);
break;
case _C_STRUCT_B:
type = __iterate_until(type, _C_STRUCT_E);
break;
case _C_UNION_B:
type = __iterate_until(type, _C_UNION_E);
break;
case _C_PTR:
type++;
buf[0] = _C_PTR;
buf_len -= 1;
return rb_objc_get_first_type(type, buf, buf_len);
}
type++;
p = type;
while (*p >= '0' && *p <= '9') { p++; }
if (buf != NULL) {
size_t len = (long)(type - orig_type);
assert(len < buf_len);
strncpy(buf, orig_type, len);
buf[len] = '\0';
}
return p;
}
static ffi_type *
fake_ary_ffi_type(size_t size, size_t align)
{
static struct st_table *ary_ffi_types = NULL;
ffi_type *type;
unsigned i;
assert(size > 0);
if (ary_ffi_types == NULL) {
ary_ffi_types = st_init_numtable();
GC_ROOT(&ary_ffi_types);
}
if (st_lookup(ary_ffi_types, (st_data_t)size, (st_data_t *)&type))
return type;
type = (ffi_type *)malloc(sizeof(ffi_type));
type->size = size;
type->alignment = align;
type->type = FFI_TYPE_STRUCT;
type->elements = malloc(size * sizeof(ffi_type *));
for (i = 0; i < size; i++)
type->elements[i] = &ffi_type_uchar;
st_insert(ary_ffi_types, (st_data_t)size, (st_data_t)type);
return type;
}
static size_t
get_ffi_struct_size(ffi_type *type)
{
ffi_type **p;
size_t s;
if (type->size > 0)
return type->size;
assert(type->type == FFI_TYPE_STRUCT);
for (s = 0, p = &type->elements[0]; *p != NULL; p++)
s += get_ffi_struct_size(*p);
return s;
}
static ffi_type *
rb_objc_octype_to_ffitype(const char *octype)
{
octype = rb_objc_skip_octype_modifiers(octype);
if (bs_cftypes != NULL && st_lookup(bs_cftypes, (st_data_t)octype, NULL))
octype = "@";
switch (*octype) {
case _C_ID:
case _C_CLASS:
case _C_SEL:
case _C_CHARPTR:
case _C_PTR:
return &ffi_type_pointer;
case _C_BOOL:
case _C_UCHR:
return &ffi_type_uchar;
case _C_CHR:
return &ffi_type_schar;
case _C_SHT:
return &ffi_type_sshort;
case _C_USHT:
return &ffi_type_ushort;
case _C_INT:
return &ffi_type_sint;
case _C_UINT:
return &ffi_type_uint;
case _C_LNG:
return sizeof(int) == sizeof(long)
? &ffi_type_sint : &ffi_type_slong;
#if defined(_C_LNG_LNG)
case _C_LNG_LNG:
return &ffi_type_sint64;
#endif
case _C_ULNG:
return sizeof(unsigned int) == sizeof(unsigned long)
? &ffi_type_uint : &ffi_type_ulong;
#if defined(_C_ULNG_LNG)
case _C_ULNG_LNG:
return &ffi_type_uint64;
#endif
case _C_FLT:
return &ffi_type_float;
case _C_DBL:
return &ffi_type_double;
case _C_ARY_B:
{
#if __LP64__
unsigned long size, align;
#else
unsigned int size, align;
#endif
@try {
NSGetSizeAndAlignment(octype, &size, &align);
}
@catch (id exception) {
rb_raise(rb_eRuntimeError, "can't get size of type `%s': %s",
octype, [[exception description] UTF8String]);
}
if (size > 0)
return fake_ary_ffi_type(size, align);
break;
}
case _C_BFLD:
{
char *type;
long lng;
size_t size;
type = (char *)octype;
lng = strtol(type, &type, 10);
/* while next type is a bit field */
while (*type == _C_BFLD) {
long next_lng;
/* skip over _C_BFLD */
type++;
/* get next bit field length */
next_lng = strtol(type, &type, 10);
/* if spans next word then align to next word */
if ((lng & ~31) != ((lng + next_lng) & ~31))
lng = (lng + 31) & ~31;
/* increment running length */
lng += next_lng;
}
size = (lng + 7) / 8;
if (size > 0) {
if (size == 1)
return &ffi_type_uchar;
else if (size == 2)
return &ffi_type_ushort;
else if (size <= 4)
return &ffi_type_uint;
return fake_ary_ffi_type(size, 0);
}
break;
}
case _C_STRUCT_B:
{
bs_element_boxed_t *bs_boxed;
if (st_lookup(bs_boxeds, (st_data_t)octype,
(st_data_t *)&bs_boxed)) {
bs_element_struct_t *bs_struct =
(bs_element_struct_t *)bs_boxed->value;
unsigned i;
assert(bs_boxed->type == BS_ELEMENT_STRUCT);
if (bs_boxed->ffi_type != NULL)
return bs_boxed->ffi_type;
bs_boxed->ffi_type = (ffi_type *)malloc(sizeof(ffi_type));
bs_boxed->ffi_type->size = 0;
bs_boxed->ffi_type->alignment = 0;
bs_boxed->ffi_type->type = FFI_TYPE_STRUCT;
bs_boxed->ffi_type->elements = malloc(
(bs_struct->fields_count) * sizeof(ffi_type *));
for (i = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *field = &bs_struct->fields[i];
bs_boxed->ffi_type->elements[i] =
rb_objc_octype_to_ffitype(field->type);
}
bs_boxed->ffi_type->elements[bs_struct->fields_count] = NULL;
{
/* Prepare a fake cif, to make sure critical things such
* as the ffi_type size is set.
*/
ffi_cif cif;
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 0, bs_boxed->ffi_type,
NULL);
assert(bs_boxed->ffi_type->size > 0);
}
return bs_boxed->ffi_type;
}
break;
}
case _C_VOID:
return &ffi_type_void;
}
rb_raise(rb_eRuntimeError, "unrecognized octype `%s'", octype);
return NULL;
}
static bool
rb_objc_rval_to_ocid(VALUE rval, void **ocval)
{
if (!rb_special_const_p(rval) && rb_objc_is_non_native(rval)) {
*(id *)ocval = (id)rval;
return true;
}
switch (TYPE(rval)) {
case T_STRING:
case T_ARRAY:
case T_HASH:
case T_OBJECT:
*(id *)ocval = (id)rval;
return true;
case T_CLASS:
case T_MODULE:
*(id *)ocval = (id)RCLASS_OCID(rval);
return true;
case T_NIL:
*(id *)ocval = NULL;
return true;
case T_TRUE:
case T_FALSE:
{
char v = RTEST(rval);
*(id *)ocval = (id)CFNumberCreate(NULL, kCFNumberCharType, &v);
CFMakeCollectable(*(id *)ocval);
return true;
}
case T_FLOAT:
{
double v = RFLOAT_VALUE(rval);
*(id *)ocval = (id)CFNumberCreate(NULL, kCFNumberDoubleType, &v);
CFMakeCollectable(*(id *)ocval);
return true;
}
case T_FIXNUM:
case T_BIGNUM:
{
if (FIXNUM_P(rval)) {
long v = FIX2LONG(rval);
*(id *)ocval = (id)CFNumberCreate(NULL, kCFNumberLongType, &v);
}
else {
#if HAVE_LONG_LONG
long long v = NUM2LL(rval);
*(id *)ocval =
(id)CFNumberCreate(NULL, kCFNumberLongLongType, &v);
#else
long v = NUM2LONG(rval);
*(id *)ocval = (id)CFNumberCreate(NULL, kCFNumberLongType, &v);
#endif
}
CFMakeCollectable(*(id *)ocval);
return true;
}
case T_SYMBOL:
{
ID name = SYM2ID(rval);
*(id *)ocval = (id)CFStringCreateWithCString(NULL, rb_id2name(name),
kCFStringEncodingASCII); /* XXX this is temporary */
CFMakeCollectable(*(id *)ocval);
return true;
}
}
return false;
}
static bool
rb_objc_rval_to_ocsel(VALUE rval, void **ocval)
{
const char *cstr;
switch (TYPE(rval)) {
case T_STRING:
cstr = StringValuePtr(rval);
break;
case T_SYMBOL:
cstr = rb_id2name(SYM2ID(rval));
break;
default:
return false;
}
*(SEL *)ocval = sel_registerName(cstr);
return true;
}
static void *
bs_element_boxed_get_data(bs_element_boxed_t *bs_boxed, VALUE rval,
bool *success)
{
void *data;
assert(bs_boxed->ffi_type != NULL);
if (NIL_P(rval) && bs_boxed->ffi_type == &ffi_type_pointer) {
*success = true;
return NULL;
}
if (rb_obj_is_kind_of(rval, rb_cBoxed) == Qfalse) {
*success = false;
return NULL;
}
Data_Get_Struct(rval, void, data);
if (bs_boxed->type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct;
unsigned i;
bs_struct = (bs_element_struct_t *)bs_boxed->value;
/* Resync the ivars if necessary.
* This is required as a field may nest another structure, which
* could have been modified as a copy in the Ruby world.
*/
for (i = 0; i < bs_struct->fields_count; i++) {
VALUE *v;
v = &((VALUE *)(data + bs_boxed->ffi_type->size))[i];
if (*v != 0) {
char buf[512];
snprintf(buf, sizeof buf, "%s=", bs_struct->fields[i].name);
rb_funcall(rval, rb_intern(buf), 1, *v);
*v = 0;
}
}
}
*success = true;
return data;
}
static void
rb_bs_boxed_assert_ffitype_ok(bs_element_boxed_t *bs_boxed)
{
if (bs_boxed->ffi_type == NULL && bs_boxed->type == BS_ELEMENT_STRUCT) {
/* Make sure the ffi_type is set before use. */
rb_objc_octype_to_ffitype(
((bs_element_struct_t *)bs_boxed->value)->type);
}
assert(bs_boxed->ffi_type != NULL);
}
static VALUE
rb_bs_boxed_new_from_ocdata(bs_element_boxed_t *bs_boxed, void *ocval)
{
void *data;
size_t soffset;
if (ocval == NULL)
return Qnil;
if (bs_boxed->type == BS_ELEMENT_OPAQUE && *(void **)ocval == NULL)
return Qnil;
rb_bs_boxed_assert_ffitype_ok(bs_boxed);
soffset = 0;
if (bs_boxed->type == BS_ELEMENT_STRUCT) {
soffset = ((bs_element_struct_t *)bs_boxed->value)->fields_count
* sizeof(VALUE);
}
data = xmalloc(soffset + bs_boxed->ffi_type->size);
memcpy(data, ocval, bs_boxed->ffi_type->size);
memset(data + bs_boxed->ffi_type->size, 0, soffset);
return Data_Wrap_Struct(bs_boxed->klass, NULL, NULL, data);
}
static long
rebuild_new_struct_ary(ffi_type **elements, VALUE orig, VALUE new)
{
long n = 0;
while ((*elements) != NULL) {
if ((*elements)->type == FFI_TYPE_STRUCT) {
long i, n2;
VALUE tmp;
n2 = rebuild_new_struct_ary((*elements)->elements, orig, new);
tmp = rb_ary_new();
for (i = 0; i < n2; i++) {
if (RARRAY_LEN(orig) == 0)
return 0;
rb_ary_push(tmp, rb_ary_shift(orig));
}
rb_ary_push(new, tmp);
}
elements++;
n++;
}
return n;
}
static void rb_objc_rval_to_ocval(VALUE, const char *, void **);
static void *
rb_objc_rval_to_boxed_data(VALUE rval, bs_element_boxed_t *bs_boxed, bool *ok)
{
void *data;
if (TYPE(rval) == T_ARRAY && bs_boxed->type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct;
long i, n;
size_t pos;
bs_struct = (bs_element_struct_t *)bs_boxed->value;
rb_bs_boxed_assert_ffitype_ok(bs_boxed);
n = RARRAY_LEN(rval);
if (n < bs_struct->fields_count)
rb_raise(rb_eArgError,
"not enough elements in array `%s' to create " \
"structure `%s' (%ld for %d)",
RSTRING_CPTR(rb_inspect(rval)), bs_struct->name, n,
bs_struct->fields_count);
if (n > bs_struct->fields_count) {
VALUE new_rval = rb_ary_new();
VALUE orig = rval;
rval = rb_ary_dup(rval);
rebuild_new_struct_ary(bs_boxed->ffi_type->elements, rval,
new_rval);
n = RARRAY_LEN(new_rval);
if (RARRAY_LEN(rval) != 0 || n != bs_struct->fields_count) {
rb_raise(rb_eArgError,
"too much elements in array `%s' to create " \
"structure `%s' (%ld for %d)",
RSTRING_CPTR(rb_inspect(orig)),
bs_struct->name, RARRAY_LEN(orig),
bs_struct->fields_count);
}
rval = new_rval;
}
pos = bs_struct->fields_count * sizeof(VALUE);
data = xmalloc(bs_boxed->ffi_type->size + pos);
memset(data + bs_boxed->ffi_type->size, 0, pos);
pos = 0;
for (i = 0; i < bs_struct->fields_count; i++) {
VALUE o = RARRAY_AT(rval, i);
char *field_type = bs_struct->fields[i].type;
rb_objc_rval_to_ocval(o, field_type, data + pos);
pos += rb_objc_octype_to_ffitype(field_type)->size;
}
*ok = true;
}
else {
data = bs_element_boxed_get_data(bs_boxed, rval, ok);
}
return data;
}
static void
rb_objc_rval_to_ocval(VALUE rval, const char *octype, void **ocval)
{
bs_element_boxed_t *bs_boxed;
bool ok = true;
octype = rb_objc_skip_octype_modifiers(octype);
if (*octype == _C_VOID)
return;
if (st_lookup(bs_boxeds, (st_data_t)octype, (st_data_t *)&bs_boxed)) {
void *data;
data = rb_objc_rval_to_boxed_data(rval, bs_boxed, &ok);
if (ok) {
if (data == NULL)
*(void **)ocval = NULL;
else {
memcpy(ocval, data, bs_boxed->ffi_type->size);
xfree(data);
}
}
goto bails;
}
if (st_lookup(bs_cftypes, (st_data_t)octype, NULL))
octype = "@";
if (*octype != _C_BOOL) {
if (rval == Qtrue)
rval = INT2FIX(1);
else if (rval == Qfalse)
rval = INT2FIX(0);
}
switch (*octype) {
case _C_ID:
case _C_CLASS:
ok = rb_objc_rval_to_ocid(rval, ocval);
break;
case _C_SEL:
ok = rb_objc_rval_to_ocsel(rval, ocval);
break;
case _C_PTR:
if (NIL_P(rval)) {
*(void **)ocval = NULL;
}
else if (TYPE(rval) == T_STRING) {
*(char **)ocval = StringValuePtr(rval);
}
else if (st_lookup(bs_boxeds, (st_data_t)octype + 1,
(st_data_t *)&bs_boxed)) {
void *data;
data = rb_objc_rval_to_boxed_data(rval, bs_boxed, &ok);
if (ok)
*(void **)ocval = data;
}
else {
ok = false;
}
break;
case _C_UCHR:
*(unsigned char *)ocval = (unsigned char)
NUM2UINT(rb_Integer(rval));
break;
case _C_BOOL:
{
unsigned char v;
switch (TYPE(rval)) {
case T_FALSE:
case T_NIL:
v = 0;
break;
case T_TRUE:
/* All other types should be converted as true, to
* follow the Ruby semantics (where for example any
* integer is always true, even 0)
*/
default:
v = 1;
break;
}
*(unsigned char *)ocval = v;
}
break;
case _C_CHR:
if (TYPE(rval) == T_STRING && RSTRING_CLEN(rval) == 1) {
*(char *)ocval = RSTRING_CPTR(rval)[0];
}
else {
*(char *)ocval = (char) NUM2INT(rb_Integer(rval));
}
break;
case _C_SHT:
*(short *)ocval = (short) NUM2INT(rb_Integer(rval));
break;
case _C_USHT:
*(unsigned short *)ocval =
(unsigned short)NUM2UINT(rb_Integer(rval));
break;
case _C_INT:
*(int *)ocval = (int) NUM2INT(rb_Integer(rval));
break;
case _C_UINT:
*(unsigned int *)ocval = (unsigned int) NUM2UINT(rb_Integer(rval));
break;
case _C_LNG:
*(long *)ocval = (long) NUM2LONG(rb_Integer(rval));
break;
case _C_ULNG:
*(unsigned long *)ocval = (unsigned long)
NUM2ULONG(rb_Integer(rval));
break;
#if HAVE_LONG_LONG
case _C_LNG_LNG:
*(long long *)ocval = (long long) NUM2LL(rb_Integer(rval));
break;
case _C_ULNG_LNG:
*(unsigned long long *)ocval =
(unsigned long long) NUM2ULL(rb_Integer(rval));
break;
#endif
case _C_FLT:
*(float *)ocval = (float) RFLOAT_VALUE(rb_Float(rval));
break;
case _C_DBL:
*(double *)ocval = RFLOAT_VALUE(rb_Float(rval));
break;
case _C_CHARPTR:
{
VALUE str = rb_obj_as_string(rval);
*(char **)ocval = StringValuePtr(str);
}
break;
default:
ok = false;
}
bails:
if (!ok)
rb_raise(rb_eArgError, "can't convert Ruby object `%s' to " \
"Objective-C value of type `%s'",
RSTRING_CPTR(rb_inspect(rval)), octype);
}
VALUE
rb_objc_boot_ocid(id ocid)
{
if (rb_objc_is_non_native((VALUE)ocid)) {
/* Make sure the ObjC class is imported in Ruby. */
rb_objc_import_class(object_getClass(ocid));
}
else if (RBASIC(ocid)->klass == 0) {
/* This pure-Ruby object was created from Objective-C, we need to
* initialize the Ruby bits.
*/
VALUE klass;
klass = rb_objc_import_class(object_getClass(ocid));
RBASIC(ocid)->klass = klass;
RBASIC(ocid)->flags =
klass == rb_cString
? T_STRING
: klass == rb_cArray
? T_ARRAY
: klass == rb_cHash
? T_HASH
: T_OBJECT;
}
return (VALUE)ocid;
}
static void
rb_objc_ocval_to_rbval(void **ocval, const char *octype, VALUE *rbval);
bool
rb_objc_ocid_to_rval(void **ocval, VALUE *rbval)
{
id ocid = *(id *)ocval;
if (ocid == NULL) {
*rbval = Qnil;
}
else {
*rbval = rb_objc_boot_ocid(ocid);
}
return true;
}
static void
rb_objc_ocval_to_rbval(void **ocval, const char *octype, VALUE *rbval)
{
bool ok;
octype = rb_objc_skip_octype_modifiers(octype);
ok = true;
{
bs_element_boxed_t *bs_boxed;
if (st_lookup(bs_boxeds, (st_data_t)octype,
(st_data_t *)&bs_boxed)) {
*rbval = rb_bs_boxed_new_from_ocdata(bs_boxed, ocval);
goto bails;
}
if (st_lookup(bs_cftypes, (st_data_t)octype, NULL))
octype = "@";
}
switch (*octype) {
case _C_ID:
ok = rb_objc_ocid_to_rval(ocval, rbval);
break;
case _C_CLASS:
*rbval = rb_objc_import_class(*(Class *)ocval);
break;
case _C_BOOL:
*rbval = *(bool *)ocval ? Qtrue : Qfalse;
break;
case _C_CHR:
*rbval = INT2NUM(*(char *)ocval);
break;
case _C_UCHR:
*rbval = UINT2NUM(*(unsigned char *)ocval);
break;
case _C_SHT:
*rbval = INT2NUM(*(short *)ocval);
break;
case _C_USHT:
*rbval = UINT2NUM(*(unsigned short *)ocval);
break;
case _C_INT:
*rbval = INT2NUM(*(int *)ocval);
break;
case _C_UINT:
*rbval = UINT2NUM(*(unsigned int *)ocval);
break;
case _C_LNG:
*rbval = INT2NUM(*(long *)ocval);
break;
case _C_ULNG:
*rbval = UINT2NUM(*(unsigned long *)ocval);
break;
case _C_FLT:
*rbval = rb_float_new((double)(*(float *)ocval));
break;
case _C_DBL:
*rbval = rb_float_new(*(double *)ocval);
break;
case _C_SEL:
{
const char *selname = sel_getName(*(SEL *)ocval);
*rbval = rb_str_new2(selname);
}
break;
case _C_CHARPTR:
*rbval = *(void **)ocval == NULL
? Qnil
: rb_str_new2(*(char **)ocval);
break;
case _C_PTR:
if (*(void **)ocval == NULL) {
*rbval = Qnil;
}
else {
/* TODO: wrap C pointers into a specific object */
ok = false;
}
break;
default:
ok = false;
}
bails:
if (!ok)
rb_raise(rb_eArgError, "can't convert C/Objective-C value `%p' " \
"of type `%s' to Ruby object", ocval, octype);
}
static void
rb_objc_exc_raise(id exception)
{
const char *name;
const char *desc;
name = [[exception name] UTF8String];
desc = [[exception reason] UTF8String];
rb_raise(rb_eRuntimeError, "%s: %s", name, desc);
}
static bs_element_method_t *
rb_bs_find_method(Class klass, SEL sel)
{
do {
bs_element_indexed_class_t *bs_class;
bs_element_method_t *bs_method;
if (st_lookup(bs_classes, (st_data_t)class_getName(klass),
(st_data_t *)&bs_class)) {
struct st_table *t = class_isMetaClass(klass)
? bs_class->cmethods : bs_class->imethods;
if (t != NULL
&& st_lookup(t, (st_data_t)sel, (st_data_t *)&bs_method))
return bs_method;
}
klass = class_getSuperclass(klass);
}
while (klass != NULL);
return NULL;
}
static const char *
rb_objc_method_get_type(Method method, unsigned count,
bs_element_method_t *bs_method, int n,
char *type, size_t type_len)
{
if (bs_method != NULL) {
unsigned i;
if (n == -1 && bs_method->retval != NULL)
return bs_method->retval->type;
for (i = 0; i < bs_method->args_count; i++) {
if (bs_method->args[i].index == i
&& bs_method->args[i].type != NULL)
return bs_method->args[i].type;
}
}
if (n == -1) {
method_getReturnType(method, type, type_len);
}
else {
if (n + 2 < count) {
method_getArgumentType(method, n + 2, type, type_len);
}
else {
assert(bs_method->variadic);
return "@"; /* FIXME: should parse the format string if any */
}
}
return type;
}
static VALUE
rb_objc_to_ruby_closure(VALUE rcv, VALUE argv)
{
Method method;
unsigned i, real_count, count;
ffi_type *ffi_rettype, **ffi_argtypes;
void *ffi_ret, **ffi_args;
ffi_cif *cif;
Class klass;
SEL selector;
const char *type;
char buf[128];
id ocrcv;
void *imp;
bs_element_method_t *bs_method;
if (TYPE(argv) != T_ARRAY)
rb_bug("argv isn't an array");
method = class_getInstanceMethod(object_getClass((void *)rcv),
sel_registerName(rb_id2name(rb_frame_this_func())));
assert(method != NULL);
count = method_getNumberOfArguments(method);
assert(count >= 2);
rb_objc_rval_to_ocid(rcv, (void **)&ocrcv);
klass = object_getClass(ocrcv);
selector = method_getName(method);
bs_method = rb_bs_find_method(klass, selector);
real_count = count;
if (bs_method != NULL && bs_method->variadic) {
if (RARRAY_LEN(argv) < count - 2)
rb_raise(rb_eArgError, "wrong number of arguments (%ld for %d)",
RARRAY_LEN(argv), count - 2);
count = RARRAY_LEN(argv) + 2;
}
else if (RARRAY_LEN(argv) != count - 2) {
rb_raise(rb_eArgError, "wrong number of arguments (%ld for %d)",
RARRAY_LEN(argv), count - 2);
}
ffi_argtypes = (ffi_type **)alloca(sizeof(ffi_type *) * (count + 1));
ffi_argtypes[0] = &ffi_type_pointer;
ffi_argtypes[1] = &ffi_type_pointer;
ffi_args = (void **)alloca(sizeof(void *) * (count + 1));
ffi_args[0] = &ocrcv;
ffi_args[1] = &selector;
if ((ruby_current_thread->cfp->flag >> FRAME_MAGIC_MASK_BITS)
& VM_CALL_SUPER_BIT) {
Class sklass;
Method smethod;
sklass = class_getSuperclass(klass);
assert(sklass != NULL);
smethod = class_getInstanceMethod(sklass, selector);
assert(smethod != method);
imp = method_getImplementation(smethod);
}
else {
imp = method == class_getInstanceMethod(klass, selector)
? method_getImplementation(method)
: objc_msgSend; /* alea jacta est */
}
for (i = 0; i < RARRAY_LEN(argv); i++) {
type = rb_objc_method_get_type(method, real_count, bs_method, i, buf,
sizeof buf);
ffi_argtypes[i + 2] = rb_objc_octype_to_ffitype(type);
assert(ffi_argtypes[i + 2]->size > 0);
ffi_args[i + 2] = (void *)alloca(ffi_argtypes[i + 2]->size);
rb_objc_rval_to_ocval(RARRAY_AT(argv, i), type, ffi_args[i + 2]);
}
ffi_argtypes[count] = NULL;
ffi_args[count] = NULL;
type = rb_objc_method_get_type(method, real_count, bs_method, -1, buf,
sizeof buf);
ffi_rettype = rb_objc_octype_to_ffitype(type);
cif = (ffi_cif *)alloca(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, count, ffi_rettype,
ffi_argtypes)
!= FFI_OK)
rb_fatal("can't prepare cif for objc method type `%s'",
method_getTypeEncoding(method));
if (ffi_rettype != &ffi_type_void) {
ffi_ret = (void *)alloca(ffi_rettype->size);
}
else {
ffi_ret = NULL;
}
@try {
ffi_call(cif, FFI_FN(imp), ffi_ret, ffi_args);
}
@catch (id e) {
rb_objc_exc_raise(e);
}
if (ffi_rettype != &ffi_type_void) {
VALUE resp;
rb_objc_ocval_to_rbval(ffi_ret, type, &resp);
return resp;
}
else {
return Qnil;
}
}
#define IGNORE_PRIVATE_OBJC_METHODS 1
static void
rb_ruby_to_objc_closure_handler(ffi_cif *cif, void *resp, void **args,
void *userdata)
{
void *rcv;
SEL sel;
ID mid;
VALUE rrcv, rargs, ret;
unsigned i;
Method method;
char type[128];
rcv = (*(id **)args)[0];
sel = (*(SEL **)args)[1];
method = class_getInstanceMethod(object_getClass(rcv), sel);
assert(method != NULL);
rargs = rb_ary_new();
for (i = 2; i < cif->nargs; i++) {
VALUE val;
method_getArgumentType(method, i, type, sizeof type);
rb_objc_ocval_to_rbval(args[i], type, &val);
rb_ary_push(rargs, val);
}
rb_objc_ocid_to_rval(&rcv, &rrcv);
mid = rb_intern((const char *)sel);
ret = rb_apply(rrcv, mid, rargs);
method_getReturnType(method, type, sizeof type);
rb_objc_rval_to_ocval(ret, type, resp);
}
static void *
rb_ruby_to_objc_closure(const char *octype, unsigned arity, NODE *node)
{
const char *p;
char buf[128];
ffi_type *ret, **args;
ffi_cif *cif;
ffi_closure *closure;
unsigned i;
p = octype;
assert((p = rb_objc_get_first_type(p, buf, sizeof buf)) != NULL);
ret = rb_objc_octype_to_ffitype(buf);
args = (ffi_type **)malloc(sizeof(ffi_type *) * (arity + 2));
i = 0;
while ((p = rb_objc_get_first_type(p, buf, sizeof buf)) != NULL) {
args[i] = rb_objc_octype_to_ffitype(buf);
assert(++i <= arity + 2);
}
cif = (ffi_cif *)malloc(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, arity + 2, ret, args) != FFI_OK)
rb_fatal("can't prepare ruby to objc cif");
closure = (ffi_closure *)malloc(sizeof(ffi_closure));
if (ffi_prep_closure(closure, cif, rb_ruby_to_objc_closure_handler, node)
!= FFI_OK)
rb_fatal("can't prepare ruby to objc closure");
return closure;
}
void
rb_objc_sync_ruby_method(VALUE mod, ID mid, NODE *node, unsigned override)
{
SEL sel;
Class ocklass;
Method method;
char *types;
int arity;
char *mid_str;
IMP imp;
bool direct_override;
/* Do not expose C functions. */
if (bs_functions != NULL
&& mod == CLASS_OF(rb_mKernel)
&& st_lookup(bs_functions, (st_data_t)mid, NULL))
return;
arity = rb_node_arity(node);
mid_str = (char *)rb_id2name(mid);
if (arity < 0) {
//printf("mid %s has negative arity %d\n", mid_str, arity);
return;
}
if (arity == 1 && mid_str[strlen(mid_str) - 1] != ':') {
char buf[100];
snprintf(buf, sizeof buf, "%s:", mid_str);
sel = sel_registerName(buf);
}
else {
sel = sel_registerName(mid_str);
}
ocklass = RCLASS_OCID(mod);
direct_override = false;
method = class_getInstanceMethod(ocklass, sel);
if (method != NULL) {
void *klass;
if (!override)
return;
/* Do not override certain NSObject selectors. */
if (sel == @selector(superclass)
|| sel == @selector(hash)
|| sel == @selector(zone)) {
klass = RCLASS_OCID(rb_cBasicObject);
if (class_getInstanceMethod(klass, sel) == method)
return;
}
if (arity >= 0 && arity + 2 != method_getNumberOfArguments(method)) {
rb_warning("cannot override Objective-C method `%s' in " \
"class `%s' because of an arity mismatch (%d for %d)",
(char *)sel,
class_getName(ocklass),
arity + 2,
method_getNumberOfArguments(method));
return;
}
types = (char *)method_getTypeEncoding(method);
klass = class_getSuperclass(ocklass);
direct_override =
klass == NULL || class_getInstanceMethod(klass, sel) != method;
}
else {
struct st_table *t = class_isMetaClass(ocklass)
? bs_inf_prot_cmethods
: bs_inf_prot_imethods;
if (t == NULL || !st_lookup(t, (st_data_t)sel, (st_data_t *)&types)) {
types = (char *)alloca((arity + 4) * sizeof(char));
types[0] = '@';
types[1] = '@';
types[2] = ':';
memset(&types[3], '@', arity);
types[arity + 3] = '\0';
}
}
// printf("registering sel %s of types %s arity %d to class %s\n",
// (char *)sel, types, arity, class_getName(ocklass));
imp = rb_ruby_to_objc_closure(types, arity, node);
if (method != NULL && direct_override) {
method_setImplementation(method, imp);
}
else {
assert(class_addMethod(ocklass, sel, imp, types));
}
}
static int
__rb_objc_add_ruby_method(ID mid, NODE *body, VALUE mod)
{
if (mid == ID_ALLOCATOR)
return ST_CONTINUE;
if (body == NULL || body->nd_body->nd_body == NULL)
return ST_CONTINUE;
if ((body->nd_body->nd_noex & NOEX_MASK) != NOEX_PUBLIC)
return ST_CONTINUE;
rb_objc_sync_ruby_method(mod, mid, body->nd_body->nd_body, 0);
return ST_CONTINUE;
}
void
rb_objc_sync_ruby_methods(VALUE mod, VALUE klass)
{
for (;;) {
st_foreach(RCLASS_M_TBL(mod), __rb_objc_add_ruby_method,
(st_data_t)klass);
mod = RCLASS_SUPER(mod);
if (mod == 0 || BUILTIN_TYPE(mod) != T_ICLASS)
break;
}
}
static inline unsigned
is_ignored_selector(SEL sel)
{
#if defined(__ppc__)
return sel == (SEL)0xfffef000;
#elif defined(__i386__)
return sel == (SEL)0xfffeb010;
#else
# error Unsupported arch
#endif
}
#if 0
static void
__rb_objc_sync_methods(VALUE mod, Class ocklass)
{
Method *methods;
unsigned int i, count;
char buffer[128];
VALUE imod;
methods = class_copyMethodList(ocklass, &count);
imod = mod;
#if 0
for (;;) {
st_foreach(RCLASS_M_TBL(imod), __rb_objc_add_ruby_method,
(st_data_t)mod);
imod = RCLASS_SUPER(imod);
if (imod == 0 || BUILTIN_TYPE(imod) != T_ICLASS)
break;
}
#endif
for (i = 0; i < count; i++) {
SEL sel;
ID mid;
st_data_t data;
NODE *node;
sel = method_getName(methods[i]);
if (is_ignored_selector(sel))
continue;
#if IGNORE_PRIVATE_OBJC_METHODS
if (*(char *)sel == '_')
continue;
#endif
rb_objc_sel_to_mid(sel, buffer, sizeof buffer);
mid = rb_intern(buffer);
if (rb_method_boundp(mod, mid, 1) == Qtrue)
continue;
node = NEW_CFUNC(rb_objc_to_ruby_closure(methods[i]), -2);
data = (st_data_t)NEW_FBODY(NEW_METHOD(node, mod,
NOEX_WITH_SAFE(NOEX_PUBLIC)), 0);
st_insert(RCLASS_M_TBL(mod), mid, data);
}
free(methods);
}
#endif
NODE *
rb_objc_define_objc_mid_closure(VALUE recv, ID mid, NODE *node)
{
SEL sel;
Class ocklass;
Method method;
VALUE mod;
NODE *data;
Method (*getMethod)(Class, SEL);
assert(mid > 1);
if (node != NULL)
return NULL; /* TODO: verify that there isn't a prior method */
sel = sel_registerName(rb_id2name(mid));
if (!rb_special_const_p(recv) && !rb_objc_is_non_native(recv)
&& TYPE(recv) == T_CLASS) {
mod = recv;
getMethod = class_getClassMethod;
}
else {
mod = CLASS_OF(recv);
getMethod = class_getInstanceMethod;
}
ocklass = RCLASS_OCID(mod);
if (class_isMetaClass(ocklass))
return NULL;
method = (*getMethod)(ocklass, sel);
if (method == NULL || method_getImplementation(method) == NULL)
return NULL; /* recv doesn't respond to this selector */
do {
Class ocsuper = class_getSuperclass(ocklass);
if ((*getMethod)(ocsuper, sel) == NULL) /* != method */
break;
ocklass = ocsuper;
}
while (1);
if (RCLASS(mod)->ocklass != ocklass) {
mod = rb_objc_import_class(ocklass);
if (TYPE(recv) == T_CLASS)
mod = CLASS_OF(mod);
}
/* Already defined. */
node = rb_method_node(mod, mid);
if (node != NULL)
return node;
node = NEW_CFUNC(rb_objc_to_ruby_closure, -2);
data = NEW_FBODY(NEW_METHOD(node, mod,
NOEX_WITH_SAFE(NOEX_PUBLIC)), 0);
rb_add_method_direct(mod, mid, data);
return data->nd_body;
}
#if 0
rb_objc_sync_objc_methods_into(VALUE mod, Class ocklass)
{
/* Load instance methods */
__rb_objc_sync_methods(mod, ocklass);
/* Load class methods */
__rb_objc_sync_methods(rb_singleton_class(mod),
object_getClass((id)ocklass));
}
void
rb_objc_sync_objc_methods(VALUE mod)
{
rb_objc_sync_objc_methods_into(mod, RCLASS_OCID(mod));
}
#endif
VALUE
rb_mod_objc_ancestors(VALUE recv)
{
void *klass;
VALUE ary;
ary = rb_ary_new();
for (klass = RCLASS(recv)->ocklass; klass != NULL;
klass = class_getSuperclass(klass)) {
rb_ary_push(ary, rb_str_new2(class_getName(klass)));
}
return ary;
}
void
rb_objc_methods(VALUE ary, Class ocklass)
{
while (ocklass != NULL) {
unsigned i, count;
Method *methods;
methods = class_copyMethodList(ocklass, &count);
if (methods != NULL) {
for (i = 0; i < count; i++) {
SEL sel = method_getName(methods[i]);
if (is_ignored_selector(sel))
continue;
rb_ary_push(ary, ID2SYM(rb_intern(sel_getName(sel))));
}
free(methods);
}
ocklass = class_getSuperclass(ocklass);
}
rb_funcall(ary, rb_intern("uniq!"), 0);
}
static bool
rb_objc_resourceful(VALUE obj)
{
/* TODO we should export this function in the runtime
* Object#__resourceful__? perhaps?
*/
extern CFTypeID __CFGenericTypeID(void *);
CFTypeID t = __CFGenericTypeID((void *)obj);
if (t > 0) {
extern void *_CFRuntimeGetClassWithTypeID(CFTypeID);
long *d = (long *)_CFRuntimeGetClassWithTypeID(t);
/* first long is version, 4 means resourceful */
if (d != NULL && *d & 4)
return true;
}
return false;
}
static VALUE
bs_function_dispatch(int argc, VALUE *argv, VALUE recv)
{
ID callee;
bs_element_function_t *bs_func;
void *sym;
unsigned i;
ffi_type *ffi_rettype, **ffi_argtypes;
void *ffi_ret, **ffi_args;
ffi_cif *cif;
VALUE resp;
callee = rb_frame_this_func();
assert(callee > 1);
if (!st_lookup(bs_functions, (st_data_t)callee, (st_data_t *)&bs_func))
rb_bug("bridgesupport function `%s' not in cache", rb_id2name(callee));
if (!st_lookup(bs_function_syms, (st_data_t)callee, (st_data_t *)&sym)
|| sym == NULL) {
sym = dlsym(RTLD_DEFAULT, bs_func->name);
if (sym == NULL)
rb_bug("cannot locate symbol for bridgesupport function `%s'",
bs_func->name);
st_insert(bs_function_syms, (st_data_t)callee, (st_data_t)sym);
}
if (argc != bs_func->args_count)
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, bs_func->args_count);
ffi_argtypes = (ffi_type **)alloca(sizeof(ffi_type *) * argc + 1);
ffi_args = (void **)alloca(sizeof(void *) * argc + 1);
for (i = 0; i < argc; i++) {
char *type = bs_func->args[i].type;
ffi_argtypes[i] = rb_objc_octype_to_ffitype(type);
ffi_args[i] = (void *)alloca(ffi_argtypes[i]->size);
rb_objc_rval_to_ocval(argv[i], type, ffi_args[i]);
}
ffi_argtypes[argc] = NULL;
ffi_args[argc] = NULL;
ffi_rettype = bs_func->retval == NULL
? &ffi_type_void
: rb_objc_octype_to_ffitype(bs_func->retval->type);
cif = (ffi_cif *)alloca(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, argc, ffi_rettype, ffi_argtypes)
!= FFI_OK)
rb_fatal("can't prepare cif for function `%s'", bs_func->name);
if (ffi_rettype != &ffi_type_void) {
ffi_ret = (void *)alloca(ffi_rettype->size);
}
else {
ffi_ret = NULL;
}
@try {
ffi_call(cif, FFI_FN(sym), ffi_ret, ffi_args);
}
@catch (id e) {
rb_objc_exc_raise(e);
}
resp = Qnil;
if (ffi_rettype != &ffi_type_void) {
rb_objc_ocval_to_rbval(ffi_ret, bs_func->retval->type, &resp);
if (bs_func->retval->already_retained && !rb_objc_resourceful(resp))
CFMakeCollectable((void *)resp);
}
return resp;
}
VALUE
rb_objc_resolve_const_value(VALUE v, VALUE klass, ID id)
{
void *sym;
bs_element_constant_t *bs_const;
if (v == rb_objc_class_magic_cookie) {
v = rb_objc_import_class(objc_getClass(rb_id2name(id)));
}
else if (v == bs_const_magic_cookie) {
if (!st_lookup(bs_constants, (st_data_t)id, (st_data_t *)&bs_const))
rb_bug("unresolved bridgesupport constant `%s' not in cache",
rb_id2name(id));
sym = dlsym(RTLD_DEFAULT, bs_const->name);
if (sym == NULL)
rb_bug("cannot locate symbol for unresolved bridgesupport " \
"constant `%s'", bs_const->name);
rb_objc_ocval_to_rbval(sym, bs_const->type, &v);
/* To avoid a runtime warning when re-defining the constant, we remove
* its entry from the table before.
*/
klass = rb_cObject;
assert(RCLASS_IV_TBL(klass) != NULL);
assert(st_delete(RCLASS_IV_TBL(klass), (st_data_t*)&id, NULL));
rb_const_set(klass, id, v);
}
return v;
}
static bs_element_boxed_t *
rb_klass_get_bs_boxed(VALUE recv)
{
bs_element_boxed_t *bs_boxed;
VALUE type;
type = rb_ivar_get(recv, rb_ivar_type);
if (NIL_P(type))
rb_bug("cannot get boxed objc type of class `%s'",
rb_class2name(recv));
assert(TYPE(type) == T_STRING);
if (st_lookup(bs_boxeds, (st_data_t)StringValuePtr(type),
(st_data_t *)&bs_boxed)) {
rb_bs_boxed_assert_ffitype_ok(bs_boxed);
return bs_boxed;
}
return NULL;
}
static VALUE
rb_bs_struct_new(int argc, VALUE *argv, VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(recv);
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
void *data;
unsigned i;
size_t pos;
if (argc > 0 && argc != bs_struct->fields_count)
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, bs_struct->fields_count);
pos = bs_struct->fields_count * sizeof(VALUE);
data = (void *)xmalloc(pos + bs_boxed->ffi_type->size);
memset(data, 0, pos + bs_boxed->ffi_type->size);
pos = 0;
for (i = 0; i < argc; i++) {
bs_element_struct_field_t *bs_field =
(bs_element_struct_field_t *)&bs_struct->fields[i];
rb_objc_rval_to_ocval(argv[i], bs_field->type, data + pos);
pos += rb_objc_octype_to_ffitype(bs_field->type)->size;
}
return Data_Wrap_Struct(recv, NULL, NULL, data);
}
static ID
rb_bs_struct_field_ivar_id(void)
{
char ivar_name[128];
int len;
len = snprintf(ivar_name, sizeof ivar_name, "@%s",
rb_id2name(rb_frame_this_func()));
if (ivar_name[len - 1] == '=')
ivar_name[len - 1] = '\0';
return rb_intern(ivar_name);
}
static VALUE
rb_bs_struct_get(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
unsigned i;
const char *ivar_id_str;
void *data;
size_t pos;
/* FIXME we should cache the ivar IDs somewhere in the
* bs_element_struct_fields
*/
ivar_id_str = rb_id2name(rb_bs_struct_field_ivar_id());
ivar_id_str++; /* skip first '@' */
Data_Get_Struct(recv, void, data);
assert(data != NULL);
rb_objc_wb_range(data + bs_boxed->ffi_type->size,
bs_struct->fields_count * sizeof(VALUE));
for (i = 0, pos = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *bs_field =
(bs_element_struct_field_t *)&bs_struct->fields[i];
if (strcmp(ivar_id_str, bs_field->name) == 0) {
VALUE *val;
val = &((VALUE *)(data + bs_boxed->ffi_type->size))[i];
if (*val == 0)
rb_objc_ocval_to_rbval(data + pos, bs_field->type, val);
return *val;
}
pos += rb_objc_octype_to_ffitype(bs_field->type)->size;
}
rb_bug("can't find field `%s' in recv `%s'", ivar_id_str,
RSTRING_CPTR(rb_inspect(recv)));
return Qnil;
}
static VALUE
rb_bs_struct_set(VALUE recv, VALUE value)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
unsigned i;
const char *ivar_id_str;
void *data;
size_t pos;
/* FIXME we should cache the ivar IDs somewhere in the
* bs_element_struct_fields
*/
ivar_id_str = rb_id2name(rb_bs_struct_field_ivar_id());
ivar_id_str++; /* skip first '@' */
Data_Get_Struct(recv, void, data);
assert(data != NULL);
for (i = 0, pos = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *bs_field =
(bs_element_struct_field_t *)&bs_struct->fields[i];
if (strcmp(ivar_id_str, bs_field->name) == 0) {
rb_objc_rval_to_ocval(value, bs_field->type, data + pos);
/* We do not update the cache because `value' may have been
* transformed (ex. fixnum to float).
*/
((VALUE *)(data + bs_boxed->ffi_type->size))[i] = 0;
return value;
}
pos += rb_objc_octype_to_ffitype(bs_field->type)->size;
}
rb_bug("can't find field `%s' in recv `%s'", ivar_id_str,
RSTRING_CPTR(rb_inspect(recv)));
return Qnil;
}
static VALUE
rb_bs_struct_to_a(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
VALUE ary;
unsigned i;
ary = rb_ary_new();
for (i = 0; i < bs_struct->fields_count; i++) {
VALUE obj;
obj = rb_funcall(recv, rb_intern(bs_struct->fields[i].name), 0, NULL);
rb_ary_push(ary, obj);
}
return ary;
}
static VALUE
rb_bs_boxed_is_equal(VALUE recv, VALUE other)
{
bs_element_boxed_t *bs_boxed;
bool ok;
void *d1, *d2;
if (recv == other)
return Qtrue;
if (rb_obj_is_kind_of(other, rb_cBoxed) == Qfalse)
return Qfalse;
bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
if (bs_boxed != rb_klass_get_bs_boxed(CLASS_OF(other)))
return Qfalse;
d1 = bs_element_boxed_get_data(bs_boxed, recv, &ok);
if (!ok)
rb_raise(rb_eRuntimeError, "can't retrieve data for boxed `%s'",
RSTRING_CPTR(rb_inspect(recv)));
d2 = bs_element_boxed_get_data(bs_boxed, other, &ok);
if (!ok)
rb_raise(rb_eRuntimeError, "can't retrieve data for boxed `%s'",
RSTRING_CPTR(rb_inspect(recv)));
if (d1 == d2)
return Qtrue;
else if (d1 == NULL || d2 == NULL)
return Qfalse;
return memcmp(d1, d2, bs_boxed->ffi_type->size) == 0 ? Qtrue : Qfalse;
}
static VALUE
rb_bs_struct_dup(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
void *data;
bool ok;
data = bs_element_boxed_get_data(bs_boxed, recv, &ok);
if (!ok)
rb_raise(rb_eRuntimeError, "can't retrieve data for boxed `%s'",
RSTRING_CPTR(rb_inspect(recv)));
if (data == NULL)
return Qnil;
return rb_bs_boxed_new_from_ocdata(bs_boxed, data);
}
static VALUE
rb_bs_struct_inspect(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
unsigned i;
VALUE str;
str = rb_str_new2("#<");
rb_str_cat2(str, rb_obj_classname(recv));
if (!bs_struct->opaque) {
for (i = 0; i < bs_struct->fields_count; i++) {
VALUE obj;
obj = rb_funcall(recv, rb_intern(bs_struct->fields[i].name),
0, NULL);
rb_str_cat2(str, " ");
rb_str_cat2(str, bs_struct->fields[i].name);
rb_str_cat2(str, "=");
rb_str_append(str, rb_inspect(obj));
}
}
rb_str_cat2(str, ">");
return str;
}
static VALUE
rb_boxed_objc_type(VALUE recv)
{
char *type;
bs_element_boxed_t *bs_boxed;
bs_boxed = rb_klass_get_bs_boxed(recv);
type = bs_boxed->type == BS_ELEMENT_OPAQUE
? ((bs_element_opaque_t *)bs_boxed->value)->type
: ((bs_element_struct_t *)bs_boxed->value)->type;
return rb_str_new2(type);
}
static VALUE
rb_boxed_is_opaque(VALUE recv)
{
bs_element_boxed_t *bs_boxed;
bs_boxed = rb_klass_get_bs_boxed(recv);
if (bs_boxed->type == BS_ELEMENT_OPAQUE)
return Qtrue;
return ((bs_element_struct_t *)bs_boxed->value)->opaque ? Qtrue : Qfalse;
}
static VALUE
rb_boxed_fields(VALUE recv)
{
bs_element_boxed_t *bs_boxed;
VALUE ary;
unsigned i;
bs_boxed = rb_klass_get_bs_boxed(recv);
ary = rb_ary_new();
if (bs_boxed->type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct;
bs_struct = (bs_element_struct_t *)bs_boxed->value;
for (i = 0; i < bs_struct->fields_count; i++)
rb_ary_push(ary, ID2SYM(rb_intern(bs_struct->fields[i].name)));
}
return ary;
}
static void
setup_bs_boxed_type(bs_element_type_t type, void *value)
{
bs_element_boxed_t *bs_boxed;
VALUE klass;
struct __bs_boxed {
char *name;
char *type;
} *p;
ffi_type *bs_ffi_type;
p = (struct __bs_boxed *)value;
klass = rb_define_class(p->name, rb_cBoxed);
assert(!NIL_P(klass));
rb_ivar_set(klass, rb_ivar_type, rb_str_new2(p->type));
if (type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct = (bs_element_struct_t *)value;
char buf[128];
int i;
/* Needs to be lazily created, because the type of some fields
* may not be registered yet.
*/
bs_ffi_type = NULL;
if (!bs_struct->opaque) {
for (i = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *field = &bs_struct->fields[i];
rb_define_method(klass, field->name, rb_bs_struct_get, 0);
strlcpy(buf, field->name, sizeof buf);
strlcat(buf, "=", sizeof buf);
rb_define_method(klass, buf, rb_bs_struct_set, 1);
}
rb_define_method(klass, "to_a", rb_bs_struct_to_a, 0);
}
rb_define_singleton_method(klass, "new", rb_bs_struct_new, -1);
rb_define_method(klass, "dup", rb_bs_struct_dup, 0);
rb_define_alias(klass, "clone", "dup");
rb_define_method(klass, "inspect", rb_bs_struct_inspect, 0);
}
else {
rb_undef_alloc_func(klass);
rb_undef_method(CLASS_OF(klass), "new");
bs_ffi_type = &ffi_type_pointer;
}
rb_define_method(klass, "==", rb_bs_boxed_is_equal, 1);
bs_boxed = (bs_element_boxed_t *)malloc(sizeof(bs_element_boxed_t));
bs_boxed->type = type;
bs_boxed->value = value;
bs_boxed->klass = klass;
bs_boxed->ffi_type = bs_ffi_type;
st_insert(bs_boxeds, (st_data_t)p->type, (st_data_t)bs_boxed);
}
static inline ID
generate_const_name(char *name)
{
ID id;
if (islower(name[0])) {
name[0] = toupper(name[0]);
id = rb_intern(name);
name[0] = tolower(name[0]);
return id;
}
else {
return rb_intern(name);
}
}
static void
bs_parse_cb(const char *path, bs_element_type_t type, void *value, void *ctx)
{
bool do_not_free = false;
switch (type) {
case BS_ELEMENT_ENUM:
{
bs_element_enum_t *bs_enum = (bs_element_enum_t *)value;
ID name = generate_const_name(bs_enum->name);
if (!rb_const_defined(rb_cObject, name)) {
VALUE val = strchr(bs_enum->value, '.') != NULL
? rb_float_new(rb_cstr_to_dbl(bs_enum->value, 1))
: rb_cstr_to_inum(bs_enum->value, 10, 1);
rb_const_set(rb_cObject, name, val);
}
else {
rb_warning("bs: enum `%s' already defined", rb_id2name(name));
}
break;
}
case BS_ELEMENT_CONSTANT:
{
bs_element_constant_t *bs_const = (bs_element_constant_t *)value;
ID name = generate_const_name(bs_const->name);
if (!rb_const_defined(rb_cObject, name)) {
st_insert(bs_constants, (st_data_t)name, (st_data_t)bs_const);
rb_const_set(rb_cObject, name, bs_const_magic_cookie);
do_not_free = true;
}
else {
rb_warning("bs: constant `%s' already defined",
rb_id2name(name));
}
break;
}
case BS_ELEMENT_STRING_CONSTANT:
{
bs_element_string_constant_t *bs_strconst =
(bs_element_string_constant_t *)value;
ID name = generate_const_name(bs_strconst->name);
if (!rb_const_defined(rb_cObject, name)) {
VALUE val;
if (bs_strconst->nsstring) {
CFStringRef string;
string = CFStringCreateWithCString(
NULL, bs_strconst->value, kCFStringEncodingUTF8);
val = (VALUE)string;
}
else {
val = rb_str_new2(bs_strconst->value);
}
rb_const_set(rb_cObject, name, val);
}
else {
rb_warning("bs: string constant `%s' already defined",
rb_id2name(name));
}
break;
}
case BS_ELEMENT_FUNCTION:
{
bs_element_function_t *bs_func = (bs_element_function_t *)value;
ID name = rb_intern(bs_func->name);
if (1) {
st_insert(bs_functions, (st_data_t)name, (st_data_t)bs_func);
/* FIXME we should reuse the same node for all functions */
rb_define_global_function(
bs_func->name, bs_function_dispatch, -1);
do_not_free = true;
}
else {
rb_warning("bs: function `%s' already defined", bs_func->name);
}
break;
}
case BS_ELEMENT_FUNCTION_ALIAS:
{
bs_element_function_alias_t *bs_func_alias =
(bs_element_function_alias_t *)value;
rb_define_alias(CLASS_OF(rb_mKernel), bs_func_alias->name,
bs_func_alias->original);
break;
}
case BS_ELEMENT_OPAQUE:
case BS_ELEMENT_STRUCT:
{
setup_bs_boxed_type(type, value);
do_not_free = true;
break;
}
case BS_ELEMENT_CLASS:
{
bs_element_class_t *bs_class = (bs_element_class_t *)value;
bs_element_indexed_class_t *bs_class_new;
unsigned i;
bs_class_new = (bs_element_indexed_class_t *)
malloc(sizeof(bs_element_indexed_class_t));
bs_class_new->name = bs_class->name;
#define INDEX_METHODS(table, ary, len) \
do { \
if (len > 0) { \
table = st_init_numtable(); \
rb_objc_retain(table); \
for (i = 0; i < len; i++) { \
bs_element_method_t *method = &ary[i]; \
st_insert(table, (st_data_t)method->name, (st_data_t)method); \
} \
} \
else { \
table = NULL; \
} \
} \
while (0)
INDEX_METHODS(bs_class_new->cmethods, bs_class->class_methods,
bs_class->class_methods_count);
INDEX_METHODS(bs_class_new->imethods, bs_class->instance_methods,
bs_class->instance_methods_count);
#undef INDEX_METHODS
st_insert(bs_classes, (st_data_t)bs_class_new->name,
(st_data_t)bs_class_new);
free(bs_class);
do_not_free = true;
break;
}
case BS_ELEMENT_INFORMAL_PROTOCOL_METHOD:
{
bs_element_informal_protocol_method_t *bs_inf_prot_method =
(bs_element_informal_protocol_method_t *)value;
struct st_table *t = bs_inf_prot_method->class_method
? bs_inf_prot_cmethods
: bs_inf_prot_imethods;
st_insert(t, (st_data_t)bs_inf_prot_method->name,
(st_data_t)bs_inf_prot_method->type);
free(bs_inf_prot_method->protocol_name);
free(bs_inf_prot_method);
do_not_free = true;
break;
}
case BS_ELEMENT_CFTYPE:
{
bs_element_cftype_t *bs_cftype = (bs_element_cftype_t *)value;
st_insert(bs_cftypes, (st_data_t)bs_cftype->type,
(st_data_t)bs_cftype);
do_not_free = true;
break;
}
}
if (!do_not_free)
bs_element_free(type, value);
}
static VALUE
rb_objc_load_bs(VALUE recv, VALUE path)
{
char *error;
if (!bs_parse(StringValuePtr(path), 0, bs_parse_cb, NULL, &error))
rb_raise(rb_eRuntimeError, error);
return recv;
}
static void
load_bridge_support(const char *framework_path)
{
char path[PATH_MAX];
char *error;
if (bs_find_path(framework_path, path, sizeof path)) {
if (!bs_parse(path, BS_PARSE_OPTIONS_LOAD_DYLIBS, bs_parse_cb, NULL,
&error))
rb_raise(rb_eRuntimeError, error);
}
}
static void
reload_class_constants(void)
{
static int class_count = 0;
int i, count;
Class *buf;
count = objc_getClassList(NULL, 0);
if (count == class_count)
return;
buf = (Class *)alloca(sizeof(Class) * count);
objc_getClassList(buf, count);
for (i = 0; i < count; i++) {
const char *name = class_getName(buf[i]);
if (name[0] != '_') {
ID id = rb_intern(name);
if (!rb_const_defined(rb_cObject, id))
rb_const_set(rb_cObject, id, rb_objc_class_magic_cookie);
}
}
class_count = count;
}
VALUE
rb_require_framework(int argc, VALUE *argv, VALUE recv)
{
VALUE framework;
VALUE search_network;
const char *cstr;
NSFileManager *fileManager;
NSString *path;
NSBundle *bundle;
NSError *error;
rb_scan_args(argc, argv, "11", &framework, &search_network);
Check_Type(framework, T_STRING);
cstr = RSTRING_CPTR(framework);
fileManager = [NSFileManager defaultManager];
path = [fileManager stringWithFileSystemRepresentation:cstr
length:strlen(cstr)];
if (![fileManager fileExistsAtPath:path]) {
/* framework name is given */
NSSearchPathDomainMask pathDomainMask;
NSString *frameworkName;
NSArray *dirs;
NSUInteger i, count;
cstr = NULL;
#define FIND_LOAD_PATH_IN_LIBRARY(dir) \
do { \
path = [[dir stringByAppendingPathComponent:@"Frameworks"] \
stringByAppendingPathComponent:frameworkName]; \
if ([fileManager fileExistsAtPath:path]) \
goto success; \
path = [[dir stringByAppendingPathComponent:@"PrivateFrameworks"] \
stringByAppendingPathComponent:frameworkName]; \
if ([fileManager fileExistsAtPath:path]) \
goto success; \
} \
while(0)
pathDomainMask = RTEST(search_network)
? NSAllDomainsMask
: NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask;
frameworkName = [path stringByAppendingPathExtension:@"framework"];
path = [[[[NSBundle mainBundle] bundlePath]
stringByAppendingPathComponent:@"Contents/Frameworks"]
stringByAppendingPathComponent:frameworkName];
if ([fileManager fileExistsAtPath:path])
goto success;
dirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
pathDomainMask, YES);
for (i = 0, count = [dirs count]; i < count; i++) {
NSString *dir = [dirs objectAtIndex:i];
FIND_LOAD_PATH_IN_LIBRARY(dir);
}
dirs = NSSearchPathForDirectoriesInDomains(NSDeveloperDirectory,
pathDomainMask, YES);
for (i = 0, count = [dirs count]; i < count; i++) {
NSString *dir = [[dirs objectAtIndex:i]
stringByAppendingPathComponent:@"Library"];
FIND_LOAD_PATH_IN_LIBRARY(dir);
}
#undef FIND_LOAD_PATH_IN_LIBRARY
rb_raise(rb_eRuntimeError, "framework `%s' not found",
RSTRING_CPTR(framework));
}
success:
if (cstr == NULL)
cstr = [path fileSystemRepresentation];
bundle = [NSBundle bundleWithPath:path];
if (bundle == nil)
rb_raise(rb_eRuntimeError,
"framework at path `%s' cannot be located",
cstr);
if ([bundle isLoaded])
return Qfalse;
if (![bundle loadAndReturnError:&error]) {
rb_raise(rb_eRuntimeError,
"framework at path `%s' cannot be loaded: %s",
cstr,
[[error description] UTF8String]);
}
load_bridge_support(cstr);
reload_class_constants();
return Qtrue;
}
static const char *
imp_rb_boxed_objCType(void *rcv, SEL sel)
{
VALUE klass, type;
klass = CLASS_OF(rcv);
type = rb_boxed_objc_type(klass);
return StringValuePtr(type);
}
static void
imp_rb_boxed_getValue(void *rcv, SEL sel, void *buffer)
{
bs_element_boxed_t *bs_boxed;
void *data;
bool ok;
bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(rcv));
data = bs_element_boxed_get_data(bs_boxed, (VALUE)rcv, &ok);
if (!ok)
[NSException raise:@"NSException"
format:@"can't get internal data for boxed type `%s'",
RSTRING_CPTR(rb_inspect((VALUE)rcv))];
if (data == NULL) {
*(void **)buffer = NULL;
}
else {
memcpy(buffer, data, bs_boxed->ffi_type->size);
}
}
static inline void
rb_objc_install_method(Class klass, SEL sel, IMP imp)
{
Method method = class_getInstanceMethod(klass, sel);
assert(method != NULL);
assert(class_addMethod(klass, sel, imp, method_getTypeEncoding(method)));
}
static inline void
rb_objc_override_method(Class klass, SEL sel, IMP imp)
{
Method method = class_getInstanceMethod(klass, sel);
assert(method != NULL);
method_setImplementation(method, imp);
}
static void
rb_install_objc_primitives(void)
{
Class klass;
/* Boxed */
klass = RCLASS_OCID(rb_cBoxed);
rb_objc_override_method(klass, @selector(objCType),
(IMP)imp_rb_boxed_objCType);
rb_objc_override_method(klass, @selector(getValue:),
(IMP)imp_rb_boxed_getValue);
}
static void *
rb_objc_allocate(void *klass)
{
return (void *)rb_obj_alloc(rb_objc_import_class(klass));
}
static void *
imp_rb_obj_alloc(void *rcv, SEL sel)
{
return rb_objc_allocate(rcv);
}
static void *
imp_rb_obj_allocWithZone(void *rcv, SEL sel, void *zone)
{
return rb_objc_allocate(rcv);
}
static void
rb_install_alloc_methods(void)
{
Class klass = RCLASS_OCID(rb_cObject)->isa;
rb_objc_install_method(klass, @selector(alloc), (IMP)imp_rb_obj_alloc);
rb_objc_install_method(klass, @selector(allocWithZone:),
(IMP)imp_rb_obj_allocWithZone);
}
ID
rb_objc_missing_sel(ID mid, int arity)
{
const char *name;
size_t len;
char buf[100];
if (mid == 0)
return mid;
name = rb_id2name(mid);
if (name == NULL)
return mid;
len = strlen(name);
if (len == 0)
return mid;
if (arity == 1 && name[len - 1] == '=') {
strlcpy(buf, "set", sizeof buf);
buf[3] = toupper(name[0]);
buf[4] = '\0';
strlcat(buf, &name[1], sizeof buf);
buf[len + 2] = ':';
}
else if (arity == 0 && name[len - 1] == '?') {
strlcpy(buf, "is", sizeof buf);
buf[2] = toupper(name[0]);
buf[3] = '\0';
strlcat(buf, &name[1], sizeof buf);
buf[len + 1] = '\0';
}
else if (arity >= 1 && name[len - 1] != ':' && len < sizeof buf) {
strlcpy(buf, name, sizeof buf);
buf[len] = ':';
buf[len + 1] = '\0';
}
else if (arity == 1 && name[len - 1] == ':' && len < sizeof buf) {
strlcpy(buf, name, sizeof buf);
buf[len - 1] = '\0';
}
else {
return mid;
}
//printf("new sel %s for %s\n", buf, name);
return rb_intern(buf);
}
static const char *
resources_path(char *path, size_t len)
{
CFBundleRef bundle;
CFURLRef url;
bundle = CFBundleGetMainBundle();
assert(bundle != NULL);
url = CFBundleCopyResourcesDirectoryURL(bundle);
*path = '-';
*(path+1) = 'I';
assert(CFURLGetFileSystemRepresentation(
url, true, (UInt8 *)&path[2], len - 2));
CFRelease(url);
return path;
}
int
macruby_main(const char *path, int argc, char **argv)
{
char **newargv;
char *p1, *p2;
int n, i;
newargv = (char **)malloc(sizeof(char *) * (argc + 2));
for (i = n = 0; i < argc; i++) {
if (!strncmp(argv[i], "-psn_", 5) == 0)
newargv[n++] = argv[i];
}
p1 = (char *)malloc(PATH_MAX);
newargv[n++] = (char *)resources_path(p1, PATH_MAX);
p2 = (char *)malloc(PATH_MAX);
snprintf(p2, PATH_MAX, "%s/%s", &p1[2], path);
newargv[n++] = p2;
argv = newargv;
argc = n;
ruby_sysinit(&argc, &argv);
{
void *tree;
RUBY_INIT_STACK;
ruby_init();
tree = ruby_options(argc, argv);
free(newargv);
free(p1);
free(p2);
return ruby_run_node(tree);
}
}
static void
rb_objc_ib_outlet_imp(void *recv, SEL sel, void *value)
{
const char *selname;
char buf[128];
size_t s;
VALUE rvalue;
selname = sel_getName(sel);
buf[0] = '@';
buf[1] = tolower(selname[3]);
s = strlcpy(&buf[2], &selname[4], sizeof buf - 2);
buf[s + 1] = '\0';
rb_objc_ocid_to_rval(&value, &rvalue);
rb_ivar_set((VALUE)recv, rb_intern(buf), rvalue);
}
VALUE
rb_mod_objc_ib_outlet(int argc, VALUE *argv, VALUE recv)
{
int i;
char buf[128];
buf[0] = 's'; buf[1] = 'e'; buf[2] = 't';
for (i = 0; i < argc; i++) {
VALUE sym = argv[i];
const char *symname;
Check_Type(sym, T_SYMBOL);
symname = rb_id2name(SYM2ID(sym));
if (strlen(symname) == 0)
rb_raise(rb_eArgError, "empty symbol given");
buf[3] = toupper(symname[0]);
buf[4] = '\0';
strlcat(buf, &symname[1], sizeof buf);
strlcat(buf, ":", sizeof buf);
if (!class_addMethod(RCLASS_OCID(recv), sel_registerName(buf),
(IMP)rb_objc_ib_outlet_imp, "v@:@"))
rb_raise(rb_eArgError, "can't register `%s' as an IB outlet",
symname);
}
return recv;
}
static CFMutableDictionaryRef __obj_flags;
long
rb_objc_flag_get_mask(const void *obj)
{
if (__obj_flags == NULL)
return 0;
return (long)CFDictionaryGetValue(__obj_flags, obj);
}
bool
rb_objc_flag_check(const void *obj, int flag)
{
long v;
v = rb_objc_flag_get_mask(obj);
if (v == 0)
return false;
return (v & flag) == flag;
}
void
rb_objc_flag_set(const void *obj, int flag, bool val)
{
long v;
if (__obj_flags == NULL) {
__obj_flags = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
}
v = (long)CFDictionaryGetValue(__obj_flags, obj);
if (val) {
v |= flag;
}
else {
v ^= flag;
}
CFDictionarySetValue(__obj_flags, obj, (void *)v);
}
long
rb_objc_remove_flags(const void *obj)
{
long flag;
if (CFDictionaryGetValueIfPresent(__obj_flags, obj,
(const void **)&flag)) {
CFDictionaryRemoveValue(__obj_flags, obj);
return flag;
}
return 0;
}
static void
rb_objc_get_types_for_format_str(char **octypes, const int len, VALUE *args,
const char *format_str, char **new_fmt)
{
unsigned i, j, format_str_len;
format_str_len = strlen(format_str);
i = j = 0;
while (i < format_str_len) {
bool sharp_modifier = false;
bool star_modifier = false;
if (format_str[i++] != '%')
continue;
if (i < format_str_len && format_str[i] == '%') {
i++;
continue;
}
while (i < format_str_len) {
char *type = NULL;
switch (format_str[i]) {
case '#':
sharp_modifier = true;
break;
case '*':
star_modifier = true;
type = "i"; // C_INT;
break;
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
type = "i"; // _C_INT;
break;
case 'c':
case 'C':
type = "c"; // _C_CHR;
break;
case 'D':
case 'O':
case 'U':
type = "l"; // _C_LNG;
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
type = "d"; // _C_DBL;
break;
case 's':
case 'S':
{
if (i - 1 > 0) {
long k = i - 1;
while (k > 0 && format_str[k] == '0')
k--;
if (k < i && format_str[k] == '.')
args[j] = (VALUE)CFSTR("");
}
type = "*"; // _C_CHARPTR;
}
break;
case 'p':
type = "^"; // _C_PTR;
break;
case '@':
type = "@"; // _C_ID;
break;
case 'B':
case 'b':
{
VALUE arg = args[j];
switch (TYPE(arg)) {
case T_STRING:
arg = rb_str_to_inum(arg, 0, Qtrue);
break;
}
arg = rb_big2str(arg, 2);
if (sharp_modifier) {
VALUE prefix = format_str[i] == 'B'
? (VALUE)CFSTR("0B") : (VALUE)CFSTR("0b");
rb_str_update(arg, 0, 0, prefix);
}
if (*new_fmt == NULL)
*new_fmt = strdup(format_str);
(*new_fmt)[i] = '@';
args[j] = arg;
type = "@";
}
break;
}
i++;
if (type != NULL) {
if (len == 0 || j >= len)
rb_raise(rb_eArgError,
"Too much tokens in the format string `%s' "\
"for the given %d argument(s)", format_str, len);
octypes[j++] = type;
if (!star_modifier)
break;
}
}
}
for (; j < len; j++)
octypes[j] = "@"; // _C_ID;
}
VALUE
rb_str_format(int argc, const VALUE *argv, VALUE fmt)
{
char **types;
ffi_type *ffi_rettype, **ffi_argtypes;
void *ffi_ret, **ffi_args;
ffi_cif *cif;
int i;
void *null;
char *new_fmt;
if (argc == 0)
return fmt;
types = (char **)alloca(sizeof(char *) * argc);
ffi_argtypes = (ffi_type **)alloca(sizeof(ffi_type *) * argc + 4);
ffi_args = (void **)alloca(sizeof(void *) * argc + 4);
null = NULL;
new_fmt = NULL;
rb_objc_get_types_for_format_str(types, argc, (VALUE *)argv,
RSTRING_CPTR(fmt), &new_fmt);
if (new_fmt != NULL) {
fmt = (VALUE)CFStringCreateWithCString(NULL, new_fmt,
kCFStringEncodingUTF8);
free(new_fmt);
CFMakeCollectable((void *)fmt);
}
for (i = 0; i < argc; i++) {
ffi_argtypes[i + 3] = rb_objc_octype_to_ffitype(types[i]);
ffi_args[i + 3] = (void *)alloca(ffi_argtypes[i + 3]->size);
rb_objc_rval_to_ocval(argv[i], types[i], ffi_args[i + 3]);
}
ffi_argtypes[0] = &ffi_type_pointer;
ffi_args[0] = &null;
ffi_argtypes[1] = &ffi_type_pointer;
ffi_args[1] = &null;
ffi_argtypes[2] = &ffi_type_pointer;
ffi_args[2] = &fmt;
ffi_argtypes[argc + 4] = NULL;
ffi_args[argc + 4] = NULL;
ffi_rettype = &ffi_type_pointer;
cif = (ffi_cif *)alloca(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, argc + 3, ffi_rettype, ffi_argtypes)
!= FFI_OK)
rb_fatal("can't prepare cif for CFStringCreateWithFormat");
ffi_ret = NULL;
ffi_call(cif, FFI_FN(CFStringCreateWithFormat), &ffi_ret, ffi_args);
if (ffi_ret != NULL) {
CFMakeCollectable((CFTypeRef)ffi_ret);
return (VALUE)ffi_ret;
}
return Qnil;
}
extern bool __CFStringIsMutable(void *);
extern bool _CFArrayIsMutable(void *);
extern bool _CFDictionaryIsMutable(void *);
bool
rb_objc_is_immutable(VALUE v)
{
switch(TYPE(v)) {
case T_STRING:
return !__CFStringIsMutable((void *)v);
case T_ARRAY:
return !_CFArrayIsMutable((void *)v);
case T_HASH:
return !_CFDictionaryIsMutable((void *)v);
}
return false;
}
static void
timer_cb(CFRunLoopTimerRef timer, void *ctx)
{
RUBY_VM_CHECK_INTS();
}
void
Init_ObjC(void)
{
rb_objc_retain(bs_constants = st_init_numtable());
rb_objc_retain(bs_functions = st_init_numtable());
rb_objc_retain(bs_function_syms = st_init_numtable());
rb_objc_retain(bs_boxeds = st_init_strtable());
rb_objc_retain(bs_classes = st_init_strtable());
rb_objc_retain(bs_inf_prot_cmethods = st_init_numtable());
rb_objc_retain(bs_inf_prot_imethods = st_init_numtable());
rb_objc_retain(bs_cftypes = st_init_strtable());
rb_objc_retain((const void *)(
bs_const_magic_cookie = rb_str_new2("bs_const_magic_cookie")));
rb_objc_retain((const void *)(
rb_objc_class_magic_cookie = rb_str_new2("rb_objc_class_magic_cookie")));
rb_cBoxed = rb_define_class("Boxed",
rb_objc_import_class(objc_getClass("NSValue")));
rb_define_singleton_method(rb_cBoxed, "objc_type", rb_boxed_objc_type, 0);
rb_define_singleton_method(rb_cBoxed, "opaque?", rb_boxed_is_opaque, 0);
rb_define_singleton_method(rb_cBoxed, "fields", rb_boxed_fields, 0);
rb_ivar_type = rb_intern("@__objc_type__");
rb_install_objc_primitives();
rb_install_alloc_methods();
rb_define_global_function("load_bridge_support_file", rb_objc_load_bs, 1);
{
CFRunLoopTimerRef timer;
timer = CFRunLoopTimerCreate(NULL,
CFAbsoluteTimeGetCurrent(), 0.1, 0, 0, timer_cb, NULL);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
}
}
@interface Protocol
@end
@implementation Protocol (MRFindProtocol)
+(id)protocolWithName:(NSString *)name
{
return (id)objc_getProtocol([name UTF8String]);
}
@end
Jump to Line
Something went wrong with that request. Please try again.