on develop help sign in
Use byte arrays across the c/java boundary
ebeb2e7Robert J. Macomber
reflex-dom/cbits/MainWidget.c+25−12
@@ -1,5 +1,6 @@
1#include <jni.h>
1#include <jni.h>
2#include <assert.h>
2#include <assert.h>
3+#include <stdlib.h>
3#include <android/log.h>
4#include <android/log.h>
4#include <HaskellActivity.h>
5#include <HaskellActivity.h>
5#include "MainWidget.h"
6#include "MainWidget.h"
@@ -29,7 +30,7 @@ jobject Reflex_Dom_Android_MainWidget_start(jobject activity, const char *url, c
29 return (*env)->NewGlobalRef(env, result);
30 return (*env)->NewGlobalRef(env, result);
30}
31}
31
32
32−void Reflex_Dom_Android_MainWidget_runJS(jobject jsExecutor, const char* js) {
33+void Reflex_Dom_Android_MainWidget_runJS(jobject jsExecutor, const char* js, size_t js_len) {
33 JNIEnv *env;
34 JNIEnv *env;
34 jint attachResult = (*HaskellActivity_jvm)->AttachCurrentThread(HaskellActivity_jvm, &env, NULL);
35 jint attachResult = (*HaskellActivity_jvm)->AttachCurrentThread(HaskellActivity_jvm, &env, NULL);
35 assert (attachResult == JNI_OK);
36 assert (attachResult == JNI_OK);
@@ -39,9 +40,10 @@ void Reflex_Dom_Android_MainWidget_runJS(jobject jsExecutor, const char* js) {
39 //TODO: Don't search for this method every time
40 //TODO: Don't search for this method every time
40 jclass cls = (*env)->GetObjectClass(env, jsExecutor);
41 jclass cls = (*env)->GetObjectClass(env, jsExecutor);
41 assert(cls);
42 assert(cls);
42− jmethodID evaluateJavascript = (*env)->GetMethodID(env, cls, "evaluateJavascript", "(Ljava/lang/String;)V");
43+ jmethodID evaluateJavascript = (*env)->GetMethodID(env, cls, "evaluateJavascript", "([B)V");
43 assert(evaluateJavascript);
44 assert(evaluateJavascript);
44− jstring js_str = (*env)->NewStringUTF(env, js);
45+ jbyteArray js_str = (*env)->NewByteArray(env, js_len);
46+ (*env)->SetByteArrayRegion(env, js_str, 0, js_len, (const jbyte*)js);
45 (*env)->CallVoidMethod(env, jsExecutor, evaluateJavascript, js_str, 0);
47 (*env)->CallVoidMethod(env, jsExecutor, evaluateJavascript, js_str, 0);
46 if((*env)->ExceptionOccurred(env)) {
48 if((*env)->ExceptionOccurred(env)) {
47 __android_log_write(ANDROID_LOG_DEBUG, "MainWidget", "runJS exception");
49 __android_log_write(ANDROID_LOG_DEBUG, "MainWidget", "runJS exception");
@@ -57,20 +59,31 @@ JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallb
57 return;
59 return;
58}
60}
59
61
60−JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processMessage (JNIEnv *env, jobject thisObj, jlong callbacksLong, jstring msg) {
62+JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processMessage (JNIEnv *env, jobject thisObj, jlong callbacksLong, jbyteArray msg) {
61 const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
63 const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
64+
62− const char *msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
65+ jbyte *msg_str = (*env)->GetByteArrayElements(env, msg, NULL);
66+ jsize msg_str_len = (*env)->GetArrayLength(env, msg);
63− (*(callbacks->jsaddleResult))(msg_str);
67+ (*(callbacks->jsaddleResult))(msg_str, msg_str_len);
64− (*env)->ReleaseStringUTFChars(env, msg, msg_str);
68+ (*env)->ReleaseByteArrayElements(env, msg, msg_str, JNI_ABORT);
65 return;
69 return;
66}
70}
67
71
68−JNIEXPORT jstring JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processSyncMessage (JNIEnv *env, jobject thisObj, jlong callbacksLong, jstring msg) {
72+JNIEXPORT jbyteArray JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processSyncMessage (JNIEnv *env, jobject thisObj, jlong callbacksLong, jbyteArray msg) {
69 const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
73 const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong;
74+
75+ char const *next_str;
76+ size_t next_str_len;
77+
70− const char *msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
78+ jbyte *msg_str = (*env)->GetByteArrayElements(env, msg, NULL);
79+ jsize msg_str_len = (*env)->GetArrayLength(env, msg);
71− char *next_str = (*(callbacks->jsaddleSyncResult))(msg_str);
80+ (*(callbacks->jsaddleSyncResult))(msg_str, msg_str_len, &next_str, &next_str_len);
72− jstring next_jstr = (*env)->NewStringUTF(env,next_str);
81+ (*env)->ReleaseByteArrayElements(env, msg, msg_str, JNI_ABORT);
82+
83+ jbyteArray next_jstr = (*env)->NewByteArray(env, next_str_len);
84+ (*env)->SetByteArrayRegion(env, next_jstr, 0, next_str_len, next_str);
85+
73 free(next_str);
86 free(next_str);
74− (*env)->ReleaseStringUTFChars(env, msg, msg_str);
87+
75 return next_jstr;
88 return next_jstr;
76}
89}
reflex-dom/cbits/include/MainWidget.h+4−2
@@ -1,10 +1,12 @@
1#ifndef MAINWIDGET_H_INCLUDED
1#ifndef MAINWIDGET_H_INCLUDED
2#define MAINWIDGET_H_INCLUDED
2#define MAINWIDGET_H_INCLUDED
3
3
4+#include <stddef.h>
5+
4typedef struct JSaddleCallbacks {
6typedef struct JSaddleCallbacks {
5 void (* jsaddleStart) ();
7 void (* jsaddleStart) ();
6− void (* jsaddleResult) (const char *);
8+ void (* jsaddleResult) (const char *, size_t);
7− char * (* jsaddleSyncResult) (const char *);
9+ void (* jsaddleSyncResult) (const char *, size_t, const char**, size_t*);
8 char * jsaddleJsData;
10 char * jsaddleJsData;
9} JSaddleCallbacks;
11} JSaddleCallbacks;
10
12
reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java+9−6
@@ -11,6 +11,7 @@ import android.webkit.WebSettings;
11import android.webkit.WebView;
11import android.webkit.WebView;
12import android.webkit.WebViewClient;
12import android.webkit.WebViewClient;
13
13
14+import java.nio.charset.StandardCharsets;
14
15
15public class MainWidget {
16public class MainWidget {
16 private static Object startMainWidget(Activity a, String url, long jsaddleCallbacks, final String initialJS) {
17 private static Object startMainWidget(Activity a, String url, long jsaddleCallbacks, final String initialJS) {
@@ -44,11 +45,12 @@ public class MainWidget {
44
45
45 final Handler hnd = new Handler();
46 final Handler hnd = new Handler();
46 return new Object() {
47 return new Object() {
47− public final void evaluateJavascript(final String js) {
48+ public final void evaluateJavascript(final byte[] js) {
49+ final String jsStr = new String(js, StandardCharsets.UTF_8);
48 hnd.post(new Runnable() {
50 hnd.post(new Runnable() {
49 @Override
51 @Override
50 public void run() {
52 public void run() {
51− wv.evaluateJavascript(js, null);
53+ wv.evaluateJavascript(jsStr, null);
52 }
54 }
53 });
55 });
54 }
56 }
@@ -58,8 +60,8 @@ public class MainWidget {
58 private static class JSaddleCallbacks {
60 private static class JSaddleCallbacks {
59 private final long callbacks;
61 private final long callbacks;
60 private native void startProcessing(long callbacks);
62 private native void startProcessing(long callbacks);
61− private native void processMessage(long callbacks, String msg);
63+ private native void processMessage(long callbacks, byte[] msg);
62− private native String processSyncMessage(long callbacks, String msg);
64+ private native byte[] processSyncMessage(long callbacks, byte[] msg);
63
65
64 public JSaddleCallbacks(long _callbacks) {
66 public JSaddleCallbacks(long _callbacks) {
65 callbacks = _callbacks;
67 callbacks = _callbacks;
@@ -73,13 +75,14 @@ public class MainWidget {
73
75
74 @JavascriptInterface
76 @JavascriptInterface
75 public boolean postMessage(final String msg) {
77 public boolean postMessage(final String msg) {
76− processMessage(callbacks, msg);
78+ processMessage(callbacks, msg.getBytes(StandardCharsets.UTF_8));
77 return true;
79 return true;
78 }
80 }
79
81
80 @JavascriptInterface
82 @JavascriptInterface
81 public String syncMessage(final String msg) {
83 public String syncMessage(final String msg) {
82− return processSyncMessage(callbacks, msg);
84+ return new String(processSyncMessage(callbacks, msg.getBytes(StandardCharsets.UTF_8)),
85+ StandardCharsets.UTF_8);
83 }
86 }
84 }
87 }
85}
88}
reflex-dom/src-android/Reflex/Dom/Android/MainWidget.hsc+18−12
@@ -15,6 +15,7 @@ import qualified Data.ByteString.Unsafe as BSU
15import Data.IORef
15import Data.IORef
16import Data.Monoid
16import Data.Monoid
17import Foreign.C.String
17import Foreign.C.String
18+import Foreign.C.Types
18import Foreign.Marshal.Utils
19import Foreign.Marshal.Utils
19import Foreign.Marshal.Array
20import Foreign.Marshal.Array
20import Foreign.Ptr
21import Foreign.Ptr
@@ -32,8 +33,8 @@ startMainWidget a url jsm = do
32 executorRef <- newIORef $ error "startMainWidget: executor not created yet"
33 executorRef <- newIORef $ error "startMainWidget: executor not created yet"
33 let go batch = do
34 let go batch = do
34 executor <- readIORef executorRef
35 executor <- readIORef executorRef
35− BS.useAsCString (LBS.toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $ \cstr -> do
36+ BSU.unsafeUseAsCStringLen (LBS.toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $ \(cstr, len) -> do
36− runJS executor cstr
37+ runJS executor cstr (fromIntegral len)
37 (processResult, processSyncResult, start) <- runJavaScript go jsm
38 (processResult, processSyncResult, start) <- runJavaScript go jsm
38 callbacks <- new <=< jsaddleCallbacksToPtrs $ JSaddleCallbacks
39 callbacks <- new <=< jsaddleCallbacksToPtrs $ JSaddleCallbacks
39 { _jsaddleCallbacks_jsaddleStart = void $ forkIO start
40 { _jsaddleCallbacks_jsaddleStart = void $ forkIO start
@@ -62,7 +63,7 @@ newtype JSExecutor = JSExecutor { unJSExecutor :: Ptr JSExecutor }
62
63
63foreign import ccall safe "Reflex_Dom_Android_MainWidget_start" startMainWidget_ :: HaskellActivity -> CString -> Ptr JSaddleCallbacksPtrs -> IO JSExecutor
64foreign import ccall safe "Reflex_Dom_Android_MainWidget_start" startMainWidget_ :: HaskellActivity -> CString -> Ptr JSaddleCallbacksPtrs -> IO JSExecutor
64
65
65−foreign import ccall safe "Reflex_Dom_Android_MainWidget_runJS" runJS :: JSExecutor -> CString -> IO ()
66+foreign import ccall safe "Reflex_Dom_Android_MainWidget_runJS" runJS :: JSExecutor -> CString -> CSize -> IO ()
66
67
67data JSaddleCallbacks = JSaddleCallbacks
68data JSaddleCallbacks = JSaddleCallbacks
68 { _jsaddleCallbacks_jsaddleStart :: IO ()
69 { _jsaddleCallbacks_jsaddleStart :: IO ()
@@ -73,28 +74,33 @@ data JSaddleCallbacks = JSaddleCallbacks
73
74
74data JSaddleCallbacksPtrs = JSaddleCallbacksPtrs
75data JSaddleCallbacksPtrs = JSaddleCallbacksPtrs
75 { _jsaddleCallbacksPtrs_jsaddleStart :: !(FunPtr (IO ()))
76 { _jsaddleCallbacksPtrs_jsaddleStart :: !(FunPtr (IO ()))
76− , _jsaddleCallbacksPtrs_jsaddleResult :: !(FunPtr (CString -> IO ()))
77+ , _jsaddleCallbacksPtrs_jsaddleResult :: !(FunPtr (CString -> CSize -> IO ()))
77− , _jsaddleCallbacksPtrs_jsaddleSyncResult :: !(FunPtr (CString -> IO CString))
78+ , _jsaddleCallbacksPtrs_jsaddleSyncResult :: !(FunPtr (CString -> CSize -> Ptr CString -> Ptr CSize -> IO ()))
78 , _jsaddleCallbacksPtrs_jsaddleJsData :: !CString
79 , _jsaddleCallbacksPtrs_jsaddleJsData :: !CString
79 }
80 }
80
81
81foreign import ccall "wrapper" wrapIO :: IO () -> IO (FunPtr (IO ()))
82foreign import ccall "wrapper" wrapIO :: IO () -> IO (FunPtr (IO ()))
82−foreign import ccall "wrapper" wrapCStringIO :: (CString -> IO ()) -> IO (FunPtr (CString -> IO ()))
83+foreign import ccall "wrapper" wrapCStringCSizeIO :: (CString -> CSize -> IO ()) -> IO (FunPtr (CString -> CSize -> IO ()))
83−foreign import ccall "wrapper" wrapCStringIOCString :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString))
84+foreign import ccall "wrapper" wrapCStringCSizeCStringCSizeIO :: (CString -> CSize -> Ptr CString -> Ptr CSize -> IO ()) -> IO (FunPtr (CString -> CSize -> Ptr CString -> Ptr CSize -> IO ()))
84
85
85−newCStringFromByteString :: ByteString -> IO CString
86+newCStringFromByteString :: ByteString -> IO (CString, CSize)
86newCStringFromByteString bs = BSU.unsafeUseAsCStringLen bs $ \(src, len) -> do
87newCStringFromByteString bs = BSU.unsafeUseAsCStringLen bs $ \(src, len) -> do
87 dest <- mallocArray0 len
88 dest <- mallocArray0 len
88 copyArray dest src len
89 copyArray dest src len
89 poke (advancePtr dest len) 0
90 poke (advancePtr dest len) 0
90− return dest
91+ return (dest, fromIntegral len)
91
92
92jsaddleCallbacksToPtrs :: JSaddleCallbacks -> IO JSaddleCallbacksPtrs
93jsaddleCallbacksToPtrs :: JSaddleCallbacks -> IO JSaddleCallbacksPtrs
93jsaddleCallbacksToPtrs jc = JSaddleCallbacksPtrs
94jsaddleCallbacksToPtrs jc = JSaddleCallbacksPtrs
94 <$> wrapIO (_jsaddleCallbacks_jsaddleStart jc)
95 <$> wrapIO (_jsaddleCallbacks_jsaddleStart jc)
95− <*> wrapCStringIO (\cstr -> _jsaddleCallbacks_jsaddleResult jc =<< BS.packCString cstr)
96+ <*> wrapCStringCSizeIO (\cstr len -> _jsaddleCallbacks_jsaddleResult jc =<< BS.packCStringLen (cstr, fromIntegral len))
97+ <*> wrapCStringCSizeCStringCSizeIO (\cstr len result resultLen -> do
98+ inStr <- BS.packCStringLen (cstr, fromIntegral len)
96− <*> wrapCStringIOCString (\cstr -> newCStringFromByteString =<< _jsaddleCallbacks_jsaddleSyncResult jc =<< BS.packCString cstr)
99+ outStr <- _jsaddleCallbacks_jsaddleSyncResult jc inStr
100+ (outStrBytes, outStrLen) <- newCStringFromByteString outStr
101+ poke result outStrBytes
102+ poke resultLen outStrLen)
97− <*> newCStringFromByteString (_jsaddleCallbacks_jsaddleJsData jc)
103+ <*> (fst <$> newCStringFromByteString (_jsaddleCallbacks_jsaddleJsData jc))
98
104
99instance Storable JSaddleCallbacksPtrs where
105instance Storable JSaddleCallbacksPtrs where
100 sizeOf _ = #{size JSaddleCallbacks}
106 sizeOf _ = #{size JSaddleCallbacks}