Sign in to follow this  
MichaelDeCicco

Need help calling an API function from a script

Recommended Posts

I'm building a scripting language for fun and the learning experience. So far I've discovered that using variadic templates I can call a function pointer that has been cast to [font=courier new,courier,monospace]void (*)()[font=arial,helvetica,sans-serif] using this function class:[/font][/font]

[source lang="cpp"]
template <typename ReturnType>
class EFunction : public BFunction
{
public:
typedef void (*FuncPtr)();

EFunction() : Function(0) { Type = BFunction::FT_ENVIRONMENT; }
template <typename FuncType>
EFunction(FuncType Func,const UString &FName) : Function((FuncPtr)Func) { Type = BFunction::FT_ENVIRONMENT; Name = FName; }
~EFunction() { }

template <typename FuncType>
void SetFunction(FuncType Func)
{
Function = (FuncPtr)Func;
}
FuncPtr GetFunction() const { return Function; }

template <typename... Arguments>
ReturnType Call(Arguments... Params)
{
ReturnType (*Func)(Arguments...) = (ReturnType (*)(Arguments...))Function;
return Func(Params...);
}

protected:
FuncPtr Function;
};
[/source]
and calling it like this:
[source lang="cpp"]
EFunction <int>Print(printf);
int Result = Print.Call("Hello, %s","world!\n");
Print.Call("Result is: %d\n",Result);
[/source]
The output of this would be:

Hello, world!
Result is: 14

This is all fine and good, I think. I only just learned about variable template arguments. Please tell me if anything I've done here is bad or in bad form.

With this in mind, my question is this: when I bind a function call on the C++ side to a function on the script side, when the execution gets to the point when it actually calls that function on the C++ side, how do I pass the arguments? Assume that I have all of the parameter values in an array with an enumeration value that defines the data type of that variable.

Do I need to get my hands dirty with assembly to push the values to the stack manually?

Share this post


Link to post
Share on other sites
Asssuming that your interpreter is not in a separate process, why not use a generic variadic call function, like you already have, that will convert your C++ arguments into whatever the scripting language wants, and then runs the relevant chunk with those parameters? Not knowing anything about your scripting language, this is somewhat reductionist, but it seems plausible to me.

To generically do the actual conversions to script types though, you probably have to use template metaprogramming to unpack and convert the parameter pack to the appropriate types. Maybe pack them into a tuple, and then call the script function with the converted arguments from the tuple. If you're willing to write some boilerplate, you wouldn't have to use template mp....just custom arg conversion functions for each script function that you want to call.

Share this post


Link to post
Share on other sites
I would be very cautious when dealing with function pointer casts like you are using in your template. In particular with the example you've decided to use, printf() is a variadic function, which means that it's calling convention is slightly different than the calling convention of a non-variadic function called with the same argument types. One of the peculiarities of the variadic function calling convention is that floats are automatically expanded to doubles when used as an argument for a variadic function. Further, on some platforms, floats and doubles are often passed in registers in regular functions, but placed on the stack just for variadic functions. Compare these two calls:
[code]
printf("%f %f %f %f\n", 1.0f, 2.0f, 3.0f, 4.0f);
Print.Call("%f %f %f %f\n", 1.0f, 2.0f, 3.0f, 4.0f);
[/code]
With clang 3.1 on i386-pc-cygwin it spits out:

1.000000 2.000000 3.000000 4.000000
2.000000 512.000123 512.000000 2.000000

In any case, there are two basic approaches to calling a registered function. The first is to break out assembly to manipulate the stack and stick the right values into registers to call the function (and do clean up afterwards if the calling convention requires it). Alternately, you can instead create an interface for extracting function parameters and setting a return type and instead of directly using function pointers to the function, instead use functions that manipulate that interface. For example, AngelScript uses this interface, which should be fairly self-explanatory, for what it calls its generic calling convention:
[code]
class asIScriptGeneric
{
public:
// Miscellaneous
virtual asIScriptEngine *GetEngine() const = 0;
#ifdef AS_DEPRECATED
// Deprecated since 2.24.0 - 2012-05-25
virtual int GetFunctionId() const = 0;
virtual void *GetFunctionUserData() const = 0;
#endif
virtual asIScriptFunction *GetFunction() const = 0;

// Object
virtual void *GetObject() = 0;
virtual int GetObjectTypeId() const = 0;

// Arguments
virtual int GetArgCount() const = 0;
virtual int GetArgTypeId(asUINT arg) const = 0;
virtual asBYTE GetArgByte(asUINT arg) = 0;
virtual asWORD GetArgWord(asUINT arg) = 0;
virtual asDWORD GetArgDWord(asUINT arg) = 0;
virtual asQWORD GetArgQWord(asUINT arg) = 0;
virtual float GetArgFloat(asUINT arg) = 0;
virtual double GetArgDouble(asUINT arg) = 0;
virtual void *GetArgAddress(asUINT arg) = 0;
virtual void *GetArgObject(asUINT arg) = 0;
virtual void *GetAddressOfArg(asUINT arg) = 0;

// Return value
virtual int GetReturnTypeId() const = 0;
virtual int SetReturnByte(asBYTE val) = 0;
virtual int SetReturnWord(asWORD val) = 0;
virtual int SetReturnDWord(asDWORD val) = 0;
virtual int SetReturnQWord(asQWORD val) = 0;
virtual int SetReturnFloat(float val) = 0;
virtual int SetReturnDouble(double val) = 0;
virtual int SetReturnAddress(void *addr) = 0;
virtual int SetReturnObject(void *obj) = 0;
virtual void *GetAddressOfReturnLocation() = 0;

protected:
virtual ~asIScriptGeneric() {}
};
[/code]
Note that it's possible to use templates to automatically generate a function that uses this kind of interface from a given C++ function. For an example of that, you can look at AngelScript's autowrapper add-on.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this