BBRETAIN
BlitzMax Forums/BlitzMax Programming/BBRETAIN
| ||
I'm currently analyzing modules code to correctly port C++ Libraries myself and found this situation in the Brucey Box2D module. This is the C++ part, everything is correctly Extern and accessible from BlitzMax: b2BodyDef * bmx_b2bodydef_create() { return new b2BodyDef; } b2Body * bmx_b2world_createbody(b2World * world, b2BodyDef * def, BBObject * body) { def->userData = body; BBRETAIN(body); return world->CreateBody(def); } And this is the BlitzMax part : Type b2World Field b2ObjectPtr:Byte Ptr ' The b2World object is correctly init somewhere else '... Method CreateBody:b2Body(def:b2BodyDef) Local body:b2Body = New b2Body body.b2ObjectPtr = bmx_b2world_createbody(b2ObjectPtr, def.b2ObjectPtr, body) body.userData = def.userData ' copy the userData Return body End Method End Type Type b2BodyDef Field b2ObjectPtr:Byte Ptr Field userData:Object Method New() b2ObjectPtr = bmx_b2bodydef_create() End Method End Type Consider the b2BodyDef pointer as a Definition object used by a factory (world->CreateBody(def)) to create body object in this physical library. You can reuse Definition or nullify them, it's up to you. You can also set your own userData inside, it will be transferred in your body at creation time. My question is: why using BBRETAIN in this case? A link is also created between the definition object and the body (def->userData = body) while it's not supposed to happen in the original library. In fact, rewriting this C++ function this way would work as well: b2Body * bmx_b2world_createbody(b2World * world, b2BodyDef * def) { return world->CreateBody(def); } I tried in a big project using GCcollect and didn't notice any loss or crash bug... In other words: Why BBRETAIN() an object created inside BlitzMax itself? Normally the garbage collector won't collect it unless you forget about it yourself inside BlitzMax, no ? Won't the C++ object persist in memory thanks to the pointer returned and managed inside BlitzMax ? I never really used BBRETAIN before and don't know much about the GC so there is probably a few things I'm missing there... Afaik Brucey is using this tricks while creating body, shape, joint and controller. But not for definition objects or C++ struct. Too bad I cannot ask him why anymore :( Last edited 2012 |
| ||
Won't the C++ object persist in memory thanks to the pointer returned and managed inside BlitzMax ? Nope. BlitzMax uses reference counting (you're thinking of "tracing", which... is different). When you set a variable to a pointer the refcount for that pointer is adjusted at the same time by code added by the BlitzMax compiler. Because the variable is being set in C++ code, the C++ is just compiled to the set operation alone. Therefore the C++ code needs to have the reference count code added explicitly, or the garbage collector won't have been informed that a new reference to the object exists. (In other words, no it does not scan anything, and therefore if it isn't watching when ref.userData is set, it will never know a pointer existed there.) EDIT: Your rewrite that only calls CreateBody is not going to cause a memory leak since the BBRETAIN isn't needed if the userData field isn't being set (I make no comment on whether the field should be being set), so within that one function at least, that change is safe. Last edited 2012 |
| ||
Thanks Yasha. I roughly understand the use of BBRETAIN. Unfortunately my skills is a bit low about wrapping library :( If you are interested in a Box2d module maybe you can give me a hand on this : http://code.google.com/p/arme-mod/ ? |
| ||
So to re-iterate its use... This is how I understand it, so please free to correct me if I'm wrong :- The scenario will be using BMax to call a C wrapped function in C++ that will create an instance of a C++ object/class/whatever and pass the address as a pointer back into BMax to be used as an 'instance handle'. In the BMax code, you call the ( C wrapped ) C++ function that creates the instance and returns its address :- you save this with a :Byte Ptr in BMax. This 32bit memory pointer will be GC managed within BMax, but ONLY the memory pointer. From what I understand the actual object is hanging on a thread now, preying that it isn't overwritten or trampled over, as its now 'out of scope' of the function that created it and BMax doesnt know of the actual C++ object itself. So now in the C++ we need to GC manage this object too. Therefore we'd wrap the C++ object with BBRETAIN / BBRELEASE when creating and destroying the instance. Also in the C++ code, if we have a 'global' variable and create an instance of something in a C++ function using that variable, we pass that variable back into BMax, I've assumed the C++ object still needs BMax GC management too? Am I correct in the usage in said scenarios? Next scenario... You have the C++ object created as above. You pass the object back into C++ and attach it as another C++ object as one of its members. So you'd also use BBRETAIN again to add another reference to it? Last edited 2012 |
| ||
Am I correct in the usage in said scenarios? No. BBRETAIN and BBRELEASE are for use with BlitzMax objects only. They are only for use when assigning BlitzMax object pointers in non-BlitzMax code, nothing else. C++ doesn't have any built-in concept of reference-counting, nor does it even have a base object class, so there is no reference count in a C++ object for the BlitzMax refcount system to adjust. C++ objects are completely separate animals from BlitzMax objects, and you either have to manually destroy them with an appropriate Free function provided by the library, or roll your own GC from the ground up (...don't do this!). Using BBRETAIN and BBRELEASE on a C++ object will, without question, break your code, not work as expected, and not actually have anything to do with garbage collection either (since the C++ object isn't in any way part of the BlitzMax GC runtime). Short answer: BlitzMax reference counting cannot be used on C++ objects. You need a completely separate lifecycle management system, either built into the library, or managed from your BlitzMax application; neither of these is provided by BlitzMax. One way to manage C++ object lifecycle from BlitzMax is to store the C++ object in a BlitzMax wrapper object that will free the C++ object in its finaliser (and make sure that the C++ object is not stored directly anywhere else). This 32bit memory pointer will be GC managed within BMax, but ONLY the memory pointer. ...this 32-bit value will be a simple numeric value; the concept of garbage collection doesn't really apply to it (any more than it applies to e.g. ints). A pointer is not an object: it refers to an object. you call the ( C wrapped ) C++ function that creates the instance and returns its address :- you save this with a :Byte Ptr in BMax ...if you like, but remember you can also have Extern Types (which lets you access C++ methods from BlitzMax, always handy), for better type safety. So you'd also use BBRETAIN again to add another reference to it? No, because you wouldn't use BBRETAIN on a C++ object. (I'm not sure I'm reading the question correctly but it also looks like you might be establishing a circular reference there...?). Sorry if that sounds harsh, but basically you have almost the exact opposite of the right idea: don't use BlitzMax memory management for C++ objects, as it will end in disaster. |
| ||
Sorry if that sounds harsh, but basically you have almost the exact opposite of the right idea: don't use BlitzMax memory management for C++ objects, as it will end in disaster. No need to apologise. After seeing it used in Armitage example, I thought that I was doing it wrong and then had the concept that I described above pop into my head. It has always been a guess for me before but you've confirmed it for me that it's ok the way I've doing it. Thanks for the explanation. |