Skip to content

Commit d561f7b

Browse files
authored
Merge pull request #84 from HarlonWang/feature/2.4.0
支持 ArrayBuffer 和 byte 类型互转
2 parents c4912a8 + 0cb3b5b commit d561f7b

File tree

10 files changed

+122
-40
lines changed

10 files changed

+122
-40
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## 2.4.0 *(2024-11-14)*
4+
- 新增方法: 获取使用内存的大小信息(getMemoryUsedSize)
5+
- 支持 ArrayBuffer 转为 Byte 数组(深拷贝,对性能有一些影响)
6+
37
## 2.2.1 *(2024-09-29)*
48
- JSObject 增加 toMap 方法,支持转 HashMap 类型
59

README.md

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ QuickJS wrapper for Android/JVM.
77
- JavaScript exception handler
88
- Compile bytecode
99
- Supports converting JS object types to Java HashMap.
10+
- ESModule (import, export)
1011

1112
Experimental Features Stability not guaranteed.
12-
- ESModule (import, export)
13+
- Supports ArrayBuffer to a byte array type.
1314

1415
## Download
1516

@@ -85,20 +86,14 @@ QuickJSLoader.init();
8586

8687
```Java
8788
QuickJSContext context = QuickJSContext.create();
88-
```
8989

90-
### Destroy QuickJSContext
90+
// evaluating JavaScript
91+
context.evaluate("var a = 1 + 2;");
9192

92-
```Java
93+
// destroy QuickJSContext
9394
context.destroy();
9495
```
9596

96-
### Evaluating JavaScript
97-
98-
```Java
99-
context.evaluate("var a = 1 + 2;");
100-
```
101-
10297
### Console Support
10398
```Java
10499
context.setConsole(your console implementation.);
@@ -107,28 +102,28 @@ context.setConsole(your console implementation.);
107102
### Supported Types
108103

109104
#### Java and JavaScript can directly convert to each other for the following basic types
110-
- `boolean`
111-
- `int`
112-
- `long`
113-
- `double`
114-
- `String`
115-
- `null`
116-
117-
#### Mutual conversion of JS object types
118-
- `JSObject` represents a JavaScript object
119-
- `JSFunction` represents a JavaScript function
120-
- `JSArray` represents a JavaScript Array
121-
122-
#### About Long type
123-
There is no Long type in JavaScript, the conversion of Long type is special.
124-
125-
- Java --> JavaScript
126-
- The Long value <= Number.MAX_SAFE_INTEGER, will be convert to Number type.
127-
- The Long value > Number.MAX_SAFE_INTEGER, will be convert to BigInt type.
128-
- Number.MIN_SAFE_INTEGER is the same to above.
129-
130-
- JavaScript --> Java
131-
- Number(Int64) or BigInt --> Long type
105+
| JavaScript | Java |
106+
|-------------|-------------------|
107+
| null | null |
108+
| undefined | null |
109+
| boolean | Boolean |
110+
| Number | Long/Int/Double |
111+
| string | String |
112+
| Array | JSArray |
113+
| object | JSObject |
114+
| Function | JSFunction |
115+
| ArrayBuffer | byte[](Deep copy) |
116+
117+
Since JavaScript doesn't have a `long` type, additional information about `long`:
118+
119+
Java --> JavaScript
120+
- The Long value <= Number.MAX_SAFE_INTEGER, will be convert to Number type.
121+
- The Long value > Number.MAX_SAFE_INTEGER, will be convert to BigInt type.
122+
- Number.MIN_SAFE_INTEGER is the same to above.
123+
124+
JavaScript --> Java
125+
- Number(Int64) or BigInt --> Long type
126+
132127

133128
### Set Property
134129
Java

native/cpp/quickjs_context_jni.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,13 @@ Java_com_whl_quickjs_wrapper_QuickJSContext_getOwnPropertyNames(JNIEnv *env, job
270270
jlong context, jlong obj_value) {
271271
auto wrapper = reinterpret_cast<QuickJSWrapper*>(context);
272272
return wrapper->getOwnPropertyNames(env, thiz, obj_value);
273+
}
274+
extern "C"
275+
JNIEXPORT jlong JNICALL
276+
Java_com_whl_quickjs_wrapper_QuickJSContext_getMemoryUsedSize(JNIEnv *env, jobject thiz,
277+
jlong runtime) {
278+
auto *rt = reinterpret_cast<JSRuntime*>(runtime);
279+
JSMemoryUsage usage;
280+
JS_ComputeMemoryUsage(rt, &usage);
281+
return (jlong)usage.memory_used_size;
273282
}

native/cpp/quickjs_wrapper.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ static string getJavaName(JNIEnv* env, jobject javaClass) {
2323
return str;
2424
}
2525

26+
// quickjs 没有提供 JS_IsArrayBuffer 方法,这里通过取巧的方式来实现,后续可以替换掉
27+
static bool JS_IsArrayBuffer(JSValue value) {
28+
// quickjs 里的 ArrayBuffer 对应的类型枚举值
29+
int8_t JS_CLASS_ARRAY_BUFFER = 19;
30+
return JS_GetClassID(value) == JS_CLASS_ARRAY_BUFFER;
31+
}
32+
2633
static void tryToTriggerOnError(JSContext *ctx, JSValueConst *error) {
2734
JSValue global = JS_GetGlobalObject(ctx);
2835
JSValue onerror = JS_GetPropertyStr(ctx, global, "onError");
@@ -231,6 +238,12 @@ jsModuleLoaderFunc(JSContext *ctx, const char *module_name, void *opaque) {
231238
int scriptLen = env->GetStringUTFLength((jstring) result);
232239
JSValue func_val = JS_Eval(ctx, script, scriptLen, module_name,
233240
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
241+
if (JS_IsException(func_val)) {
242+
JS_FreeValue(ctx, func_val);
243+
throwJSException(env, ctx);
244+
return (JSModuleDef *) JS_VALUE_GET_PTR(JS_EXCEPTION);
245+
}
246+
234247
m = JS_VALUE_GET_PTR(func_val);
235248
JS_FreeValue(ctx, func_val);
236249
}
@@ -333,6 +346,7 @@ QuickJSWrapper::QuickJSWrapper(JNIEnv *env, jobject thiz, JSRuntime *rt) {
333346
quickjsContextClass = (jclass)(jniEnv->NewGlobalRef(jniEnv->FindClass("com/whl/quickjs/wrapper/QuickJSContext")));
334347
moduleLoaderClass = (jclass)(jniEnv->NewGlobalRef(jniEnv->FindClass("com/whl/quickjs/wrapper/ModuleLoader")));
335348
creatorClass = (jclass)(jniEnv->NewGlobalRef(jniEnv->FindClass("com/whl/quickjs/wrapper/JSObjectCreator")));
349+
byteArrayClass = (jclass) jniEnv->NewGlobalRef(env->FindClass("[B"));
336350

337351
booleanValueOf = jniEnv->GetStaticMethodID(booleanClass, "valueOf", "(Z)Ljava/lang/Boolean;");
338352
integerValueOf = jniEnv->GetStaticMethodID(integerClass, "valueOf", "(I)Ljava/lang/Integer;");
@@ -376,6 +390,7 @@ QuickJSWrapper::~QuickJSWrapper() {
376390
jniEnv->DeleteGlobalRef(moduleLoaderClass);
377391
jniEnv->DeleteGlobalRef(quickjsContextClass);
378392
jniEnv->DeleteGlobalRef(creatorClass);
393+
jniEnv->DeleteGlobalRef(byteArrayClass);
379394
}
380395

381396
jobject QuickJSWrapper::toJavaObject(JNIEnv *env, jobject thiz, JSValueConst& this_obj, JSValueConst& value) const{
@@ -436,6 +451,16 @@ jobject QuickJSWrapper::toJavaObject(JNIEnv *env, jobject thiz, JSValueConst& th
436451
result = env->CallObjectMethod(creatorObj, newFunctionM, thiz, value_ptr, obj_ptr);
437452
} else if (JS_IsArray(context, value)) {
438453
result = env->CallObjectMethod(creatorObj, newArrayM, thiz, value_ptr);
454+
} else if (JS_IsArrayBuffer(value)) {
455+
size_t byteLength = 0;
456+
uint8_t *buffer = JS_GetArrayBuffer(context, &byteLength, value);
457+
jbyteArray byteArray = env->NewByteArray(byteLength);
458+
void *elementsPtr = env->GetPrimitiveArrayCritical(byteArray, nullptr);
459+
jbyte *elements = reinterpret_cast<jbyte *>(elementsPtr);
460+
memcpy(elements, buffer, byteLength);
461+
result = byteArray;
462+
JS_FreeValue(context, value);
463+
env->ReleasePrimitiveArrayCritical(byteArray, elements, 0);
439464
} else {
440465
result = env->CallObjectMethod(creatorObj, newObjectM, thiz, value_ptr);
441466
}
@@ -514,7 +539,8 @@ jobject QuickJSWrapper::call(JNIEnv *env, jobject thiz, jlong func, jlong this_o
514539
// 基础类型(例如 string )和 Java callback 类型需要使用完 free.
515540
if (env->IsInstanceOf(arg, stringClass) || env->IsInstanceOf(arg, doubleClass) ||
516541
env->IsInstanceOf(arg, integerClass) || env->IsInstanceOf(arg, longClass) ||
517-
env->IsInstanceOf(arg, booleanClass) || env->IsInstanceOf(arg, jsCallFunctionClass)) {
542+
env->IsInstanceOf(arg, booleanClass) || env->IsInstanceOf(arg, jsCallFunctionClass)
543+
|| env->IsInstanceOf(arg, byteArrayClass)) {
518544
freeArguments.push_back(jsArg);
519545
}
520546

@@ -676,6 +702,12 @@ JSValue QuickJSWrapper::toJSValue(JNIEnv *env, jobject thiz, jobject value) cons
676702
}
677703
} else if (env->IsInstanceOf(value, booleanClass)) {
678704
result = JS_NewBool(context, env->CallBooleanMethod(value, booleanGetValue));
705+
} else if (env->IsInstanceOf(value, byteArrayClass)) {
706+
jbyteArray bytes = static_cast<jbyteArray>(value);
707+
jbyte* byteData = env->GetByteArrayElements(bytes, nullptr);
708+
jsize length = env->GetArrayLength(bytes);
709+
result = JS_NewArrayBufferCopy(context, reinterpret_cast<uint8_t*>(byteData), length);
710+
env->ReleaseByteArrayElements(bytes, byteData, JNI_ABORT);
679711
} else if (env->IsInstanceOf(value, jsObjectClass)) {
680712
result = JS_MKPTR(JS_TAG_OBJECT, reinterpret_cast<void *>(env->CallLongMethod(value, jsObjectGetValue)));
681713
} else if (env->IsInstanceOf(value, jsCallFunctionClass)) {

native/cpp/quickjs_wrapper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class QuickJSWrapper {
4242
jclass quickjsContextClass;
4343
jclass moduleLoaderClass;
4444
jclass creatorClass;
45+
jclass byteArrayClass;
4546
JSValue ownPropertyNames;
4647

4748
jmethodID booleanValueOf;

wrapper-android/src/androidTest/java/com/whl/quickjs/wrapper/QuickJSTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,4 +1236,26 @@ public void testObjectLeakDetection() {
12361236
context.destroy();
12371237
}
12381238

1239+
@Test
1240+
public void testArrayBytes() {
1241+
QuickJSContext context = createContext();
1242+
byte[] bytes = "test测试".getBytes();
1243+
byte[] buffer = (byte[]) context.evaluate("new Int8Array([116, 101, 115, 116, -26, -75, -117, -24, -81, -107]).buffer");
1244+
assertArrayEquals(bytes, buffer);
1245+
1246+
context.getGlobalObject().setProperty("testBuffer", bytes);
1247+
byte[] testBuffers = context.getGlobalObject().getBytes("testBuffer");
1248+
assertArrayEquals(testBuffers, buffer);
1249+
1250+
context.destroy();
1251+
}
1252+
1253+
@Test
1254+
public void testArrayBytes1() {
1255+
QuickJSContext context = createContext();
1256+
JSFunction bufferTest = (JSFunction) context.evaluate("const bufferTest = (buffer) => { if(new Int8Array(buffer)[0] !== 116) { throw Error('failed, not equal'); }; }; bufferTest;");
1257+
bufferTest.callVoid("test测试".getBytes());
1258+
context.destroy();
1259+
}
1260+
12391261
}

wrapper-java/src/main/java/com/whl/quickjs/wrapper/JSObject.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public interface JSObject {
1414
void setProperty(String name, JSObject value);
1515
void setProperty(String name, boolean value);
1616
void setProperty(String name, double value);
17+
void setProperty(String name, byte[] value);
1718
void setProperty(String name, JSCallFunction value);
1819
void setProperty(String name, Class<?> clazz);
1920
long getPointer();
@@ -32,6 +33,7 @@ public interface JSObject {
3233
Double getDoubleProperty(String name);
3334
Double getDouble(String name);
3435
Long getLong(String name);
36+
byte[] getBytes(String name);
3537
@Deprecated
3638
JSObject getJSObjectProperty(String name);
3739
JSObject getJSObject(String name);

wrapper-java/src/main/java/com/whl/quickjs/wrapper/QuickJSContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ public void setMemoryLimit(int memoryLimitSize) {
125125
setMemoryLimit(runtime, memoryLimitSize);
126126
}
127127

128+
// Return the byte size.
129+
public long getMemoryUsedSize() {
130+
return getMemoryUsedSize(runtime);
131+
}
132+
128133
public void dumpMemoryUsage(File target) {
129134
if (target == null || !target.exists()) {
130135
return;
@@ -578,6 +583,7 @@ public Object getOwnPropertyNames(JSObject object) {
578583
private native void setMemoryLimit(long runtime, int size);
579584
private native void dumpMemoryUsage(long runtime, String fileName);
580585
private native void dumpObjects(long runtime, String fileName);
586+
private native long getMemoryUsedSize(long runtime);
581587

582588
// context
583589
private native long createContext(long runtime);

wrapper-java/src/main/java/com/whl/quickjs/wrapper/QuickJSFunction.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ public class QuickJSFunction extends QuickJSObject implements JSFunction {
1212
* 函数执行状态
1313
*/
1414
enum Status {
15-
NOT_STARTED,
16-
IN_PROGRESS,
17-
COMPLETED
15+
NOT_CALLED,
16+
CALLING,
17+
CALLED
1818
}
1919
private int stashTimes = 0;
20-
private Status currentStatus = Status.NOT_STARTED;
20+
private Status currentStatus = Status.NOT_CALLED;
2121

2222
private final long thisPointer;
2323

@@ -30,7 +30,7 @@ public QuickJSFunction(QuickJSContext context, long pointer, long thisPointer) {
3030
public void release() {
3131
// call 函数未执行完,触发了 release 操作,会导致 quickjs 野指针异常,
3232
// 这里暂存一下,待函数执行完,才执行 release。
33-
if (currentStatus == Status.IN_PROGRESS) {
33+
if (currentStatus == Status.CALLING) {
3434
stashTimes++;
3535
return;
3636
}
@@ -41,9 +41,9 @@ public void release() {
4141
public Object call(Object... args) {
4242
checkRefCountIsZero();
4343

44-
currentStatus = Status.IN_PROGRESS;
44+
currentStatus = Status.CALLING;
4545
Object ret = getContext().call(this, thisPointer, args);
46-
currentStatus = Status.COMPLETED;
46+
currentStatus = Status.CALLED;
4747

4848
if (stashTimes > 0) {
4949
// 如果有暂存,这里需要恢复下 release 操作

wrapper-java/src/main/java/com/whl/quickjs/wrapper/QuickJSObject.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ public void setProperty(String name, double value) {
8484
setPropertyObject(name, value);
8585
}
8686

87+
@Override
88+
public void setProperty(String name, byte[] value) {
89+
setPropertyObject(name, value);
90+
}
91+
8792
@Override
8893
public void setProperty(String name, JSCallFunction value) {
8994
setPropertyObject(name, value);
@@ -176,6 +181,12 @@ public Long getLong(String name) {
176181
return value instanceof Long ? (Long) value : null;
177182
}
178183

184+
@Override
185+
public byte[] getBytes(String name) {
186+
Object value = getProperty(name);
187+
return value instanceof byte[] ? (byte[]) value : null;
188+
}
189+
179190
@Override
180191
public JSObject getJSObjectProperty(String name) {
181192
return getJSObject(name);

0 commit comments

Comments
 (0)