static void setMouseHwnd( HWND hwnd,int x,int y,BBObject *source ){ if( hwnd==mouseHwnd ) return; if( hwnd && source ){ BBRETAIN( source ); } if( mouseHwnd ){ POINT p; GetCursorPos( &p ); ScreenToClient( mouseHwnd,&p ); bbSystemEmitEvent( BBEVENT_MOUSELEAVE,mouseSource,0,0,p.x,p.y,&bbNullObject ); if( mouseSource ){ BBRELEASE( mouseSource ); } } mouseHwnd=hwnd; updateMouseVisibility(); if( mouseHwnd ){ TRACKMOUSEEVENT tm={sizeof(tm),TME_LEAVE,hwnd,0}; mouseSource=source; bbSystemEmitEvent( BBEVENT_MOUSEENTER,mouseSource,0,0,x,y,&bbNullObject ); _TrackMouseEvent( &tm ); } }
void lua_boxobject( lua_State *L,BBObject *obj ) { void *p; BBRETAIN( obj ); p=lua_newuserdata( L,4 ); *(BBObject**)p=obj; }
MaxTreeItemData::MaxTreeItemData(BBObject * data) : maxHandle(data) { if (data != &bbNullObject) { BBRETAIN(data); } }
BBArray * bmx_filesystem_loaddir(BBString * dir) { boost::filesystem::path p(bbStringToPath(dir)); std::vector<boost::filesystem::path> v; if (boost::filesystem::exists(p)) { copy(boost::filesystem::directory_iterator(p), boost::filesystem::directory_iterator(), back_inserter(v)); int n = v.size(); if (n > 0) { int i = 0; BBArray *arr = bbArrayNew1D( "$",n ); BBString **s = (BBString**)BBARRAYDATA( arr, arr->dims ); for (std::vector<boost::filesystem::path>::const_iterator it (v.begin()); it != v.end(); ++it) { boost::filesystem::path path((*it).filename()); s[i] = bbStringFromPath( path ); BBRETAIN( s[i] ); i++; } return arr; } } return &bbEmptyArray; }
int bmx_wxlistctrl_setitemdata(wxListCtrl * list, long item, BBObject * data) { // delete current client data if any exists bmx_releaseindexedclientdata(list, item); if (data != &bbNullObject) { BBRETAIN( data ); } return static_cast<int>(list->SetItemData(item, wxPtrToUInt((void *)data))); }
void bbSystemStartAsyncOp( BBAsyncOp asyncOp,int asyncInfo,BBSyncOp syncOp,BBObject *syncInfo ){ DWORD threadId; AsyncOp *p=(AsyncOp*)malloc( sizeof( AsyncOp ) ); BBRETAIN( syncInfo ); p->asyncOp=asyncOp; p->asyncInfo=asyncInfo; p->syncOp=syncOp; p->syncInfo=syncInfo; CreateThread( 0,0,asyncOpThread,p,0,&threadId ); }
void lua_boxobject( lua_State *L,BBObject *obj ){ void *p; BBRETAIN( obj ); struct BBObjectContainer * uc = (struct BBObjectContainer *)GC_MALLOC_UNCOLLECTABLE(sizeof(struct BBObjectContainer)); uc->o = obj; p=lua_newuserdata( L, sizeof(struct BBObjectContainer) ); *(struct BBObjectContainer**)p=uc; }
void MaxTreeItemData::SetData(BBObject * data) { if (maxHandle != &bbNullObject) { BBRELEASE(maxHandle); } maxHandle = data; if (data != &bbNullObject) { BBRETAIN(data); } }
int bbTimerStart( float hertz,BBObject *bbTimer ){ int timer; if( n_timers==MAX_TIMERS ) return 0; timer=(int)timeSetEvent( 1000.0/hertz,0,timerProc,(DWORD)bbTimer,TIME_PERIODIC ); if( !timer ) return 0; BBRETAIN( bbTimer ); timers[n_timers++]=timer; return timer; }
void XMLCALL bmx_expat_StartElementHandler(void *userData, const char *name, const char **atts) { int n; for (n = 0; atts[n]; n += 2) {} BBArray *p = bbArrayNew1D( "$",n ); BBString **s = (BBString**)BBARRAYDATA( p,p->dims ); for( int i = 0; i < n; ++i ){ s[i] = bbStringFromUTF8String(atts[i]); BBRETAIN(s[i]); } _bah_expat_TXMLParser__StartElementHandler((BBObject *)userData, bbStringFromUTF8String(name), p); }
void bmx_wxlistitem_setdata(MaxListItem * item, BBObject * data) { if (item->Item().GetColumn() == 0) { // is there any data here already? BBObject * oldData = (BBObject *)wxUIntToPtr(item->Item().GetData()); if (oldData && (oldData != &bbNullObject)) { BBRELEASE(oldData); item->Item().SetData((void*)NULL); } if (data != &bbNullObject) { BBRETAIN( data ); } item->Item().SetData(wxPtrToUInt((void*)data)); } }
BBTimer *bbTimerStart( float hertz,BBObject *bbTimer ){ BBTimer *timer; int start=bbMilliSecs(); timer=(BBTimer*)malloc( sizeof( BBTimer ) ); timer->status=1; timer->puts=1; timer->gets=0; timer->start=start; timer->period=1000.0f/hertz; timer->bbTimer=bbTimer; if( pthread_create( &timer->thread,0,(void*(*)(void*))timerProc,timer )<0 ){ free( timer ); return 0; } BBRETAIN( timer->bbTimer ); return timer; }
int bmx_libwebsockets_callback(struct libwebsocket_context * context, struct libwebsocket * wsi, enum libwebsocket_callback_reasons reason, void * user, void * in, size_t len, struct libwebsocket_protocols * protocol) { BBString * name = bbStringFromCString(protocol->name); struct per_session_data_obj *maxObj = (struct per_session_data_obj *)user; if (reason == LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION) { maxObj = 0; } // on a new connection, we should initialise the object if (maxObj && ((reason == LWS_CALLBACK_ESTABLISHED) || (!maxObj->handle))) { if (maxObj->handle && maxObj->handle != &bbNullObject) { BBRELEASE(maxObj->handle); } // call the object creation function maxObj->handle = CB_PREF(bah_libwebsockets_TLWSProtocol__objectCallback)(name); // prevent early GC if (maxObj->handle != &bbNullObject) { BBRETAIN(maxObj->handle); } } int result = CB_PREF(bah_libwebsockets_TLWSProtocol__callback)(name, context, wsi, reason, (maxObj) ? maxObj->handle : &bbNullObject, in, len); // release on close of connection if (reason == LWS_CALLBACK_CLOSED) { if (maxObj && maxObj->handle) { if (maxObj->handle != &bbNullObject) { BBRELEASE(maxObj->handle); } } } return result; }
MaxTextDropTarget::MaxTextDropTarget(BBObject * handle) : maxHandle(handle) { BBRETAIN(handle); }
MaxSoundStopEventReceiver(BBObject * handle) : maxHandle(handle) { BBRETAIN(handle); }
/* Converts a Lua table to a BMax array - the type of the array is determined by the first value in the table (at index 1) */ BBArray *lua_tobmaxarray(lua_State *state, int index) { switch (lua_type(state, index)) { case LUA_TNONE: luaL_error(state, ERRORSTR("@lua_tobmaxarray: Invalid index (%d)"), index); return &bbEmptyArray; case LUA_TNIL: return &bbEmptyArray; case LUA_TUSERDATA: if ( lua_isbmaxobject(state, index) ) { BBObject *obj = lua_tobmaxobject(state, index); if ( obj->clas == &bbArrayClass ) { return (BBArray*)obj; } else { luaL_error(state, ERRORSTR("@lua_tobmaxarray: Value at index (%d) is not an array."), index); return &bbEmptyArray; } } case LUA_TTABLE: /* code below */ break; default: if (lua_type(state, index) != LUA_TTABLE) { luaL_error(state, ERRORSTR("@lua_tobmaxarray: Value at index (%d) is not a table."), index); return &bbEmptyArray; } } /* make the index absolute, since we're now dealing with more than one value on the stack */ if (index < 0 && index > LUA_REGISTRYINDEX) index = lua_gettop(state)+(index+1); /* the index into the array */ size_t table_index; BBArray *arr = NULL; size_t len = lua_objlen(state, index); if ( len == 0 ) return &bbEmptyArray; /* get the first item of the table */ lua_pushinteger(state, 1); lua_gettable(state, index); /* starting at index 2 when iterating */ table_index = 2; /* determine what type of array to create based on the first value in the table (at index 0) */ switch (lua_type(state, -1)) { case LUA_TNUMBER: { /* array of doubles */ double *p; arr = bbArrayNew1D("d", len); p = (double*)BBARRAYDATA(arr, arr->dims); *p++ = lua_tonumber(state, -1); lua_pop(state, 1); for (; table_index <= len; ++table_index) { lua_pushinteger(state, table_index); lua_gettable(state, index); *p++ = lua_tonumber(state, -1); lua_pop(state, 1); } return arr; } case LUA_TBOOLEAN: { /* array of integers */ int *p; arr = bbArrayNew1D("i", len); p = (int*)BBARRAYDATA(arr, arr->dims); *p++ = lua_toboolean(state, -1); lua_pop(state, 1); for (; table_index <= len; ++table_index) { lua_pushinteger(state, table_index); lua_gettable(state, index); *p++ = lua_toboolean(state, -1); lua_pop(state, 1); } return arr; } case LUA_TSTRING: { /* array of strings */ BBString **p; arr = bbArrayNew1D("$", len); p = (BBString**)BBARRAYDATA(arr, arr->dims); *p = bbStringFromCString(lua_tostring(state, -1)); BBRETAIN((BBObject*)*p++); lua_pop(state, 1); for (; table_index <= len; ++table_index) { lua_pushinteger(state, table_index); lua_gettable(state, index); *p = bbStringFromCString(lua_tostring(state, -1)); BBRETAIN((BBObject*)*p++); lua_pop(state, 1); } return arr; } case LUA_TTABLE: /* array of arrays (arrays inside of arrays do not have to be the same type) */ case LUA_TUSERDATA: { BBObject **p; arr = bbArrayNew1D(":Object", len); p = (BBObject**)BBARRAYDATA(arr, arr->dims); *p = lua_tobmaxobject(state, -1); BBRETAIN(*p++); lua_pop(state, 1); for (; table_index <= len; ++table_index) { lua_pushinteger(state, table_index); lua_gettable(state, index); *p = lua_tobmaxobject(state, -1); BBRETAIN(*p++); lua_pop(state, 1); } return arr; } default: luaL_error(state, ERRORSTR("@lua_tobmaxarray: Arrays of type %s are not unsupported"), lua_typename(state, lua_type(state, -1))); return &bbEmptyArray; } /* switch lua type */ return arr; } /* lua_tobmaxarray */
/* table[key] = value OR table.key = value */ static int lugi_newindex_object(lua_State *state) { if (lua_type(state, 2) == LUA_TSTRING) { BBObject *obj = lua_tobmaxobject(state, 1); BBClass *clas = obj->clas; /* while class != NULL */ do { /* get class VMT */ lua_pushlightuserdata(state, clas); lua_gettable(state, LUA_REGISTRYINDEX); if ( lua_type(state, -1) == LUA_TTABLE ) /* there's a lookup table for the class.. */ { lua_pushvalue(state, 2); lua_rawget(state, -2); if (lua_type(state, -1) == LUA_TUSERDATA) { /* getting the value of a field */ fieldinfo_t *info = (fieldinfo_t*)lua_touserdata(state, -1);; bmx_field *field = (bmx_field*)((char*)obj+info->offset); clas = NULL; switch (info->type) /* set the value of a field */ { case BYTEFIELD: if (lua_type(state, 3) == LUA_TBOOLEAN) { field->byte_value = (unsigned char)lua_toboolean(state, 3); } else { field->byte_value = (unsigned char)lua_tointeger(state, 3); } break; case SHORTFIELD: if (lua_type(state, 3) == LUA_TBOOLEAN) { field->short_value = (unsigned short)lua_toboolean(state, 3); } else { field->short_value = (unsigned short)lua_tointeger(state, 3); } break; case INTFIELD: if (lua_type(state, 3) == LUA_TBOOLEAN) { field->int_value = lua_toboolean(state, 3); } else { field->int_value = lua_tointeger(state, 3); } break; case FLOATFIELD: field->float_value = (float)lua_tonumber(state, 3); break; case DOUBLEFIELD: field->double_value = lua_tonumber(state, 3); break; case LONGFIELD: if (lua_type(state, 3) == LUA_TBOOLEAN) { field->long_value = lua_toboolean(state, 3); } else { field->long_value = lua_tointeger(state, 3); } break; case STRINGFIELD: { const char *strbuf = lua_tostring(state, 3); BBString *last = field->string_value; BBRETAIN((BBObject*)(field->string_value = bbStringFromCString(strbuf))); BBRELEASE((BBObject*)last); } break; case ARRAYFIELD: { BBArray *last = field->arr_value; BBRETAIN((BBObject*)(field->arr_value = lua_tobmaxarray(state, 3))); BBRELEASE((BBObject*)last); } break; case OBJECTFIELD: { BBObject *last = field->obj_value; BBRETAIN(field->obj_value = lua_tobmaxobject(state, 3)); BBRELEASE(last); } break; default: return luaL_error(state, ERRORSTR("@lugi_newindex_object: Unrecognized field type (%d)."), info->type); break; } /* set value based on type */ return 0; } else { lua_pop(state, 1); } } /* VMT found */ lua_pop(state, 1); clas = clas->super; /* iterate to a superclass if nothing is found in the class */ } while (clas != NULL); } /* key is string */ #if BMX_TABLE_SUPPORT < 1 const char *objtype="null"; if (lua_isbmaxobject(state, 1)) objtype = lua_tobmaxobject(state, 1)->clas->debug_scope->name; if ((LUA_TNUMBER|LUA_TSTRING)&lua_type(state,2)) return luaL_error(state, ERRORSTR("@lugi_newindex_object: Index (%s) for object<%s> is not a valid field or method."), lua_tostring(state, 2), objtype); else return luaL_error(state, ERRORSTR("@lugi_newindex_object: Index for object<%s> is not a valid field or method."), objtype); #else /* prior table behavior for BMax objects - disabled by default */ lua_settop(state, 3); lua_getfenv(state, 1); lua_insert(state, 2); lua_settable(state, 2); return 0; #endif }
static int lugi_newindex_array(lua_State *state) { if (lua_isnumber(state, 2) == 1) { BBArray *arr = lua_tobmaxarray(state, 1); lua_Integer index = lua_tointeger(state, 2); if ( index < 0 || arr->scales[0] <= index ) { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Attempt to index array failed: index (%d) out of bounds"), index); } switch (arr->type[0]) { case 'b': /* byte */ if ( lua_isnumber(state, 3) ) { ((BBBYTE*)BBARRAYDATA(arr, arr->dims))[index] = (BBBYTE)lua_tointeger(state, 3); } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of byte array"), lua_typename(state, lua_type(state, 3))); } break; case 's': /* short */ if ( lua_isnumber(state, 3) ) { ((BBSHORT*)BBARRAYDATA(arr, arr->dims))[index] = (BBSHORT)lua_tointeger(state, 3); } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of short array"), lua_typename(state, lua_type(state, 3))); } break; case 'i': /* int */ if ( lua_isnumber(state, 3) ) { ((BBINT*)BBARRAYDATA(arr, arr->dims))[index] = (BBINT)lua_tointeger(state, 3); } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of integer array"), lua_typename(state, lua_type(state, 3))); } break; case 'l': /* long */ if ( lua_isnumber(state, 3) ) { ((BBLONG*)BBARRAYDATA(arr, arr->dims))[index] = (BBLONG)lua_tointeger(state, 3); } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of long array"), lua_typename(state, lua_type(state, 3))); } break; case 'f': /* float */ if ( lua_isnumber(state, 3) ) { ((BBFLOAT*)BBARRAYDATA(arr, arr->dims))[index] = (BBFLOAT)lua_tonumber(state, 3); } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of short array"), lua_typename(state, lua_type(state, 3))); } break; case 'd': /* double */ if ( lua_isnumber(state, 3) ) { ((BBDOUBLE*)BBARRAYDATA(arr, arr->dims))[index] = (BBDOUBLE)lua_tonumber(state, 3); } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of short array"), lua_typename(state, lua_type(state, 3))); } break; case '$': /* string */ { BBString **data = ((BBString**)BBARRAYDATA(arr, arr->dims)); if ( lua_isstring(state, 3) ) { BBString *newstring = bbStringFromCString(lua_tostring(state, 3)); BBRETAIN((BBObject*)newstring); BBRELEASE((BBObject*)data[index]); data[index] = newstring; } else if ( lua_isnil(state, 3 ) ) { BBRELEASE((BBObject*)data[index]); data[index] = &bbEmptyString; } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign value with type %s to element of string array"), lua_typename(state, lua_type(state, 3))); } } break; case ':': /* any type of object */ { BBObject *value = lua_tobmaxobject(state, 3); BBClass *arrclas = NULL; { /* search for the array element type's class */ const char *arrtypename = arr->type+1; int numTypes = 0; int regidx = 0; BBClass **regtypes = bbObjectRegisteredTypes(&numTypes); for (; regidx < numTypes; ++regidx) { if ( strcmp(regtypes[regidx]->debug_scope->name, arrtypename) == 0 ) { arrclas = regtypes[regidx]; break; } } } if ( arrclas == NULL ) { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Undefined array type encountered: %s"), arr->type); } value = bbObjectDowncast(value, arrclas); BBObject **data = (BBObject**)BBARRAYDATA(arr, arr->dims); BBRETAIN(value); BBRELEASE(data[index]); data[index] = value; } break; case '[': { BBArray *value = lua_tobmaxarray(state, 3); if ( (BBObject*)value == &bbNullObject ) { value = &bbEmptyArray; } if ( value == &bbEmptyArray || strcmp(arr->type+2, value->type) == 0 ) { BBArray **data = (BBArray**)BBARRAYDATA(arr, arr->dims); BBRETAIN((BBObject*)value); BBRELEASE((BBObject*)data[index]); data[index] = value; } else { return luaL_error(state, ERRORSTR("@lugi_newindex_array: Cannot assign array value to an element of an array of a differing type")); } } break; default: return luaL_error(state, ERRORSTR("@lugi_newindex_array: Unsupported array element type: %s"), arr->type); } return 0; } /* key is a valid type of index for an array */ return luaL_error(state, ERRORSTR("@lugi_newindex_array: Invalid type for an array index (%s), must be an integer or a string convertible to an integer"), lua_typename(state, lua_type(state, 2))); }
/* precondition: lua interface must be initialized for this state, or you will experience a crash. */ void lua_pushbmaxobject(lua_State *state, BBObject *obj) { int top = lua_gettop(state); if (obj == &bbNullObject || (BBArray*)obj == &bbEmptyArray) { lua_pushnil(state); return; } else if ((BBString*)obj == &bbEmptyString) { lua_pushlstring(state, "", 0); return; } if (obj->clas == &bbStringClass) { BBString *str = (BBString*)obj; char *buf = bbStringToCString(str); lua_pushlstring(state, buf, str->length); bbMemFree(buf); return; } /* check object cache for existing object */ lua_pushlightuserdata(state, LUGI_OBJECT_CACHE_KEY); lua_gettable(state, LUA_REGISTRYINDEX); /* cache[object] */ lua_pushlightuserdata(state, obj); lua_gettable(state, -2); /* if cache[object] == object */ if (lua_type(state, -1) == LUA_TUSERDATA) { lua_remove(state, -2); /* object exists, remove table and return */ return; } lua_pop(state, 1); /* increment reference count for the object since BMax's default GC relies on ref counting */ BBRETAIN(obj); /* store object */ BBObject **data = (BBObject**)lua_newuserdata(state, sizeof(BBObject*)); *data = obj; #if BMX_TABLE_SUPPORT > 0 /* this is part of an unsupported feature to enable table-like behavior for BMax objects decided not to include it (by default, anyway) because, frankly, it doesn't fit in when you might be trying to set a field for some reason and you'll never receive an error that the field doesn't exist because you're just setting some value for a table */ lua_newtable(state); if ( lua_setfenv(state, -2) == 0 ) { lua_settop(state, top); /* clean up stack.. */ luaL_error(state, ERRORSTR("@lua_pushbmaxobject: Failed to set environment table for BMax object.")); return; } #endif /* set the metatable */ if ( obj->clas == &bbArrayClass ) lua_pushlightuserdata(state, LUGI_METATABLE_ARRAY_KEY); else lua_pushlightuserdata(state, LUGI_METATABLE_KEY); lua_gettable(state, LUA_REGISTRYINDEX); lua_setmetatable(state, -2); /* cache the object and remove the cache table */ lua_pushlightuserdata(state, obj); lua_pushvalue(state, -2); lua_settable(state, -4); lua_remove(state, -2); /* done */ }
MaxTipProvider::MaxTipProvider(BBObject * handle, int currentTip) : maxHandle(handle), wxTipProvider(currentTip) { BBRETAIN(handle); }
BBArray * bmx_bass_channelgettags(DWORD handle, DWORD tags) { const char * text; TAG_ID3 *id3; switch (tags) { case BASS_TAG_ID3: id3 = (TAG_ID3*) BASS_ChannelGetTags(handle, BASS_TAG_ID3); // get the ID3 tags if (id3) { char buffer[4]; BBArray * p = bbArrayNew1D("$", 7); BBString **s = (BBString**)BBARRAYDATA( p,p->dims ); sprintf(buffer, "%.3s", id3->id); s[0] = bbStringFromCString(buffer); BBRETAIN( s[0] ); s[1] = bbStringFromCString(id3->title); BBRETAIN( s[1] ); s[2] = bbStringFromCString(id3->artist); BBRETAIN( s[2] ); s[3] = bbStringFromCString(id3->album); BBRETAIN( s[3] ); sprintf(buffer, "%.4s", id3->year); s[4] = bbStringFromCString(buffer); BBRETAIN( s[4] ); s[5] = bbStringFromCString(id3->comment); BBRETAIN( s[5] ); sprintf(buffer, "%d", id3->genre); s[6] = bbStringFromCString(buffer); BBRETAIN( s[6] ); return p; } else { return &bbEmptyArray; } case BASS_TAG_META: case BASS_TAG_LYRICS3: case BASS_TAG_VENDOR: case BASS_TAG_MUSIC_NAME: case BASS_TAG_MUSIC_MESSAGE: text = BASS_ChannelGetTags(handle, tags); if (text) { BBArray * p = bbArrayNew1D("$", 1); BBString **s = (BBString**)BBARRAYDATA( p,p->dims ); s[0] = bbStringFromCString( text ); BBRETAIN( s[0] ); return p; } else { return &bbEmptyArray; } default: text = BASS_ChannelGetTags(handle, tags); if (text) { int count = 0; const char * current = text; while (*current) { current += strlen(current) + 1; count++; } BBArray * p = bbArrayNew1D("$", count); BBString **s = (BBString**)BBARRAYDATA( p,p->dims ); count = 0; current = text; while (*current) { s[count] = bbStringFromCString( current ); BBRETAIN( s[count] ); current += strlen(current) + 1; count++; } return p; } else { return &bbEmptyArray; } } }
MaxBxStream::MaxBxStream(BBObject * stream) : maxStream(stream) { BBRETAIN(stream); }
void cpbind( void *obj, BBObject *peer ) { if( !obj || peer==&bbNullObject ) return; peerMap.insert( std::make_pair( obj,peer ) ); BBRETAIN( peer ); }
void wxlogbind( wxLog *obj, BBObject *peer ) { if( !obj || peer==&bbNullObject ) return; logPeerMap.insert( std::make_pair( obj,peer ) ); BBRETAIN( peer ); }
void *TCOD_sys_load_image(const char *filename) { BBObject * obj = _bah_libtcod_TCODSystem__LoadImage(bbStringFromCString(filename)); BBRETAIN(obj); return obj; }
void bmx_soundexinfo_setuserdata(FMOD_CREATESOUNDEXINFO * info, BBObject * obj) { BBRETAIN(obj); info->userdata = obj; }