зеркало из https://github.com/mozilla/pjs.git
Bug 513778 - Support passing JS functions as callbacks to C APIs. Part 3: Add closures for callback
support. r=benjamn
This commit is contained in:
Родитель
47a4f5f1ca
Коммит
509bb50f59
|
@ -83,7 +83,7 @@ static JSClass sCTypeProtoClass = {
|
|||
"CType",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(CTYPEPROTO_SLOTS),
|
||||
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
||||
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
|
||||
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, CType::FinalizeProtoClass,
|
||||
NULL, NULL, ConstructAbstract, ConstructAbstract, NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
|
@ -114,6 +114,14 @@ static JSClass sCDataClass = {
|
|||
NULL, NULL, FunctionType::Call, FunctionType::Call, NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
static JSClass sCClosureClass = {
|
||||
"CClosure",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(CCLOSURE_SLOTS) | JSCLASS_MARK_IS_TRACE,
|
||||
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
||||
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, CClosure::Finalize,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, JS_CLASS_TRACE(CClosure::Trace), NULL
|
||||
};
|
||||
|
||||
#define CTYPESFN_FLAGS \
|
||||
(JSFUN_FAST_NATIVE | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)
|
||||
|
||||
|
@ -560,8 +568,9 @@ static JSBool
|
|||
AttachProtos(JSContext* cx, JSObject* proto, JSObject** protos)
|
||||
{
|
||||
// For a given 'proto' of [[Class]] "CTypeProto", attach each of the 'protos'
|
||||
// to the appropriate CTypeProtoSlot.
|
||||
for (PRUint32 i = 0; i < CTYPEPROTO_SLOTS; ++i) {
|
||||
// to the appropriate CTypeProtoSlot. (SLOT_UINT64PROTO is the last slot
|
||||
// of [[Class]] "CTypeProto".)
|
||||
for (PRUint32 i = 0; i <= SLOT_UINT64PROTO; ++i) {
|
||||
if (!JS_SetReservedSlot(cx, proto, i, OBJECT_TO_JSVAL(protos[i])))
|
||||
return false;
|
||||
}
|
||||
|
@ -2386,6 +2395,21 @@ CType::Finalize(JSContext* cx, JSObject* obj)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
CType::FinalizeProtoClass(JSContext* cx, JSObject* obj)
|
||||
{
|
||||
// Finalize the CTypeProto class. The only important bit here is our
|
||||
// SLOT_CLOSURECX -- it contains the JSContext that was (lazily) instantiated
|
||||
// for use with FunctionType closures. And if we're here, in this finalizer,
|
||||
// we're guaranteed to not need it anymore. Note that this slot will only
|
||||
// be set for the object (of class CTypeProto) ctypes.FunctionType.prototype.
|
||||
jsval slot;
|
||||
if (!JS_GetReservedSlot(cx, obj, SLOT_CLOSURECX, &slot) || JSVAL_IS_VOID(slot))
|
||||
return;
|
||||
|
||||
JS_DestroyContextNoGC(static_cast<JSContext*>(JSVAL_TO_PRIVATE(slot)));
|
||||
}
|
||||
|
||||
void
|
||||
CType::Trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
|
@ -4214,51 +4238,60 @@ FunctionType::ConstructData(JSContext* cx,
|
|||
return JS_FALSE;
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
JS_ReportError(cx, "constructor takes zero or one argument");
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
JSObject* result = CData::Create(cx, obj, NULL, NULL, true);
|
||||
if (!result)
|
||||
return JS_FALSE;
|
||||
|
||||
*rval = OBJECT_TO_JSVAL(result);
|
||||
|
||||
if (argc == 1) {
|
||||
// Construct from a raw pointer value.
|
||||
if (!ExplicitConvert(cx, argv[0], obj, CData::GetData(cx, result)))
|
||||
return JS_FALSE;
|
||||
if (argc == 0) {
|
||||
// Construct a null pointer.
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
return JS_TRUE;
|
||||
}
|
||||
if (argc == 1 || argc == 2) {
|
||||
jsval arg = argv[0];
|
||||
PRFuncPtr* data = static_cast<PRFuncPtr*>(CData::GetData(cx, result));
|
||||
|
||||
JSObject*
|
||||
FunctionType::ConstructWithLibrary(JSContext* cx,
|
||||
JSObject* typeObj,
|
||||
JSObject* libraryObj,
|
||||
PRFuncPtr fnptr)
|
||||
{
|
||||
JS_ASSERT(CType::IsCType(cx, typeObj));
|
||||
JS_ASSERT(CType::GetTypeCode(cx, typeObj) == TYPE_function);
|
||||
if (JSVAL_IS_OBJECT(arg) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(arg))) {
|
||||
// Construct from a JS function, and allow an optional 'this' argument.
|
||||
JSObject* thisObj = NULL;
|
||||
if (argc == 2) {
|
||||
if (JSVAL_IS_OBJECT(argv[1])) {
|
||||
thisObj = JSVAL_TO_OBJECT(argv[1]);
|
||||
} else if (!JS_ValueToObject(cx, argv[1], &thisObj)) {
|
||||
return JS_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a CData object with the Library as a referent, for GC safety.
|
||||
JSObject* result = CData::Create(cx, typeObj, libraryObj, &fnptr, true);
|
||||
if (!result)
|
||||
return NULL;
|
||||
JSAutoTempValueRooter root(cx, result);
|
||||
JSObject* fnObj = JSVAL_TO_OBJECT(arg);
|
||||
JSObject* closureObj = CClosure::Create(cx, obj, fnObj, thisObj, data);
|
||||
if (!closureObj)
|
||||
return JS_FALSE;
|
||||
JSAutoTempValueRooter root(cx, closureObj);
|
||||
|
||||
// Seal the CData object, to prevent modification of the function pointer.
|
||||
// This permanently associates this object with the library, and avoids
|
||||
// having to do things like remove the Library from SLOT_REFERENT when
|
||||
// someone tries to change the pointer value.
|
||||
// XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
|
||||
// could be called on a sealed object.
|
||||
if (!JS_SealObject(cx, result, JS_FALSE))
|
||||
return NULL;
|
||||
// Set the closure object as the referent of the new CData object.
|
||||
if (!JS_SetReservedSlot(cx, result, SLOT_REFERENT,
|
||||
OBJECT_TO_JSVAL(closureObj)))
|
||||
return JS_FALSE;
|
||||
|
||||
return result;
|
||||
// Seal the CData object, to prevent modification of the function pointer.
|
||||
// This permanently associates this object with the closure, and avoids
|
||||
// having to do things like reset SLOT_REFERENT when someone tries to
|
||||
// change the pointer value.
|
||||
// XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
|
||||
// could be called on a sealed object.
|
||||
return JS_SealObject(cx, result, JS_FALSE);
|
||||
}
|
||||
|
||||
if (argc == 1) {
|
||||
// Construct from a raw pointer value.
|
||||
return ExplicitConvert(cx, arg, obj, data);
|
||||
}
|
||||
}
|
||||
|
||||
JS_ReportError(cx, "constructor takes 0, 1, or 2 arguments");
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
JSBool
|
||||
|
@ -4421,6 +4454,202 @@ FunctionType::ABIGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp)
|
|||
return JS_TRUE;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** CClosure implementation
|
||||
*******************************************************************************/
|
||||
|
||||
JSObject*
|
||||
CClosure::Create(JSContext* cx,
|
||||
JSObject* typeObj,
|
||||
JSObject* fnObj,
|
||||
JSObject* thisObj,
|
||||
PRFuncPtr* fnptr)
|
||||
{
|
||||
JSObject* result = JS_NewObject(cx, &sCClosureClass, NULL, NULL);
|
||||
if (!result)
|
||||
return NULL;
|
||||
JSAutoTempValueRooter root(cx, result);
|
||||
|
||||
// Get the FunctionInfo from the FunctionType.
|
||||
FunctionInfo* fninfo = FunctionType::GetFunctionInfo(cx, typeObj);
|
||||
|
||||
nsAutoPtr<ClosureInfo> cinfo(new ClosureInfo());
|
||||
if (!cinfo) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the prototype of the FunctionType object, of class CTypeProto,
|
||||
// which stores our JSContext for use with the closure.
|
||||
JSObject* proto = JS_GetPrototype(cx, typeObj);
|
||||
JS_ASSERT(proto);
|
||||
JS_ASSERT(JS_GET_CLASS(cx, proto) == &sCTypeProtoClass);
|
||||
|
||||
// Get a JSContext for use with the closure.
|
||||
jsval slot;
|
||||
ASSERT_OK(JS_GetReservedSlot(cx, proto, SLOT_CLOSURECX, &slot));
|
||||
if (!JSVAL_IS_VOID(slot)) {
|
||||
// Use the existing JSContext.
|
||||
cinfo->cx = static_cast<JSContext*>(JSVAL_TO_PRIVATE(slot));
|
||||
JS_ASSERT(cinfo->cx);
|
||||
} else {
|
||||
// Lazily instantiate a new JSContext, and stash it on
|
||||
// ctypes.FunctionType.prototype.
|
||||
JSRuntime* runtime = JS_GetRuntime(cx);
|
||||
cinfo->cx = JS_NewContext(runtime, 8192);
|
||||
if (!cinfo->cx) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!JS_SetReservedSlot(cx, proto, SLOT_CLOSURECX,
|
||||
PRIVATE_TO_JSVAL(cinfo->cx))) {
|
||||
JS_DestroyContextNoGC(cinfo->cx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
cinfo->closureObj = result;
|
||||
cinfo->typeObj = typeObj;
|
||||
cinfo->thisObj = thisObj;
|
||||
cinfo->jsfnObj = fnObj;
|
||||
#ifdef DEBUG
|
||||
cinfo->thread = PR_GetCurrentThread();
|
||||
#endif
|
||||
|
||||
// Create an ffi_closure object and initialize it.
|
||||
void* code;
|
||||
cinfo->closure =
|
||||
static_cast<ffi_closure*>(ffi_closure_alloc(sizeof(ffi_closure), &code));
|
||||
if (!cinfo->closure || !code) {
|
||||
JS_ReportError(cx, "couldn't create closure - libffi error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ffi_status status = ffi_prep_closure_loc(cinfo->closure, &fninfo->mCIF,
|
||||
CClosure::ClosureStub, cinfo, code);
|
||||
if (status != FFI_OK) {
|
||||
ffi_closure_free(cinfo->closure);
|
||||
JS_ReportError(cx, "couldn't create closure - libffi error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Stash the ClosureInfo struct on our new object.
|
||||
if (!JS_SetReservedSlot(cx, result, SLOT_CLOSUREINFO,
|
||||
PRIVATE_TO_JSVAL(cinfo.get()))) {
|
||||
ffi_closure_free(cinfo->closure);
|
||||
return NULL;
|
||||
}
|
||||
cinfo.forget();
|
||||
|
||||
*fnptr = (PRFuncPtr) code;
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
CClosure::Trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
JSContext* cx = trc->context;
|
||||
|
||||
// Make sure our ClosureInfo slot is legit. If it's not, bail.
|
||||
jsval slot;
|
||||
if (!JS_GetReservedSlot(cx, obj, SLOT_CLOSUREINFO, &slot) ||
|
||||
JSVAL_IS_VOID(slot))
|
||||
return;
|
||||
|
||||
ClosureInfo* cinfo = static_cast<ClosureInfo*>(JSVAL_TO_PRIVATE(slot));
|
||||
|
||||
// Identify our objects to the tracer. (There's no need to identify
|
||||
// 'closureObj', since that's us.)
|
||||
JS_CALL_TRACER(trc, cinfo->typeObj, JSTRACE_OBJECT, "typeObj");
|
||||
JS_CALL_TRACER(trc, cinfo->thisObj, JSTRACE_OBJECT, "thisObj");
|
||||
JS_CALL_TRACER(trc, cinfo->jsfnObj, JSTRACE_OBJECT, "jsfnObj");
|
||||
}
|
||||
|
||||
void
|
||||
CClosure::Finalize(JSContext* cx, JSObject* obj)
|
||||
{
|
||||
// Make sure our ClosureInfo slot is legit. If it's not, bail.
|
||||
jsval slot;
|
||||
if (!JS_GetReservedSlot(cx, obj, SLOT_CLOSUREINFO, &slot) ||
|
||||
JSVAL_IS_VOID(slot))
|
||||
return;
|
||||
|
||||
ClosureInfo* cinfo = static_cast<ClosureInfo*>(JSVAL_TO_PRIVATE(slot));
|
||||
if (cinfo->closure)
|
||||
ffi_closure_free(cinfo->closure);
|
||||
|
||||
delete cinfo;
|
||||
}
|
||||
|
||||
void
|
||||
CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData)
|
||||
{
|
||||
JS_ASSERT(cif);
|
||||
JS_ASSERT(result);
|
||||
JS_ASSERT(args);
|
||||
JS_ASSERT(userData);
|
||||
|
||||
// Initialize the result to zero, in case something fails.
|
||||
if (cif->rtype != &ffi_type_void)
|
||||
memset(result, 0, cif->rtype->size);
|
||||
|
||||
// Retrieve the essentials from our closure object.
|
||||
ClosureInfo* cinfo = static_cast<ClosureInfo*>(userData);
|
||||
JSContext* cx = cinfo->cx;
|
||||
JSObject* typeObj = cinfo->typeObj;
|
||||
JSObject* thisObj = cinfo->thisObj;
|
||||
JSObject* jsfnObj = cinfo->jsfnObj;
|
||||
|
||||
#ifdef DEBUG
|
||||
// Assert that we're on the thread we were created from.
|
||||
PRThread* thread = PR_GetCurrentThread();
|
||||
JS_ASSERT(thread == cinfo->thread);
|
||||
#endif
|
||||
|
||||
JSAutoRequest ar(cx);
|
||||
|
||||
// Assert that our CIFs agree.
|
||||
FunctionInfo* fninfo = FunctionType::GetFunctionInfo(cx, typeObj);
|
||||
JS_ASSERT(cif == &fninfo->mCIF);
|
||||
|
||||
// Get a death grip on 'closureObj'.
|
||||
JSAutoTempValueRooter root(cx, cinfo->closureObj);
|
||||
|
||||
// Set up an array for converted arguments.
|
||||
nsAutoTArray<jsval, 16> argv;
|
||||
if (!argv.SetLength(cif->nargs)) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
for (PRUint32 i = 0; i < cif->nargs; ++i)
|
||||
argv[i] = JSVAL_VOID;
|
||||
|
||||
JSAutoTempValueRooter roots(cx, argv.Length(), argv.Elements());
|
||||
for (PRUint32 i = 0; i < cif->nargs; ++i) {
|
||||
// Convert each argument, and have any CData objects created depend on
|
||||
// the existing buffers.
|
||||
if (!ConvertToJS(cx, fninfo->mArgTypes[i], NULL, args[i], false, false,
|
||||
&argv[i]))
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the JS function. 'thisObj' may be NULL, in which case the JS engine
|
||||
// will find an appropriate object to use.
|
||||
jsval rval;
|
||||
if (!JS_CallFunctionValue(cx, thisObj, OBJECT_TO_JSVAL(jsfnObj), cif->nargs,
|
||||
argv.Elements(), &rval))
|
||||
return;
|
||||
|
||||
// Convert the result. Note that we pass 'isArgument = false', such that
|
||||
// ImplicitConvert will *not* autoconvert a JS string into a pointer-to-char
|
||||
// type, which would require an allocation that we can't track. The JS
|
||||
// function must perform this conversion itself and return a PointerType
|
||||
// CData; thusly, the burden of freeing the data is left to the user.
|
||||
ImplicitConvert(cx, rval, fninfo->mReturnType, result, false, NULL);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** CData implementation
|
||||
*******************************************************************************/
|
||||
|
|
|
@ -110,6 +110,20 @@ struct FunctionInfo
|
|||
nsTArray<ffi_type*> mFFITypes;
|
||||
};
|
||||
|
||||
// Parameters necessary for invoking a JS function from a C closure.
|
||||
struct ClosureInfo
|
||||
{
|
||||
JSContext* cx; // JSContext to use
|
||||
JSObject* closureObj; // CClosure object
|
||||
JSObject* typeObj; // FunctionType describing the C function
|
||||
JSObject* thisObj; // 'this' object to use for the JS function call
|
||||
JSObject* jsfnObj; // JS function
|
||||
ffi_closure* closure; // The C closure itself
|
||||
#ifdef DEBUG
|
||||
PRThread* thread; // The thread the closure was created on
|
||||
#endif
|
||||
};
|
||||
|
||||
JSBool InitTypeClasses(JSContext* cx, JSObject* parent);
|
||||
|
||||
JSBool ConvertToJS(JSContext* cx, JSObject* typeObj, JSObject* dataObj, void* data, bool wantPrimitive, bool ownResult, jsval* result);
|
||||
|
@ -135,6 +149,7 @@ enum CTypeProtoSlot {
|
|||
SLOT_FUNCTIONDATAPROTO = 8, // common ancestor of all CData objects of FunctionType
|
||||
SLOT_INT64PROTO = 9, // ctypes.Int64.prototype object
|
||||
SLOT_UINT64PROTO = 10, // ctypes.UInt64.prototype object
|
||||
SLOT_CLOSURECX = 11, // JSContext for use with FunctionType closures
|
||||
CTYPEPROTO_SLOTS
|
||||
};
|
||||
|
||||
|
@ -166,6 +181,11 @@ enum CDataSlot {
|
|||
CDATA_SLOTS
|
||||
};
|
||||
|
||||
enum CClosureSlot {
|
||||
SLOT_CLOSUREINFO = 0, // ClosureInfo struct
|
||||
CCLOSURE_SLOTS
|
||||
};
|
||||
|
||||
enum TypeCtorSlot {
|
||||
SLOT_FN_CTORPROTO = 0 // ctypes.{Pointer,Array,Struct}Type.prototype
|
||||
// JSFunction objects always get exactly two slots.
|
||||
|
@ -187,6 +207,7 @@ public:
|
|||
static JSObject* DefineBuiltin(JSContext* cx, JSObject* parent, const char* propName, JSObject* typeProto, JSObject* dataProto, const char* name, TypeCode type, jsval size, jsval align, ffi_type* ffiType);
|
||||
static void Trace(JSTracer* trc, JSObject* obj);
|
||||
static void Finalize(JSContext* cx, JSObject* obj);
|
||||
static void FinalizeProtoClass(JSContext* cx, JSObject* obj);
|
||||
|
||||
static JSBool ConstructAbstract(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval);
|
||||
static JSBool ConstructData(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval);
|
||||
|
@ -289,7 +310,7 @@ public:
|
|||
static JSObject* CreateInternal(JSContext* cx, jsval abi, jsval rtype, jsval* argtypes, jsuint arglen);
|
||||
|
||||
static JSBool ConstructData(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval);
|
||||
static JSObject* ConstructWithLibrary(JSContext* cx, JSObject* typeObj, JSObject* libraryObj, PRFuncPtr fnptr);
|
||||
static JSObject* ConstructWithObject(JSContext* cx, JSObject* typeObj, JSObject* refObj, PRFuncPtr fnptr, JSObject* result);
|
||||
|
||||
static JSBool Call(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval);
|
||||
|
||||
|
@ -300,6 +321,15 @@ public:
|
|||
static JSBool ABIGetter(JSContext* cx, JSObject* obj, jsval idval, jsval* vp);
|
||||
};
|
||||
|
||||
class CClosure {
|
||||
public:
|
||||
static JSObject* Create(JSContext* cx, JSObject* typeObj, JSObject* fnObj, JSObject* thisObj, PRFuncPtr* fnptr);
|
||||
static void Trace(JSTracer* trc, JSObject* obj);
|
||||
static void Finalize(JSContext* cx, JSObject* obj);
|
||||
|
||||
static void ClosureStub(ffi_cif* cif, void* result, void** args, void* userData);
|
||||
};
|
||||
|
||||
class CData {
|
||||
public:
|
||||
static JSObject* Create(JSContext* cx, JSObject* typeObj, JSObject* refObj, void* data, bool ownResult);
|
||||
|
|
|
@ -254,12 +254,19 @@ Library::Declare(JSContext* cx, uintN argc, jsval* vp)
|
|||
return JS_FALSE;
|
||||
JSAutoTempValueRooter root(cx, typeObj);
|
||||
|
||||
JSObject* fn = FunctionType::ConstructWithLibrary(cx, typeObj, obj, func);
|
||||
JSObject* fn = CData::Create(cx, typeObj, obj, &func, true);
|
||||
if (!fn)
|
||||
return JS_FALSE;
|
||||
|
||||
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(fn));
|
||||
return JS_TRUE;
|
||||
|
||||
// Seal the CData object, to prevent modification of the function pointer.
|
||||
// This permanently associates this object with the library, and avoids
|
||||
// having to do things like reset SLOT_REFERENT when someone tries to
|
||||
// change the pointer value.
|
||||
// XXX This will need to change when bug 541212 is fixed -- CData::ValueSetter
|
||||
// could be called on a sealed object.
|
||||
return JS_SealObject(cx, fn, JS_FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -320,3 +320,17 @@ test_fnptr()
|
|||
return (void*)test_ansi_len;
|
||||
}
|
||||
|
||||
PRInt32
|
||||
test_closure_cdecl(PRInt8 i, PRInt32 (*f)(PRInt8))
|
||||
{
|
||||
return f(i);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && !defined(__WIN64)
|
||||
PRInt32
|
||||
test_closure_cdecl(PRInt8 i, PRInt32 (NS_STDCALL *f)(PRInt8))
|
||||
{
|
||||
return f(i);
|
||||
}
|
||||
#endif /* defined(_WIN32) && !defined(__WIN64) */
|
||||
|
||||
|
|
|
@ -187,5 +187,13 @@ NS_EXTERN_C
|
|||
NS_EXPORT SEVEN_BYTE test_7_byte_struct_return(RECT);
|
||||
|
||||
NS_EXPORT void * test_fnptr();
|
||||
|
||||
NS_EXPORT PRInt32 test_closure_cdecl(PRInt8, PRInt32 (*)(PRInt8));
|
||||
#if defined(_WIN32) && !defined(__WIN64)
|
||||
NS_EXPORT PRInt32 test_closure_stdcall(PRInt8, PRInt32 (NS_STDCALL *)(PRInt8));
|
||||
#endif /* defined(_WIN32) && !defined(__WIN64) */
|
||||
|
||||
NS_EXPORT PRInt32 test_callme(PRInt8);
|
||||
NS_EXPORT void* test_getfn();
|
||||
}
|
||||
|
||||
|
|
|
@ -188,6 +188,7 @@ function run_test()
|
|||
run_string_tests(library);
|
||||
run_struct_tests(library);
|
||||
run_function_tests(library);
|
||||
run_closure_tests(library);
|
||||
|
||||
// test the string version of ctypes.open() as well
|
||||
let libpath = libfile.path;
|
||||
|
@ -2065,6 +2066,45 @@ function run_function_tests(library)
|
|||
do_check_eq(ptrValue(test_ansi_len), ptrValue(ptr));
|
||||
}
|
||||
|
||||
function run_closure_tests(library)
|
||||
{
|
||||
run_single_closure_tests(library, ctypes.default_abi, "cdecl");
|
||||
#ifdef _WIN32
|
||||
#ifndef _WIN64
|
||||
run_single_closure_tests(library, ctypes.stdcall_abi, "stdcall");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
function run_single_closure_tests(library, abi, suffix)
|
||||
{
|
||||
let b = 23;
|
||||
|
||||
function closure_fn(i)
|
||||
{
|
||||
return "a" in this ? i + this.a : i + b;
|
||||
}
|
||||
|
||||
do_check_eq(closure_fn(7), 7 + b);
|
||||
let thisobj = { a: 5 };
|
||||
do_check_eq(closure_fn.call(thisobj, 7), 7 + thisobj.a);
|
||||
|
||||
// Construct a closure, and call it ourselves.
|
||||
let fn_t = ctypes.FunctionType(abi, ctypes.int32_t, [ ctypes.int8_t ]);
|
||||
let closure = fn_t(closure_fn);
|
||||
do_check_eq(closure(-17), -17 + b);
|
||||
|
||||
// Have C code call it.
|
||||
let test_closure = library.declare("test_closure_" + suffix,
|
||||
ctypes.default_abi, ctypes.int32_t, ctypes.int8_t, fn_t);
|
||||
do_check_eq(test_closure(-52, closure), -52 + b);
|
||||
|
||||
// Do the same, but specify 'this'.
|
||||
let closure2 = fn_t(closure_fn, thisobj);
|
||||
do_check_eq(closure2(-17), -17 + thisobj.a);
|
||||
do_check_eq(test_closure(-52, closure2), -52 + thisobj.a);
|
||||
}
|
||||
|
||||
// bug 522360 - try loading system library without full path
|
||||
function run_load_system_library()
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче