如何使用范型技术在C++中添加对JavaScript的支持

来源:互联网 发布:软件开发的发展前景 编辑:程序博客网 时间:2024/06/05 13:18

前段时间在做M2M产品开发过程中需要加入对JavaScript的支持,以便使很多逻辑可以下载到嵌入式网关中执行。我比较了现在三种主要的JavaScript引擎,分别是: SpiderMonkey,MS JS,V8,最后选择使用SpiderMonkey做为项目中使用的引擎。关于三种引擎的比较我以后会写出来。

在使用SpiderMonkey中发现要把现在有的C++类和函数导入jscontext是件很麻烦的事,你必须把可变类型的jsval转换成有明确类型的C++变量传递给相应的被调用函数。这个工作需要你大量地编写差不多的代码。可以想像如果你要把50个类,平均每个类有10个方法,而每个方法又有5个参数,那这个工作量可能是你的梦魇。而且你辛苦写的代码可能有很多bug,对测试工作也是一个极大的考验。并且在SpiderMonkey中并不支持C++的异常机制,如果你在C++的函数中产生异常并没有处理那么就会导致整个程序崩溃。于是我考虑使用boost提供的meta-programming机制来实现类型的自动转换。
下面我贴出部分代码供大家分享:
template<typename FuncT, class From = typename mpl::begin<ft::parameter_types<FuncT>>::type,
    class To = typename mpl::end<ft::parameter_types<FuncT>>::type>
struct CPPInvoker;

template<typename FuncT, class From, class To>
struct CPPInvoker
{
    // add an argument to a Fusion cons-list for each parameter type
    template<typename FusionArgsT>
    static inline void apply(FuncT func, JSContext* cx, jsval* argv, FusionArgsT const& fuArgs, jsval& rval)
    {
        typedef typename mpl::deref<From>::type arg_type;
        typedef typename mpl::next<From>::type next_iter_type;
        CPPInvoker<FuncT, next_iter_type, To>::apply(func, cx, argv + 1, fusion::push_back(fuArgs, jsval_to<arg_type>(cx, *argv)), rval);
    }
};

template<typename FuncT, class To>
struct CPPInvoker<FuncT, To, To>
{
    // the argument list is complete, now call the function
    template<typename FusionArgsT>
    static inline void apply(FuncT func, JSContext* cx, jsval* argv, FusionArgsT const& fuArgs, jsval& rval)
    {
        applyRetTIsVoid(func, cx, argv, fuArgs, rval, is_void<ft::result_type<FuncT>::type>());
    }

    template<typename FusionArgsT>
    static inline void applyRetTIsVoid(FuncT func, JSContext* cx, jsval* argv, FusionArgsT const& fuArgs, jsval& rval, const false_type&)
    {
        rval = to_jsval(cx, fusion::invoke(func, fuArgs));
    }

    template<typename FusionArgsT>
    static inline void applyRetTIsVoid(FuncT func, JSContext* cx, jsval* argv, FusionArgsT const& fuArgs, jsval& rval, const true_type&)
    {
        fusion::invoke(func, fuArgs);
    }
};

class CDefineFuncBase
{
public:
    JSFunctionSpec spec;
};

template<typename FuncT, int _NO = 0>
class CDefineFunc : public CDefineFuncBase
{
public:
    CDefineFunc(const char* pszFuncName, FuncT func)
    {
        s_func = func;
        spec.name = pszFuncName;
        spec.flags = 0;
        spec.call = &call_func<is_member_function_pointer<FuncT>::type>;
        spec.extra = 0;
        // Get count of parameters to register function in SpiderMonkey
        spec.nargs = ft::function_arity<FuncT>::value - 1;
    }
private:
    static FuncT s_func;

    template<typename IsMemberT>
    static JSBool call_func(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
    {
        typedef typename mpl::begin<ft::parameter_types<FuncT>>::type BeginT;
        typedef typename remove_reference<mpl::deref<BeginT>::type>::type ClassT;
        SMW_JS_TRY
            CPPInvoker<FuncT, mpl::next<BeginT>::type>::apply(s_func, cx, argv, fusion::push_back(fusion::nil(), JSObjectConvert<ClassT*>(cx, obj)), *rval);
        SMW_JS_CATCH(cx)
    }

    template<>
    static JSBool call_func<false_type>(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
    {
        typedef typename mpl::begin<ft::parameter_types<FuncT>>::type BeginT;
        SMW_JS_TRY
            CPPInvoker<FuncT, mpl::next<BeginT>::type>::apply(s_func, cx, argv, fusion::push_back(fusion::nil(), cx), *rval);
        SMW_JS_CATCH(cx)
    }
};
template<typename FuncT, int _NO> FuncT CDefineFunc<FuncT, _NO>::s_func;

template<class ClassT>
class CJSObject : public CJSObjectBase
{
public:
    CJSObject(JSContext* cx, JSObject *obj) : CJSObjectBase(cx, obj) {}

    // Registers a function with the interpreter.
    template<int _NO, typename FuncT>
    static typename enable_if<ft::is_member_function_pointer<FuncT>>::type DefineMember(const char* pszFunctionName, FuncT func)
    {
        for (unsigned i = 0; i < ms_Members.size(); ++i)
            if (std::string(ms_Members[i]->spec.name) == pszFunctionName) return;
        ms_Members.push_back(static_cast<CDefineFuncBase*>(new CDefineFunc<FuncT, _NO>(pszFunctionName, func)));
    }
    static ClassT* This() { return (ClassT*)0; }
    
    template<class PropertyT>
    static void DefineProperty(const char* pszPropertyName, PropertyT& _property, unsigned _attributes = JSPROP_ENUMERATE)
    {
        for (int i = 0; i < ms_PropertySpecCount; ++i)
            if (std::string(ms_PropertySpecs[i].name) == pszPropertyName) return;
        ms_PropertySpecs[ms_PropertySpecCount].name = pszPropertyName;
        ms_PropertySpecs[ms_PropertySpecCount].tinyid = ms_PropertySpecCount;
        ms_PropertySpecs[ms_PropertySpecCount].flags = _attributes;
        ms_GetPropertyFunctions.push_back(boost::bind(&_getProperty<PropertyT>, _1, _2, (int)&_property));
        ms_SetPropertyFunctions.push_back(boost::bind(&_setProperty<PropertyT>, _1, _2, (int)&_property, _3));
        ++ms_PropertySpecCount;
    }

    static void Register(JSContext* cx, const char* pszClassName, JSObject* obj = NULL)
    {
        JSFunctionSpec ClassMethodSpecs[MAX_CLASS_METHOD_SPECS];

        ms_JSClass.name = pszClassName;
        memset(&ClassMethodSpecs, 0, sizeof(ClassMethodSpecs));
        for (unsigned i = 0; i < ms_Members.size(); ++i)
            ClassMethodSpecs[i] = ms_Members[i]->spec;

        /* Mandatory non-null function pointer members. */
        ms_JSClass.addProperty = JS_PropertyStub;
        ms_JSClass.delProperty = JS_PropertyStub;
        ms_JSClass.getProperty = JS_getPropertyStub;
        ms_JSClass.setProperty = JS_setPropertyStub;
        ms_JSClass.enumerate   = JS_EnumerateStub;
        ms_JSClass.resolve     = JS_ResolveStub;
        ms_JSClass.convert     = JS_ConvertStub;
        ms_JSClass.construct   = Construct;
        ms_JSClass.finalize    = Destory;
        ms_JSClass.flags       = JSCLASS_HAS_PRIVATE;

        if (cx)
            SMW_SAFE_CALL(cx, ms_JSObject = JS_InitClass(cx, obj ? obj : JS_GetGlobalObject(cx), NULL, &ms_JSClass,
                Construct, 0, ms_PropertySpecs, ClassMethodSpecs, NULL, NULL));
    }

    virtual jsval RawGetObjProperty(const WString& strName)
    {
        ProperiesT::iterator pProperty = m_Properties.find(strName);
        if (pProperty != m_Properties.end())
        {
            if (pProperty->second.Type == ptMember)
                return ms_GetPropertyFunctions[pProperty->second.Value](m_cx, this);
            else if (pProperty->second.Type == ptJSVal)
                return pProperty->second.Value;
            return JSVAL_VOID;
        }
        else
            throw std::exception((string("CJSObjectBase::GetObjProperty() Bad property name ") + WStrToMBS(strName)).c_str());
    }

    virtual void RawSetObjProperty(const WString& strName, jsval Value)
    {
        ProperiesT::iterator pProperty = m_Properties.find(strName);
        if (pProperty != m_Properties.end())
        {
            if (pProperty->second.Type == ptJSVal)
                pProperty->second.Value = Value;
            else if (pProperty->second.Type == ptMember)
            {
                if (!(ms_PropertySpecs[pProperty->second.Value].flags & JSPROP_READONLY))
                    ms_SetPropertyFunctions[pProperty->second.Value](m_cx, this, Value);
            }
        }
        else if (m_bAllowAddProperty)
            DefineObjProperty(strName, _jsval(Value), JSPROP_ENUMERATE);
        else
            throw std::exception((string("CJSObjectBase::SetObjProperty() Cannot add property name ") + WStrToMBS(strName)).c_str());
    }

    static JSClass ms_JSClass;
    static JSObject* ms_JSObject;
private:
    static int ms_PropertySpecCount;
    static JSPropertySpec ms_PropertySpecs[MAX_PROPERTY_SPECS];
    static std::vector<CDefineFuncBase*> ms_Members; // stores all members of class called by SpiderMonkey
    static std::vector<function<jsval (JSContext*, void*)>> ms_GetPropertyFunctions;
    static std::vector<function<void (JSContext*, void*, jsval)>> ms_SetPropertyFunctions;

    static JSBool JS_getPropertyStub(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
    {
        SMW_JS_TRY
            if (JSVAL_IS_STRING(id))
            {
                jsval jsRet = JSObjectConvert<ClassT*>(cx, obj)->RawGetObjProperty(jsval_to<WString>(cx, id));
                if (jsRet != JSVAL_VOID) *vp = jsRet;
                return JSVAL_TRUE;
            }

            if (!JSVAL_IS_INT(id) || (JSVAL_TO_INT(id) >= (int)ms_GetPropertyFunctions.size()))
                throw std::exception("CJSObject::GetPropertyIndexByID() Bad property index!");
            *vp = ms_GetPropertyFunctions[JSVAL_TO_INT(id)](cx, JSObjectConvert<ClassT*>(cx, obj));
        SMW_JS_CATCH(cx)
    }

    static JSBool JS_setPropertyStub(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
    {
        SMW_JS_TRY
            if (JSVAL_IS_STRING(id))
                { JSObjectConvert<ClassT*>(cx, obj)->RawSetObjProperty(jsval_to<WString>(cx, id), *vp); return JSVAL_TRUE; }

            if (!JSVAL_IS_INT(id) || (JSVAL_TO_INT(id) >= (int)ms_GetPropertyFunctions.size()))
                throw std::exception("CJSObject::JS_setPropertyStub() Bad property index!");
            if (!(ms_PropertySpecs[JSVAL_TO_INT(id)].flags & JSPROP_READONLY))
                ms_SetPropertyFunctions[JSVAL_TO_INT(id)](cx, JSObjectConvert<ClassT*>(cx, obj), *vp);
        SMW_JS_CATCH(cx)
    }

    template <class PropertyT>
    static jsval _getProperty(JSContext *cx, void* _this, int _Offset)
    {
        return to_jsval(cx, *(PropertyT*)((char*)_this + _Offset));
    }

    template <class PropertyT>
    static void _setProperty(JSContext *cx, void* _this, int _Offset, jsval _Value)
    {
        *(PropertyT*)((char*)_this + _Offset) = jsval_to<PropertyT>(cx, _Value);
    }

    static JSBool Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
    {
        SMW_JS_TRY
            if (!JS_IsConstructing(cx))
                throw std::exception("CJSObject::Construct() Construct may not be called as a function!");
            ClassT* newObj = new ClassT(cx, obj, argc, argv, rval);
            *rval = OBJECT_TO_JSVAL(obj);
            for (int i = 0; i < ms_PropertySpecCount; ++i)
                newObj->m_Properties[ms_PropertySpecs[i].name] = SProperty(ptMember, i);
            for (unsigned i = 0; i < ms_Members.size(); ++i)
                newObj->m_Properties[ms_Members[i]->spec.name] = SProperty(ptFunction, JS_TRUE);
        SMW_JS_CATCH(cx)
    }

    static void Destory(JSContext *cx, JSObject *obj)
    {
        try {
            delete JSObjectConvert<ClassT*>(cx, obj);
        } catch(...) {}
    }
};
template<class ClassT> int CJSObject<ClassT>::ms_PropertySpecCount;
template<class ClassT> JSPropertySpec CJSObject<ClassT>::ms_PropertySpecs[MAX_PROPERTY_SPECS];
template<class ClassT> std::vector<CDefineFuncBase*> CJSObject<ClassT>::ms_Members;
template<class ClassT> std::vector<function<jsval (JSContext*, void*)>> CJSObject<ClassT>::ms_GetPropertyFunctions;
template<class ClassT> std::vector<function<void (JSContext*, void*, jsval)>> CJSObject<ClassT>::ms_SetPropertyFunctions;
template<class ClassT> JSClass CJSObject<ClassT>::ms_JSClass;
template<class ClassT> JSObject* CJSObject<ClassT>::ms_JSObject;

template<typename FuncT, class From = typename mpl::next<mpl::begin<ft::parameter_types<FuncT>>::type>::type,
    class To = typename mpl::end<ft::parameter_types<FuncT>>::type>
struct JSInvoker;

template<typename FuncT, class From, class To>
struct JSInvoker
{
    // add an argument to a Fusion cons-list for each parameter type
    static inline jsval apply(JSContext* cx, const char* pszFuncName, void* pParams, jsval* argv1, jsval* argv2)
    {
        typedef typename mpl::deref<From>::type arg_type;
        typedef typename mpl::next<From>::type next_iter_type;
        arg_type* pParam = (arg_type*)(pParams);
        *argv1 = to_jsval(cx, *pParam);
        return JSInvoker<FuncT, next_iter_type, To>::apply(cx, pszFuncName, pParam + 1, argv1 + 1, argv2);
    }
};

template<typename FuncT, class To>
struct JSInvoker<FuncT, To, To>
{
    // the argument list is complete, now call the function
    static inline jsval apply(JSContext* cx, const char* pszFuncName, void* pParams, jsval* argv1, jsval* argv2)
    {
        jsval jsRet;
        SMW_CHECK_CALL(cx, JS_CallFunctionName(cx, JS_GetGlobalObject(cx), pszFuncName, ft::function_arity<FuncT>::value - 1, argv2, &jsRet),
            (string("JSInvoker() call function ") + pszFuncName + " failed!").c_str());
        return jsRet;
    }
};

template<typename FuncT, int _NO = 0>
struct CJavascriptFunc
{
    static const char* s_pszFuncName;

    static FuncT inline GetStub(const char* pszFuncName)
    {
        s_pszFuncName = pszFuncName;
        return reinterpret_cast<FuncT>(Stub<ft::result_type<FuncT>::type>);
    }

    template<typename RetT>
    static RetT inline Stub(JSContext* cx, int First)
    {
        jsval Params[ft::function_arity<FuncT>::value];
        jsval jsret = JSInvoker<FuncT>::apply(cx, s_pszFuncName, &First, Params, Params);
        return jsval_to<RetT>(cx, jsret);
    }

    template<>
    static void inline Stub<void>(JSContext* cx, int First)
    {
        jsval Params[ft::function_arity<FuncT>::value];
        JSInvoker<FuncT>::apply(cx, s_pszFuncName, &First, Params, Params);
    }
};
template<typename FuncT, int _NO> const char* CJavascriptFunc<FuncT, _NO>::s_pszFuncName;

template<class JSClassT>
JSClassT inline JSObjectConvert(JSContext *cx, JSObject* obj)
{
    JSClassT retObj = (obj ? (JSClassT)JS_GetPrivate(cx, obj) : NULL);
    if (!retObj) throw std::exception("JSObjectConvert() retObj is NULL!");
    return retObj;
}

template<class JSClassT>
JSClassT* CreateJSObject(JSContext *cx, int iParamsCount = 0, ...)
{
    va_list Params;
    JSObject* retObj;
    va_start(Params, iParamsCount);
    SMW_SAFE_CALL(cx, retObj = (iParamsCount == 0) ? JS_ConstructObject(cx, &JSClassT::ms_JSClass, NULL, NULL)
        : JS_ConstructObjectWithArguments(cx, &JSClassT::ms_JSClass, NULL, NULL, iParamsCount, (jsval*)Params));
    return JSObjectConvert<JSClassT*>(cx, retObj);
}

使用时只需要CJSObject定义类的方法和属性就可以导入到JSContext中,测试代码如下:
class MyT : public SMW::CJSObject<MyT>
{
public:
    // Required ctor:
    MyT( JSContext * cx, JSObject *obj, uintN argc,
        jsval * argv, jsval * rval ) : CJSObject<MyT>(cx, obj) { id = 5; name = "abcd"; }
    // member functions we want to bind:
    int funcA() {
        return 123; }
    double funcB(int a, int b) {
        return 3.14; }
    int funcC(MyT* a, int b) {
        return a->id + b;
    }
    // these strings are explained below:
    int id;
    std::string name;
    static void Register(JSContext* cx)
    {
        DefineMember<0>("funcA", &funcA);
        DefineMember<0>("funcB", &funcB);
        DefineMember<0>("funcC", &funcC);
        DefineProperty("id", This()->id);
        DefineProperty("name", This()->name);
        SMW::CJSObject<MyT>::Register(cx, "MyT");
    }
};
调用类MyT中的Register注册到指定JSContext中后,就可以使用JS直接建立相应对象实例使用了。
以上代码在VC9中编译通过并运行正确。

原创粉丝点击