Jump to content


(script) virtual machine to c++ binding


11 replies to this topic

#1 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 15 February 2006 - 02:13 PM

I've pretty much reached the point where I want to integrate scripting into my app. I've got this old script system of mine which I'm perfectly happy with except the binding (calling native functions from the virtual machine).

As the old script stuff was win32 only I just used asm to push the arguments on the stack and called the function address. All native functions accessable by script had a defined calling convention etc so that it worked.

This time I'd like to get away from the dirty asm and do it properly.
So the basic problem is:
I've got an array of values (the function arguments) and the function address.
Now I'd like to call the function with the arguments stored in the array.

What I don NOT want to do is to declare another version of each function that takes an array instead of the separate values. It would be ok to do that if that could be automated (some sleek looking macro/template that does it).

It certainly is possible to write a separate template for functions with different
argument count. That's still ugly.

Is there any (more or less commonly supported, automated way) to map function arguments to an array?

In other words: I'm missing a syntax that allows me to generate a list of values that I can pass to a function.
I want some decent metaprograming/code generation without having to abuse templates. Also I'd like to query the compiler (at compile time)
for the type/name of a template argument so I can auto generate the link information necessary for the vm.

Alex

#2 .oisyn

    DevMaster Staff

  • Moderators
  • 1822 posts

Posted 15 February 2006 - 03:49 PM

Interesting problem, I once was thinking of this myself when writing my own script language.

How would you store different argument types in a single array? Something like boost::any comes to mind, but then you have to pass arguments at runtime, which isn't possible with clean C++ code.

The only thing I could think of is creating a templated adapter function that casts the elements of the array to the correct type and passes it to your function. Of course, you still need to define multiple implementations for each number of arguments, as there's no way you can automate that in C++.

typedef std::vector<boost::any> ParamArray;

// base interface for calling the functions
struct adapter_base
{
    virtual void call(const ParamArray &) const = 0;
};

template<class T> struct adapter;

// for functions with 1 parameter
template<class R, class P1>
struct adapter<R (P1)> : public adapter_base
{
    typedef R (*funcptr)(P1);
    adapter(funcptr f) : m_func(f) { }
    void call(const ParamArray & p) const { m_func(p[0].as<P1>()); }

private:
    funcptr m_func;
};


// for functions with 2 parameters
template<class R, class P1, class P2>
struct adapter<R (P1, P2)> : public adapter_base
{
    typedef R (*funcptr)(P1, P2);
    adapter(funcptr f) : m_func(f) { }
    void call(const ParamArray & p) const { m_func(p[0].as<P1>(), p[1].as<P2>()); }

private:
    funcptr m_func;
};


// and with 3
template<class R, class P1, class P2, class P3>
struct adapter<R (P1, P2, P3)> : public adapter_base
{
    typedef R (*funcptr)(P1, P2, P3);
    adapter(funcptr f) : m_func(f) { }
    void call(const ParamArray & p) const
    {
        m_func(p[0].as<P1>(), p[1].as<P2>(), p[2].as<P3>());
    }

private:
    funcptr m_func;
};


// 4
template<class R, class P1, class P2, class P3, class P4>
struct adapter<R (P1, P2, P3, P4)> : public adapter_base
{
    typedef R (*funcptr)(P1, P2, P3, P4);
    adapter(funcptr f) : m_func(f) { }
    void call(const ParamArray & p) const
    {
        m_func(p[0].as<P1>(), p[1].as<P2>(), p[2].as<P3>(), p[3].as<P4>());
    }

private:
    funcptr m_func;
};

// etc.


// handy helper function
template<class T>
adapter<T> make_adapter(T * func)
{
    return adapter<T>(func);
}



// putting it all to the test
void myfunc(int i, float f, const char * str)
{
    std::cout << i << f << str << std::endl;
}

int main()
{
    ParamArray params;
    params.push_back(34);
    params.push_back(23.34f);
    params.push_back("hi");

    const adapter_base & f = make_adapter(&myfunc);
    f.call(params);
}

(If you don't have boost installed, here's a bare minimum boost::any implementation that will make the above code compile and run
namespace boost
{
	struct any
	{
		any() { }
		template<class T> any(T t) { as<T>() = t; }

		template<class T> T & as() { return reinterpret_cast<T&>(data); }
		template<class T> const T & as() const { return reinterpret_cast<const T&>(data); }

		union { int i; float f; const char * str; } data;
	};
}

:) )
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#3 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 15 February 2006 - 08:25 PM

Thx .oisyn.

The actual array is not a problem in my case. It's just a window into the vm's stack (where the parameters are). The script's compiler/linker guarantee that the types are ok.

Alex

#4 Vandervecken

    Member

  • Members
  • PipPip
  • 32 posts

Posted 16 February 2006 - 05:17 AM

Here is a C way of doing it.. kinda.. you'd have to clean up this psuedocode though.

typedef int  generic;  // replace this with a type factory

void CallFunctionFromVM(void* function, generic* argArray, uint argCount)
{
	switch (argCount) {
	case 0:
		( ( (void*)() ) function ) ( );
		return;

	case 1:
		( ( (void*)(generic*) ) function ) ( argArray[0] );
		return;

	case 2:
		// todo: replace this with a type factory that you determine the type of each
		//       argument; many ways to do it easiest is just to store the types
		//       in your array
		( ( (void*)(generic*, generic*) ) function ) ( argArray[0], argArray[1] );
		return;

	// add more if needed
	default:
		assert_message(false, "unsupported argument count");
		return;
}

// for kicks if you dont actually have a built array for some reason
void Call(void* function, uint count, ...)
{
#ifdef DEPENDANT_UPON_YOUR_COMPILERS_VAARG_IMPLEMENTATION
	CallFunctionFromVM(function, (generic*)(&count + sizeof(count)), count)
#else
	generic x[MAX_FUNCTION_ARGS];

	va_list a;
	va_start(a, count);
	for (int i=0; i<count; i++) x[i] = va_arg(a, generic);
	va_end(a);

	CallFunctionFromVM(function, x, count)
#endif
}

I kept trying to think of a way to generate the function call at runtime (your asm approach) but I think every way is compiler and architecture dependant.

#5 geon

    Senior Member

  • Members
  • PipPipPipPip
  • 893 posts

Posted 16 February 2006 - 05:23 PM

Check how Angelscript does it.
http://www.angelcode.com/angelscript/

#6 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 17 February 2006 - 11:26 AM

hm...after some pondering about this I pretty much came to the conclusion that I'll stick to some conditionally compiled (depending on the target platform/compiler) asm generating the function call. It appears to be impossible to pass the arguments in any sane way that doesn't require another virtual function call (to abstract the different call implementations).
Eventually I'd write tons of code, make things slower with a virtual function call ...and all that just to avoid a simple block of asm that pushes the arguments backwards onto the stack , the last argument being the this pointer (cause all script callable funcs are __cdecl).

Alex

p.s. I demand the language to be enhanced to enable me to do this without asm!!!

#7 .oisyn

    DevMaster Staff

  • Moderators
  • 1822 posts

Posted 17 February 2006 - 12:37 PM

I don't see the problem of the extra virtual call, But you could also use a function pointer if you don't use the interface and make the call method static. And you can also remove the function pointer you pass to the register function by making the function pointer a template argument. But again, I don't see the point.

Quote

p.s. I demand the language to be enhanced to enable me to do this without asm!!!
Now that's just nonsense, the language already does this perfectly as I showed.
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#8 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 17 February 2006 - 01:23 PM

The extra virtual call makes things slower than necessary. Replacing the virtual call with any other kind of indirection will not improve the performance (compared to the virtual call). The vm needs a generic interface to call the different member functions of different base clases.

Now your code is fine (it's pretty much what I described in my first post, a different template for each possible number of arguments...so by posting that you were missing the point)..but it's far from elegant. That's not your fault, but the language doesn't allow to do it in a more elegant manner ("Of course, you still need to define multiple implementations for each number of arguments, as there's no way you can automate that in C++.").

So what I meant to say was: I'd like an enhancement so that I can create code that is as efficient as the asm version and doesn't require me to generate tons of redundant code by hand that could be auto generated (I thought that's obvious).

Finally I have to say that I'm thankfull for your responses but a slighty less arrogant/smart ass way of answering would be greatly apreciated. I'm rather confident that you're not the only one who can code/has experience on this board.

Alex

#9 bramz

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 17 February 2006 - 01:45 PM

virtual calls? what virtual calls?

edit: oh that one =)

now, be honest, if you're writing some code in a SCRIPT language, does one virtual call really matter?
and for that 'multiple implementations for each number of arguments' thing, that code can easily be generated ... That's how I did it anyway ...
hi, i'm a signature viruz, plz set me as your signature and help me spread :)
Bramz' warehouse | LiAR isn't a raytracer

#10 .oisyn

    DevMaster Staff

  • Moderators
  • 1822 posts

Posted 17 February 2006 - 02:49 PM

Alex said:

Now your code is fine (it's pretty much what I described in my first post, a different template for each possible number of arguments...so by posting that you were missing the point)..but it's far from elegant.
I agree, but I don't see why that's a problem. You only need to define them once, and you can easily auto-generate it with a script. But I can see how I was missing the point, I was mistakenly thinking that the one using your vm had to be saved of the trouble of specifying the function type by hand, instead of you simply wanting to clean up your implementation.

Quote

That's not your fault
Of course not, and I would never take it that way :)

Quote

So what I meant to say was: I'd like an enhancement so that I can create code that is as efficient as the asm version and doesn't require me to generate tons of redundant code by hand that could be auto generated (I thought that's obvious).
You can as efficiently, and the redundant code doesn't need to be written by hand. Of course, I can see how script generated code isn't a clean solution as how it could be, but unfortunately there are no better alternatives.

Quote

Finally I have to say that I'm thankfull for your responses but a slighty less arrogant/smart ass way of answering would be greatly apreciated.
Geez, don't get your panties in a knot. I'm sorry, but if this is your reaction to my "that's nonsense" statement I have absolutely no idea why you say it. I wasn't being a smartass, nor arrogant. If you see it that way I suggest next time try harder not to interpret things in the worst possible way. Also, if this pushes your buttons maybe you shouldn't react like that yourself. A sentence starting with "I demand" and ending in exclamation marks can be interpreted pretty arrograntly. Not that I did, btw, so I suggest you do the same :). I don't think that I'm better than you, and I'm merely trying to help (I wouldn't have written the piece of example code by hand if I wasn't - that it turns out it didn't help you is of course besides the point).

So, friends? :)
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#11 Alex

    Valued Member

  • Members
  • PipPipPip
  • 152 posts

Posted 17 February 2006 - 03:26 PM

That's funny..when I wrote my last post I wondered what exactly made u interpret my post in the way you did and why exactly it was nonsense especially as you had agreed that auto generating the code (using c++) isn't possible.
It appeared you tried very hard to not see my point but instead to show that I'm talking nonsense. Apparently it wasn't meant that way , I'm sorry.

Obviously I'm not in the position to demand anything done to c++. So my demand was about as serious as your "nonsense" :)

Let's make a deal..next time you think I wrote total nonsense pls read my post again to see if I might have meant something different ;)

sure..friends

Alex

#12 .oisyn

    DevMaster Staff

  • Moderators
  • 1822 posts

Posted 17 February 2006 - 03:58 PM

Right, I seem to have misread the aforementioned post. Sorry about that :)
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users