Passing an Opaque Pointer to AngelScript

Started by
11 comments, last by ZsjaWkaos 12 years, 6 months ago
Here is what I would like to do:
  • pass an opaque pointer (OP) from C++ to a function written in AngelScript (AS)
  • not allow AS to keep a copy around after the function has returned
  • allow AS to pass OP around to other functions (either written in C++ and registered in AS or written in AS)
  • use different types for different OPs, AS must not be able to pass the wrong OP to a function!

The reasons for doing it this way are:
  • to decouple any data stored in the OP from AS as much as possible (AS must not have any knowledge about OP's implementation details)
  • to keep any interfacing with AS very basic and simple (no need for any "tricky" reference counting, just passing a pointer around)
  • to avoid overhead from garbage collecting (AS will only keep OP around for the duration of the function call)

Here is my attempt but as I'm rather new to AS and after some trial and error I still didn't succeed (hence this post :wink:). Code snippet for C++:

class SomeData{
public:
void release(){
}
};

void print_some_data( SomeData &some_data ){
cout << "SomeData!" << endl;
}

/* ... */
r = engine->RegisterObjectType( "SomeData", 0, asOBJ_REF | asOBJ_SCOPED ); assert( r >= 0 );
r = engine->RegisterGlobalFunction( "void print_some_data( const SomeData &in )", asFUNCTION( print_some_data ), asCALL_CDECL ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_RELEASE, "void f( )", asMETHOD( SomeData, release ), asCALL_THISCALL ); assert( r >= 0 );


Code for AS (test.as):

void main( SomeData some_data ){
print_some_data( some_data );
}


I get the error. "test.as (1, 11) : ERR : Parameter type can't be 'SomeData'". I think it is because AS wants to create a new object SomeData but does not know how. However, I just want AS to keep the OP around and not do anything with it, except pass it to some functions.

Is what I want to do possible in AS and if so how? It seems like an incredibly simple thing to do yet I can't figure it out. Any help will be greatly appreciated!
Advertisement
Did you try just registering the pointer as a value type?

Did you try just registering the pointer as a value type?


Thanks, I haven't thought of treating the OP as a value itself. :)

Here is my current solution that seems to work:

class SomeData{
public:
int x;
int y;
int z;
};

void print_some_data( SomeData **some_data ){
cout << "SomeData! " << *some_data << ", " << (*some_data)->x << ", " << (*some_data)->y << ", " << (*some_data)->z << endl;
}

static void construct_ptr( void **ptr ){
}

static void copy_construct_ptr( void **other, void **ptr ){
*ptr = *other;
}

static void destruct_ptr( void *ptr ){
}

/* ... */

r = engine->RegisterObjectType( "SomeData", sizeof( SomeData* ), asOBJ_VALUE | asOBJ_APP_CLASS_C | asOBJ_APP_CLASS_DESTRUCTOR ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION( construct_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_CONSTRUCT, "void f( const SomeData &in )", asFUNCTION( copy_construct_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_DESTRUCT, "void f()", asFUNCTION( destruct_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );


AS code is same as before.

Interestingly it even does not allow the value to be assigned to a global variable (no copy constructor) but it does allow it to be passed around. The C++ is a bit more verbose than I hoped for because of the double pointer maybe that can be improved somehow. On the AS side everything looks exactly like I want.
You can simplify some of your C++ code by using references to pointer rather than pointers to pointers. Alternately you could use pass by value rather than pass by reference. Also, it doesn't look like your object flags match up to what they should be for a pointer type. If it hasn't caused you trouble so far, then it probably doesn't matter for your specific compiler, but be aware that your code may not port cleanly to other platforms.
The code is now greatly simplified, I hope it is also correct.


class SomeData{
public:
int x;
int y;
int z;
};

void print_some_data( SomeData *some_data ){
cout << "SomeData! " << some_data << ", " << some_data->x << ", " << some_data->y << ", " << some_data->z << endl;
}

/* ... */

r = engine->RegisterObjectType( "SomeData", sizeof( SomeData* ), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS ); assert( r >= 0 );



Instead of "asOBJ_APP_CLASS" "asOBJ_APP_PRIMITIVE" also works. However, I'm not passing an AS primitive so I think this is the right way to do it. C++ pointer types are plain old data types and I'm passing by value so I think this code should be correct. :)
A pointer is a primitive in C++, so the correct flag to use is asOBJ_APP_PRIMITIVE.

You will want to register a default constructor behaviour for this type, which will simply make sure the pointer is 0 when the object is first declared. Otherwise you'll likely end up using invalid pointers from time to time.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I've updated the C++ code as follows:

void zero_ptr( void* memory ){
cout << "zero_ptr" << endl;
memory = 0;
}

/* ... */
r = engine->RegisterObjectType( "SomeData", sizeof( SomeData* ), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_PRIMITIVE ); assert( r >= 0 );
r = engine->RegisterObjectBehaviour( "SomeData", asBEHAVE_CONSTRUCT, "void f( )", asFUNCTION( zero_ptr ), asCALL_CDECL_OBJLAST ); assert( r >= 0 );


The AS code (this should not be allowed!):

void main( SomeData some_data ){
SomeData sd = SomeData();
}


Unfortunately "zero_ptr" seems to be called very often instead of only for the construction of SomeData. I would like to raise an exception on the construction (or assignment) of SomeData because that shouldn't be possible.

EDIT:
Apparently this is also possible :(:

void main( SomeData some_data ){
SomeData sd;
}
You've registered the type as a POD. AngelScript will allow the construction and assignment of values of this type. You can set a script exception in the constructor and opAssign method, but if you do not allow the use of constructor/assignment then AngelScript will not be able to pass the value to other functions.



AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Does this mean that passing a pointer around (as described in my first post) is an impossible thing to do in AS? Is there any chance that this can be added in a future release (I can take a look at it myself)?

  • pass an opaque pointer (OP) from C++ to a function written in AngelScript (AS)
  • not allow AS to keep a copy around after the function has returned
  • allow AS to pass OP around to other functions (either written in C++ and registered in AS or written in AS)
  • use different types for different OPs, AS must not be able to pass the wrong OP to a function!


[/quote]


Out of these 4 only the second cannot be done, or at least is difficult to do.

The other 3 I would solve as you've done, i.e. by registering the OP as a new value type. This will allow you to guarantee that the OP is always a valid pointer, or null, as the script will not be able to manipulate the actual address. You will also have the guarantee that only the correct type of OP is passed to a function, as AngelScript will not allow any type casting (unless explicitly registered by the application).

However, in order to not allow AS to keep a copy of the OP after the function returns, would mean that you know exactly what AS does with the OP. For example it should be allowed to store the OP in a local variable, but not a global variable, it must not be possible to store the OP in a class member, unless the class itself is guaranteed to die after the function returns. I'm not sure how you expect AngelScript to control this. You could try to do it yourself by enumerating all global variables after the function has returned and destroy the OP that might have been stored, and also look for objects that may be alive that also may keep the OP in one of its members.

If you really want full control over the OP, I suggest you either register it as a singleton with the asOBJ_NOHANDLE flag. This will allow the script to access the value through a registered global property, but not store or copy the value. On the other hand this also won't allow the script to pass the value as a parameter to a function, as accesses to the value has to go through the global property.

Alternatively you can implement a proxy class, with reference counting. Instead of passing the OP to the script, you would pass the proxy. The script would be able to pass the proxy around as a normal reference object, and would even be able to hold multiple copies of it, however as soon as you no longer want the script to have access to the OP the application clears the internal pointer in the proxy object. This won't destroy the actual proxy, but it would turn it into a null pointer that the script can handle at the next access. I do something similar to this in my own game engine. You can also check the 'game' sample in the latest release where I implemented this.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement