• Create Account

## Passing an Opaque Pointer to AngelScript

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

12 replies to this topic

### #1TrickyLogic  Members

Posted 24 September 2011 - 05:11 AM

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 ). 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!

### #2SiCrane  Moderators

Posted 24 September 2011 - 07:20 AM

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

### #3TrickyLogic  Members

Posted 24 September 2011 - 09:06 AM

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.

### #4SiCrane  Moderators

Posted 24 September 2011 - 09:58 AM

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.

### #5TrickyLogic  Members

Posted 24 September 2011 - 11:57 AM

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.

### #6Andreas Jonsson  Moderators

Posted 25 September 2011 - 10:37 AM

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

### #7TrickyLogic  Members

Posted 25 September 2011 - 12:29 PM

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;
}


### #8Andreas Jonsson  Moderators

Posted 25 September 2011 - 04:51 PM

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

### #9TrickyLogic  Members

Posted 26 September 2011 - 09:26 AM

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)?

### #10Andreas Jonsson  Moderators

Posted 27 September 2011 - 07:29 PM

• 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!

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

### #11TrickyLogic  Members

Posted 28 September 2011 - 01:29 PM

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.

A class with an OP shouldn't be stored globally. To make it easier I wouldn't mind simply disallowing all global variables (it's good design anyway) I think I can easily patch this in myself with the following code (in as_compiler.cpp, after line 6269):
		if( found ){
Error( "ERROR: NO GLOBAL VARIABLES ALLOWED!", errNode );
return -1;
}


All state data between calls will then be stored outside of AS.

This means that I'll be running a modified version of AS. I think making a better solution will require quite some work (disallowing classes with OPs to be stored in global variables) on your end and I don't want to deliver you any more work than necessary.

Unfortunately, I'm still not sure how disallow the following AS code (any pointers willbe greatly appreciated):

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


While still allowing:

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


The "IsVariableInitialized" returned always "true" for the code I tested it with. I placed the following code right at the beginning/end of "asCCompiler::CompileVariableAccess" hoping that it would return false for the first case of "SomeData".
	cout << "  IsVariableInitialized" << name.AddressOf() << ", " << IsVariableInitialized( &ctx->type, errNode ) << endl;


Alternatively you can implement a proxy class [..]

This is an interesting alternative, I'll consider it. I'm not a big fan of runtime errors when they can be found at compile time. There is also a (small?) amount of additional overhead/complexity.

### #12Andreas Jonsson  Moderators

Posted 28 September 2011 - 06:50 PM

You don't have to modify the library to disable use of global variables. Just add a simple check after the compilation, e.g.

mod->Build();

// Check if global variables were declared
if( mod->GetGlobalVarCount() )
{
// Give error
return -1;
}


I may however consider adding an engine property to disable global variables, that would allow you to do this with a simple engine->SetEngineProperty(asEP_DISABLE_GLOBAL_VARS);

Why do you have to disallow the declaration of a local variable of the OP type? The script cannot assign an invalid value to it anyway. It would just initialize to null, which your code is hopefully treating anyway.

However, if you really wish to prevent it, the best place to do it is the asCCompiler::CompileDeclaration(). Give an error in this function unless the declaration is followed by an assignment.

The IsVariableInitialized only works for primitives, as it is assumed that registered types have a proper default value.

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

### #13TrickyLogic  Members

Posted 29 September 2011 - 01:38 PM

You don't have to modify the library to disable use of global variables. Just add a simple check after the compilation, e.g.

Neat! I'll check this out tomorrow, I didn't have time to code anything today.

Why do you have to disallow the declaration of a local variable of the OP type? The script cannot assign an invalid value to it anyway. It would just initialize to null, which your code is hopefully treating anyway.

That would be great. Even if it's not checked at compile time it would be enough for me to use it. If I can get both techniques working like you described I'll be using AngelCode in my next project.

Thanks for your patience and help.

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.