Catching call by value/return by value with class types

Started by
15 comments, last by WitchLord 16 years, 9 months ago
I've noticed that a common error with using AngelScript is registering a native function that accepts a class type by value. One way to address this would be to modify the function cast macros to detect non-fundamental types passed or returned by value. I implemented proof of concept versions of asMETHOD, asMETHODPR, asFUNCTION and asFUNCTIONPR. They seem to work, but the template ugliness involved cause a noticeable increase in compile times. Well, so the code doesn't go to waste, I present a stand alone add on header file that adds four new macros: asMETHOD_STRICT, asMETHODPR_STRICT, asFUNCTION_STRICT and asFUNCTIONPR_STRICT that will cause compiler errors if the functions take class arguments by value or return them, and will also detect (and error out) trying to register a __fastcall function. Caveats: the code as presented only recognizes functions with up to four arguments. The code will fail to compile on platforms that fail to support __cdecl, __stdcall and __fastcall function types. The code will fail to compile on compilers that don't properly support static const member variables. The error message for a returning by value is the same as calling by value. The code only recognizes fundamental types defined by the C++ language; vendor specific primitive type extensions will fail. (Then again, those types probably wouldn't be reconginzed by AngelScript, so maybe that's a good thing.) The header will also pollute the AngelScript namespace (Koenig lookup becomes unhappy if that's not the case). Code is a product of gratuitous copy and paste and so may contain spot bugs. It would be possible to create a version with a boost dependency that could address a number of these issues (in particular boost::type_traits and boost::preprocessor). It'd probably make the compile times even worse, however.

#ifndef ASWRAP_STRICT_FN_MACROS_H
#define ASWRAP_STRICT_FN_MACROS_H

#include "angelscript.h"

BEGIN_AS_NAMESPACE

  template <typename T> 
  struct asIsFundamental {
    static const bool value = false;
  };
  #define MAKE_FUNDAMENTAL(type) template <> struct asIsFundamental<type> { static const bool value = true; }
  MAKE_FUNDAMENTAL(bool);
  MAKE_FUNDAMENTAL(signed char);
  MAKE_FUNDAMENTAL(signed short);
  MAKE_FUNDAMENTAL(signed long);
  MAKE_FUNDAMENTAL(signed long long);
  MAKE_FUNDAMENTAL(unsigned char);
  MAKE_FUNDAMENTAL(unsigned short);
  MAKE_FUNDAMENTAL(unsigned long);
  MAKE_FUNDAMENTAL(unsigned long long);
  MAKE_FUNDAMENTAL(float);
  MAKE_FUNDAMENTAL(double);
  MAKE_FUNDAMENTAL(char);
  MAKE_FUNDAMENTAL(signed int);
  MAKE_FUNDAMENTAL(unsigned int);
  MAKE_FUNDAMENTAL(long double);
  MAKE_FUNDAMENTAL(wchar_t);
  
  #undef MAKE_FUNDAMENTAL

  template <bool t>
  struct asERROR_CannotBindClassArgumentsByValue;

  template <>
  struct asERROR_CannotBindClassArgumentsByValue<true> {
  };

  struct UndefinedType;
  void asERROR_CannotBindFastCallFunctions(const UndefinedType &);

  template <typename T>
  struct asMapper {
    typedef asERROR_CannotBindClassArgumentsByValue<asIsFundamental<T>::value> t;
  };  

  template <typename T>
  struct asMapper<T *> {
    typedef int t;
  };

  template <typename T>
  struct asMapper<T &> {
    typedef int t;
  };

  template <typename T>
  struct asMemFnCheckerWorker;

  template <typename C, typename R>
  struct asMemFnCheckerWorker<R (C::*)()> {
    static asUPtr Convert(R (C::*ptr)()) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (C::*BasePtr)();
      return asSMethodPtr<sizeof(void (C::*)())>::Convert(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename C, typename R, typename A1>
  struct asMemFnCheckerWorker<R (C::*)(A1)> {
    static asUPtr Convert(R (C::*ptr)(A1)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      typedef void (C::*BasePtr)();
      return asSMethodPtr<sizeof(void (C::*)())>::Convert(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename C, typename R, typename A1, typename A2>
  struct asMemFnCheckerWorker<R (C::*)(A1, A2)> {
    static asUPtr Convert(R (C::*ptr)(A1, A2)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      typedef void (C::*BasePtr)();
      return asSMethodPtr<sizeof(void (C::*)())>::Convert(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename C, typename R, typename A1, typename A2, typename A3>
  struct asMemFnCheckerWorker<R (C::*)(A1, A2, A3)> {
    static asUPtr Convert(R (C::*ptr)(A1, A2, A3)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      (void)sizeof(typename asMapper<A3>::t);
      typedef void (C::*BasePtr)();
      return asSMethodPtr<sizeof(void (C::*)())>::Convert(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename C, typename R, typename A1, typename A2, typename A3, typename A4>
  struct asMemFnCheckerWorker<R (C::*)(A1, A2, A3, A4)> {
    static asUPtr Convert(R (C::*ptr)(A1, A2, A3, A4)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      (void)sizeof(typename asMapper<A3>::t);
      (void)sizeof(typename asMapper<A4>::t);
      typedef void (C::*BasePtr)();
      return asSMethodPtr<sizeof(void (C::*)())>::Convert(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename T>
  struct asFnCheckerWorker;

  template <typename R>
  struct asFnCheckerWorker<R (__cdecl *)()> {
    static asUPtr Convert(R (__cdecl * ptr)()) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R>
  struct asFnCheckerWorker<R (__stdcall *)()> {
    static asUPtr Convert(R (__stdcall * ptr)()) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R>
  struct asFnCheckerWorker<R (__fastcall *)()> {
    static asUPtr Convert(R (__fastcall * ptr)()) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      asERROR_CannotBindFastCallFunctions(ptr);
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename R, typename A1>
  struct asFnCheckerWorker<R (__cdecl *)(A1)> {
    static asUPtr Convert(R (__cdecl * ptr)(A1)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1>
  struct asFnCheckerWorker<R (__stdcall *)(A1)> {
    static asUPtr Convert(R (__stdcall * ptr)(A1)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1>
  struct asFnCheckerWorker<R (__fastcall *)(A1)> {
    static asUPtr Convert(R (__fastcall * ptr)(A1)) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      asERROR_CannotBindFastCallFunctions(ptr);
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename R, typename A1, typename A2>
  struct asFnCheckerWorker<R (__cdecl *)(A1, A2)> {
    static asUPtr Convert(R (__cdecl * ptr)(A1, A2)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1, typename A2>
  struct asFnCheckerWorker<R (__stdcall *)(A1, A2)> {
    static asUPtr Convert(R (__stdcall * ptr)(A1, A2)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1, typename A2>
  struct asFnCheckerWorker<R (__fastcall *)(A1, A2)> {
    static asUPtr Convert(R (__fastcall * ptr)(A1, A2)) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      asERROR_CannotBindFastCallFunctions(ptr);
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename R, typename A1, typename A2, typename A3>
  struct asFnCheckerWorker<R (__cdecl *)(A1, A2, A3)> {
    static asUPtr Convert(R (__cdecl * ptr)(A1, A2, A3)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      (void)sizeof(typename asMapper<A3>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1, typename A2, typename A3>
  struct asFnCheckerWorker<R (__stdcall *)(A1, A2, A3)> {
    static asUPtr Convert(R (__stdcall * ptr)(A1, A2, A3)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      (void)sizeof(typename asMapper<A3>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1, typename A2, typename A3>
  struct asFnCheckerWorker<R (__fastcall *)(A1, A2, A3)> {
    static asUPtr Convert(R (__fastcall * ptr)(A1, A2, A3)) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      asERROR_CannotBindFastCallFunctions(ptr);
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };

  template <typename R, typename A1, typename A2, typename A3, typename A4>
  struct asFnCheckerWorker<R (__cdecl *)(A1, A2, A3, A4)> {
    static asUPtr Convert(R (__cdecl * ptr)(A1, A2, A3, A4)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      (void)sizeof(typename asMapper<A3>::t);
      (void)sizeof(typename asMapper<A4>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1, typename A2, typename A3, typename A4>
  struct asFnCheckerWorker<R (__stdcall *)(A1, A2, A3, A4)> {
    static asUPtr Convert(R (__stdcall * ptr)(A1, A2, A3, A4)) {
      (void)sizeof(typename asMapper<R>::t);
      (void)sizeof(typename asMapper<A1>::t);
      (void)sizeof(typename asMapper<A2>::t);
      (void)sizeof(typename asMapper<A3>::t);
      (void)sizeof(typename asMapper<A4>::t);
      typedef void (*BasePtr)();
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };
  template <typename R, typename A1, typename A2, typename A3, typename A4>
  struct asFnCheckerWorker<R (__fastcall *)(A1, A2, A3, A4)> {
    static asUPtr Convert(R (__fastcall * ptr)(A1, A2, A3, A4)) {
      (void)sizeof(typename asMapper<R>::t);
      typedef void (*BasePtr)();
      asERROR_CannotBindFastCallFunctions(ptr);
      return asFunctionPtr(reinterpret_cast<BasePtr>(ptr));
    };
  };


  template <typename T>
  asUPtr asMemFnChecker(T f) {
    return asMemFnCheckerWorker<T>::Convert(f);
  }

  template <typename T>
  asUPtr asFnChecker(T f) {
    return asFnCheckerWorker<T>::Convert(f);
  }

  #define asMETHOD_STRICT(c,m) asMemFnChecker(&c::m)
  #define asMETHODPR_STRICT(c,m,p,r) asMemFnChecker((r (c::*)p)(&c::m))

  #define asFUNCTION_STRICT(f) asFnChecker(f)
  #define asFUNCTIONPR_STRICT(f,p,r) asFnChecker((r (*)p)(f)))

END_AS_NAMESPACE

#endif

Advertisement
This is really cool. I was wondering if it was possible to detect the calling convention somehow so that I can validate that parameter in AngelScript. Now you've shown me how to do that.

I actually don't want to prevent passing non-primitive types by value, because AngelScript already supports that. The problem is when trying to pass a reference counted type by value, since AngelScript won't know how to deal with that.

I'm going to spend time on improving validations that AngelScript does, because that seems to be the biggest problem new comers have. What I want to do is to provide an interface in the engine that let you enumerate the parameter types for the registered function, and with that it ought to be possible to write template functions the verify that the AngelScripr parameter type matches the C++ parameter type. I'm not very good with templates, but your example should give me a good starting point for developing what I had intended.

It shall also be easier to register new types in AngelScript. The application writer should for example inform AngelScript how the type is to be handled, i.e. if it is to be handled as a reference counted type, or a primitive type, etc. With this extra information AngelScript will be able to validate the behaviours that are registered as well.

Thanks,
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

Well, I've been working on a wrapper for validating function arguments too. Here's the current testbed code. The main.cpp shows an example of the wrapped calls, which come in two varieties: calling when you know the function id/signature, and calling when you just know the name and module, which are represented by the classes CallableId and CallableName in the callable.h header.

In order to operate, the system requires that types be registered via the EngineProxy class in the engine_proxy.h header. The register_class, does among other things, takes the typeid of the registered class and places it in a type library class along with the name. When you call a function by name, it uses the typeids of the templated arguments of the operator() overload to generate a list of asCDataTypes, which is then use in a very rough overloading match against registered functions. With a match it extracts the (hopefully) correct function id which is then used to call the function by id.

In the call by id operator() call, the arguments are packed into the script context via the Boxer<> template class in boxer.h. The boxer class then matches the type information in the context's initialFunction->parameterTypes to determine the correct SetArg*() function to call. So it'll always call SetArgDouble() when called against a function that accepts a double even if you pass an int.

There are still some (read: a lot of) rough spots: for example, it doesn't seem to handle calling AS functions with reference arguments properly; it requires a modified version of AS to get at the function ids when registering and it requires a lot of access to non-public parts of AS. I haven't focused a lot of effort on calling AS functions since I spent most of the time automating binding C++ functions to AS in the testbed code.

edit: Oh, the user can add specific types to be allowed to be passed by value by adding an explicit template instantiation. So if they wanted to make MyType allowed to be passed by value they can declare:
template <> struct asIsFundamental<MyType> { static const bool value = true; }

Though if you decide to implement something like this you'd probably want to rename the asIsFundamental<> type to something like asAllowPassByValue<>, the MAKE_FUNDAMENTAL() macro to something like ALLOW_PASS_BY_VALUE() and remove the #undef from the header.
I just received this from a developer at ReactorZero. It shows how they are binding the application interface to AngelScript.

They seem to have a lot of similar ideas as your own. Maybe you would be interested in taking a look at it to see if it can benefit your binding routines? You would certainly understand their template code a lot better than I. :)

Eventually I would like to provide an easier way of exposing the application interface to the script engine. Less experienced programmers suffer a lot from having to understand calling conventions, etc. The AngelScript interface just isn't as intuitive as I would like it to be. It's not bad, it just needs an abstraction layer that hides the complexities somehow.


By the way, you seem to be investing quite a bit of work on this. Are you working on any specific project that uses AngelScript, or are you still just playing around with it? Just curious. ;)

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 took a look at the Reactor Zero code, and it's got, for lack of a better term, a difference in design philosophy from the kind of wrapper code I want to use. Basically, what I'm aiming for is making only one declaration and having the function export decipher calling conventions, arguments and so on. While probably less effort than manual calls to the script system, the Reactor Zero still requires duplication of declarations.

For example, their method of exposing a global function looks like:
DECLARE_FUNCTION(void, MyCoolFunction, int, int);
I'd rather just have
register_function(&MyCoolFunction);

Most of the time when I use scripting in an application, I try to move as much of the application logic to scripts. This means that recompiles of C++ code happens relatively infrequently (which is why I'm willing to put up with the long compile cycles that all this nasty template code causes). However, since the code changes are usually dictated by the scripting system, code changes in the C++ almost always involve a change to import/export functionality, which means I want as few problems as possible there as possible; especially problems that can result in hard to diagnose runtime errors like a mistake in declaring exported types to the scripting layer that occur when you need to modify parameter lists in two places.

In any case, you could probably move the calling convention arguments from most functions if you used template calling convention detection into the the asMETHOD and asFUNCTION macros, like in the header here. Then you can embed the calling convention as a member of the asUPtr union. This would get rid of most of the callConv arguments in the Register*() functions. You'd still need a flag of some sort on RegisterObjectMethod() and RegisterObjectBehaviour() to differentiate a member function pointer from an object argument first or object argument last call.

Another place I have trouble keeping straight with the interface is the SetArgAddress, SetArgObject, GetArgPointer and GetReturnAddress, GetReturnObject, GetReturnPointer differences in the context interface and the GetArgAddress, GetArgObject, GetArgPointer and SetReturnAddress, SetReturnObject, GetReturnPointer differences in the generic interface. Especially the GetReturnPointer and GetArgPointer things since they seem to be intended to set the return value and set argument value rather than getting data. I really have no suggestions on how to help out here, though. (However, if you want suggestions on additional functionality to expose through your public interface, I'm your man.)

In any case, right now I'm evaluating AngelScript for a project I have planned. In general, I like the idea of a statically typed scripting language; so I've used it in a few minor apps and seeing what I can do with the interface. After all, you can't really see what the water is like without getting your feet wet.
I'd be very happy if it was possible to just write 'register_function(&my_function)' and it would simply work. However, I think that since AngelScript uses object handles and parameter references with extra directives it might be a bit difficult. Unless you write your application interface directly for angelscript, in which case you could simply choose to always use object handles (or something other way), which definitely would make things easier.

Yeah, I guess the introduction of GetArgPointer got a bit confusing, since it doesn't quite have the same intention as the other methods. The idea with this method (and it's like) is that you get the pointer to where the value is stored or will be stored, rather than the value itself. Perhaps I should rename these methods to GetPtrToArg, and GetPtrToRetVal. It would be a much closer representation of what they really do.

With these methods it is much easier for the application to use template functions for setting and getting values, e.g:

*(bool*)ctx->GetArgPointer(0) = boolValue;*(int*)ctx->GetArgPointer(0) = intValue;*(float*)ctx->GetArgPointer(0) = floatValue;


I had the idea of removing all the other methods, but unfortunately handling parameter references and objects got more complicated through these methods.

I'm all ears for suggestions on improving AngelScript. I assume you've already seen the to-do list, but even if what you want is on the to-do list already don't hesitate to tell me that you'd rather want it sooner than later. I always try to accomodate users' wishes, as best as I can.

I look forward to hearing more about your project in the future.


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

Our wrapper template macros are written the way they are because not all of AngelScript and C++ are "directly translatable". Sometimes wrapper functions need to be generated to expose a member/global function to the script as the original C++ function can not be used directly by AngelScript.

Examples of this include when pass-by-value or return-by-value are required. If you have a type that uses vector registers, as we do, AngelScript is not going to properly pass/return the data when it is by value (and probably never will) - thus a generic wrapper needs to be written.

Our template macros will automatically generate the wrapper functions when necessary. We made them as simple as we possibly could, but so we could still get all the functionality we need.

Can they be made simpler? Possibly. Can you have it so you can just call "RegisterFunction(&myfunction)"? I'm not too sure about (at least, not in an easy platform independent way). Are they the end all be all and can do everything? Definitely not.
I'm not trying to knock your wrappers; as I said, it's just a difference in design philosophy.

However, just because AngelScript won't do call by value/return by value properly doesn't mean that you can't get to RegisterFunction(myfunction). The key is to have RegisterFunction() generate the generic function call wrapper.

Take the register_function<>() function I use in the testbed application I've linked to in my second post in this thread. register_function<Fn>() will accept basically any function pointer (and also any function object compatible with boost::function<>). This, in turn, creates a FunctionThunk<Fn> object that disassembles the function argument types and acts as a proxy that will handle extracting arguments from the asIScriptGeneric object. The only platform dependent code in there involves categorizing the __cdecl, __stdcall and __fastcall pointer types.

On the downside, which I've mentioned before, all this template code causes compile times to slow down to a crawl and requires the compiler to have very good support for template code. (e.g. MSVC 2005 handles it, but 2003 will choke. I suspect that there are workarounds to get 2003 to work with it, but that is very low on my priority list.) The implementation I've used also requires some runtime lookup and requires modifications of the AS code, but that revolves around the fact that I want to be able register arbitrary function objects. The code can be adapted to register static member functions that don't require the id hooks.

Anyways, on my list of things for AS changes: 1) exposing the AS types to inspection an manipulation by user code. This is really a rather broad suggestion. For example, in a lot of places I've got code that looks at the asCScriptFunction::parameterTypes member to see which function to call to set arguments. But I'd also like to be able to build function declarations in a form that AS can process without needing to compile a string.

2) This is probably related to 1, but I'd also like to be able to have the AS engine determine an appropriate function overload call from an appropriate description. One example would be a version of GetFunctionIDByDecl() that if I gave it the string "void func(double, double)" would return the function id for "void func(float, float)" if I had both "void func(float, float)" and "void func(string, cow)" defined in a module.

3) I've already mentioned exposing the function id's from registration and the asIScriptGeneric interface, but (for my purposes anyways) they wouldn't be necessary if you allowed an optional void * parameter argument when registering functions.

4) I'd like to be able to call bound native functions on a context via its id. If you try to use a context with an id thats not a script function it bails out with an invalid state, so the normal execute and get return functions don't work; you need to use CallSystemFunction() and then extract the return value directly from the asCContext object.

5) Being able to use the engine to create a string object with the registered string factory.
I'll try to accomodate your wishes.

1. I intend to allow the application to enumerate paramter types and return types of functions. It should be possible to allow the application to find matching functions or even register functions by passing in a list of type ids as well.

2. This would be something like MatchFunctions in the compiler. I'll see what can be done, though with the addition of number 1 it should be possible to implement this logic outside the library.

3. I'll expose the function ids for registered functions. At first I'll have the register methods return the id, but at a later time it would also be necessary to allow the enumeration of registered functions.

As to passing the object pointer to the register function method. I've turned down this request before, but I believe you've actually given me a convincing argument for this feature. Previously users wanted to register singleton methods as global functions using this method, but you want to register functor objects. Although not quite the same, I think I can accomodate both with a simple change.

4. It would be cool if the application could use the same way for calling script functions and registered functions. I'll see if I can have this implemented, the context would have to understand that it is calling a system function rather than a script function, but that shouldn't be too difficult.

5. Well, you can already use CreateScriptObject to create a string. I understand that you wish to be able to construct objects with arguments, correct? That's definitely something I want to be able to do as well.

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

Looks like my wrapper is totally outdated now. SiCrane, I implemented most of what you want, but I wasn't using the generic calling conventions. I used an any class and bound all the types to AS using macros, so I could automatically create tables of all the type information. Then, given a list of anys, my wrapper would be able to ask them for their types, then enumerate the functions in a module and find the best match, even performing conversions if neccessary (and possible).

Perhaps we could collaborate?

Our goals might conflict somewhat, though. I've always used AngelScript just like C++. I didn't upgrade to 2.x until references were put back in, and I've never even used it's handle mechanism - I just register smart pointers, and let it pass them around by value. My wrapper reflects this. It has special code for handling references and such and can't handle the generic calling convention at all.

Also, I wouldn't mind a bit of elaboration on
Quote:I've noticed that a common error with using AngelScript is registering a native function that accepts a class type by value.
How is this anymore of an error in AngelScript than in C++? Passing a large class around by value might be a design flaw, but it's not an error. And passing small classes by value shouldn't be forbidden.

[edit]And I'm still using 2.7.0. I'll be so happy if my project still works after I bump it up to 2.8.[/edit]

This topic is closed to new replies.

Advertisement