| giolekva | 313ee2b | 2021-12-15 15:17:29 +0400 | [diff] [blame] | 1 | // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // Package jni implements various helper functions for communicating with the Android JVM |
| 6 | // though JNI. |
| 7 | package jni |
| 8 | |
| 9 | import ( |
| 10 | "errors" |
| 11 | "fmt" |
| 12 | "reflect" |
| 13 | "runtime" |
| 14 | "sync" |
| 15 | "unicode/utf16" |
| 16 | "unsafe" |
| 17 | ) |
| 18 | |
| 19 | /* |
| 20 | #cgo CFLAGS: -Wall |
| 21 | |
| 22 | #include <jni.h> |
| 23 | #include <stdlib.h> |
| 24 | |
| 25 | static jint jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) { |
| 26 | return (*vm)->AttachCurrentThread(vm, p_env, thr_args); |
| 27 | } |
| 28 | |
| 29 | static jint jni_DetachCurrentThread(JavaVM *vm) { |
| 30 | return (*vm)->DetachCurrentThread(vm); |
| 31 | } |
| 32 | |
| 33 | static jint jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) { |
| 34 | return (*vm)->GetEnv(vm, (void **)env, version); |
| 35 | } |
| 36 | |
| 37 | static jclass jni_FindClass(JNIEnv *env, const char *name) { |
| 38 | return (*env)->FindClass(env, name); |
| 39 | } |
| 40 | |
| 41 | static jthrowable jni_ExceptionOccurred(JNIEnv *env) { |
| 42 | return (*env)->ExceptionOccurred(env); |
| 43 | } |
| 44 | |
| 45 | static void jni_ExceptionClear(JNIEnv *env) { |
| 46 | (*env)->ExceptionClear(env); |
| 47 | } |
| 48 | |
| 49 | static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) { |
| 50 | return (*env)->GetObjectClass(env, obj); |
| 51 | } |
| 52 | |
| 53 | static jmethodID jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) { |
| 54 | return (*env)->GetMethodID(env, clazz, name, sig); |
| 55 | } |
| 56 | |
| 57 | static jmethodID jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) { |
| 58 | return (*env)->GetStaticMethodID(env, clazz, name, sig); |
| 59 | } |
| 60 | |
| 61 | static jsize jni_GetStringLength(JNIEnv *env, jstring str) { |
| 62 | return (*env)->GetStringLength(env, str); |
| 63 | } |
| 64 | |
| 65 | static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) { |
| 66 | return (*env)->GetStringChars(env, str, NULL); |
| 67 | } |
| 68 | |
| 69 | static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) { |
| 70 | return (*env)->NewString(env, unicodeChars, len); |
| 71 | } |
| 72 | |
| 73 | static jboolean jni_IsSameObject(JNIEnv *env, jobject ref1, jobject ref2) { |
| 74 | return (*env)->IsSameObject(env, ref1, ref2); |
| 75 | } |
| 76 | |
| 77 | static jobject jni_NewGlobalRef(JNIEnv *env, jobject obj) { |
| 78 | return (*env)->NewGlobalRef(env, obj); |
| 79 | } |
| 80 | |
| 81 | static void jni_DeleteGlobalRef(JNIEnv *env, jobject obj) { |
| 82 | (*env)->DeleteGlobalRef(env, obj); |
| 83 | } |
| 84 | |
| 85 | static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) { |
| 86 | (*env)->CallStaticVoidMethodA(env, cls, method, args); |
| 87 | } |
| 88 | |
| 89 | static jint jni_CallStaticIntMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) { |
| 90 | return (*env)->CallStaticIntMethodA(env, cls, method, args); |
| 91 | } |
| 92 | |
| 93 | static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) { |
| 94 | return (*env)->CallStaticObjectMethodA(env, cls, method, args); |
| 95 | } |
| 96 | |
| 97 | static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { |
| 98 | return (*env)->CallObjectMethodA(env, obj, method, args); |
| 99 | } |
| 100 | |
| 101 | static jboolean jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { |
| 102 | return (*env)->CallBooleanMethodA(env, obj, method, args); |
| 103 | } |
| 104 | |
| 105 | static jint jni_CallIntMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { |
| 106 | return (*env)->CallIntMethodA(env, obj, method, args); |
| 107 | } |
| 108 | |
| 109 | static void jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { |
| 110 | (*env)->CallVoidMethodA(env, obj, method, args); |
| 111 | } |
| 112 | |
| 113 | static jbyteArray jni_NewByteArray(JNIEnv *env, jsize length) { |
| 114 | return (*env)->NewByteArray(env, length); |
| 115 | } |
| 116 | |
| 117 | static jboolean *jni_GetBooleanArrayElements(JNIEnv *env, jbooleanArray arr) { |
| 118 | return (*env)->GetBooleanArrayElements(env, arr, NULL); |
| 119 | } |
| 120 | |
| 121 | static void jni_ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray arr, jboolean *elems, jint mode) { |
| 122 | (*env)->ReleaseBooleanArrayElements(env, arr, elems, mode); |
| 123 | } |
| 124 | |
| 125 | static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) { |
| 126 | return (*env)->GetByteArrayElements(env, arr, NULL); |
| 127 | } |
| 128 | |
| 129 | static jint *jni_GetIntArrayElements(JNIEnv *env, jintArray arr) { |
| 130 | return (*env)->GetIntArrayElements(env, arr, NULL); |
| 131 | } |
| 132 | |
| 133 | static void jni_ReleaseIntArrayElements(JNIEnv *env, jintArray arr, jint *elems, jint mode) { |
| 134 | (*env)->ReleaseIntArrayElements(env, arr, elems, mode); |
| 135 | } |
| 136 | |
| 137 | static jlong *jni_GetLongArrayElements(JNIEnv *env, jlongArray arr) { |
| 138 | return (*env)->GetLongArrayElements(env, arr, NULL); |
| 139 | } |
| 140 | |
| 141 | static void jni_ReleaseLongArrayElements(JNIEnv *env, jlongArray arr, jlong *elems, jint mode) { |
| 142 | (*env)->ReleaseLongArrayElements(env, arr, elems, mode); |
| 143 | } |
| 144 | |
| 145 | static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *elems, jint mode) { |
| 146 | (*env)->ReleaseByteArrayElements(env, arr, elems, mode); |
| 147 | } |
| 148 | |
| 149 | static jsize jni_GetArrayLength(JNIEnv *env, jarray arr) { |
| 150 | return (*env)->GetArrayLength(env, arr); |
| 151 | } |
| 152 | |
| 153 | static void jni_DeleteLocalRef(JNIEnv *env, jobject localRef) { |
| 154 | return (*env)->DeleteLocalRef(env, localRef); |
| 155 | } |
| 156 | |
| 157 | static jobject jni_GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index) { |
| 158 | return (*env)->GetObjectArrayElement(env, array, index); |
| 159 | } |
| 160 | |
| 161 | static jboolean jni_IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz) { |
| 162 | return (*env)->IsInstanceOf(env, obj, clazz); |
| 163 | } |
| 164 | */ |
| 165 | import "C" |
| 166 | |
| 167 | type JVM C.JavaVM |
| 168 | |
| 169 | type Env C.JNIEnv |
| 170 | |
| 171 | type ( |
| 172 | Class C.jclass |
| 173 | Object C.jobject |
| 174 | MethodID C.jmethodID |
| 175 | String C.jstring |
| 176 | ByteArray C.jbyteArray |
| 177 | ObjectArray C.jobjectArray |
| 178 | BooleanArray C.jbooleanArray |
| 179 | LongArray C.jlongArray |
| 180 | IntArray C.jintArray |
| 181 | Boolean C.jboolean |
| 182 | Value uint64 // All JNI types fit into 64-bits. |
| 183 | ) |
| 184 | |
| 185 | // Cached class handles. |
| 186 | var classes struct { |
| 187 | once sync.Once |
| 188 | stringClass, integerClass Class |
| 189 | |
| 190 | integerIntValue MethodID |
| 191 | } |
| 192 | |
| 193 | func env(e *Env) *C.JNIEnv { |
| 194 | return (*C.JNIEnv)(unsafe.Pointer(e)) |
| 195 | } |
| 196 | |
| 197 | func javavm(vm *JVM) *C.JavaVM { |
| 198 | return (*C.JavaVM)(unsafe.Pointer(vm)) |
| 199 | } |
| 200 | |
| 201 | // Do invokes a function with a temporary JVM environment. The |
| 202 | // environment is not valid after the function returns. |
| 203 | func Do(vm *JVM, f func(env *Env) error) error { |
| 204 | runtime.LockOSThread() |
| 205 | defer runtime.UnlockOSThread() |
| 206 | var env *C.JNIEnv |
| 207 | if res := C.jni_GetEnv(javavm(vm), &env, C.JNI_VERSION_1_6); res != C.JNI_OK { |
| 208 | if res != C.JNI_EDETACHED { |
| 209 | panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) |
| 210 | } |
| 211 | if C.jni_AttachCurrentThread(javavm(vm), &env, nil) != C.JNI_OK { |
| 212 | panic(errors.New("runInJVM: AttachCurrentThread failed")) |
| 213 | } |
| 214 | defer C.jni_DetachCurrentThread(javavm(vm)) |
| 215 | } |
| 216 | |
| 217 | return f((*Env)(unsafe.Pointer(env))) |
| 218 | } |
| 219 | |
| 220 | func Bool(b bool) Boolean { |
| 221 | if b { |
| 222 | return C.JNI_TRUE |
| 223 | } |
| 224 | return C.JNI_FALSE |
| 225 | } |
| 226 | |
| 227 | func varArgs(args []Value) *C.jvalue { |
| 228 | if len(args) == 0 { |
| 229 | return nil |
| 230 | } |
| 231 | return (*C.jvalue)(unsafe.Pointer(&args[0])) |
| 232 | } |
| 233 | |
| 234 | func IsSameObject(e *Env, ref1, ref2 Object) bool { |
| 235 | same := C.jni_IsSameObject(env(e), C.jobject(ref1), C.jobject(ref2)) |
| 236 | return same == C.JNI_TRUE |
| 237 | } |
| 238 | |
| 239 | func CallStaticIntMethod(e *Env, cls Class, method MethodID, args ...Value) (int, error) { |
| 240 | res := C.jni_CallStaticIntMethodA(env(e), C.jclass(cls), C.jmethodID(method), varArgs(args)) |
| 241 | return int(res), exception(e) |
| 242 | } |
| 243 | |
| 244 | func CallStaticVoidMethod(e *Env, cls Class, method MethodID, args ...Value) error { |
| 245 | C.jni_CallStaticVoidMethodA(env(e), C.jclass(cls), C.jmethodID(method), varArgs(args)) |
| 246 | return exception(e) |
| 247 | } |
| 248 | |
| 249 | func CallVoidMethod(e *Env, obj Object, method MethodID, args ...Value) error { |
| 250 | C.jni_CallVoidMethodA(env(e), C.jobject(obj), C.jmethodID(method), varArgs(args)) |
| 251 | return exception(e) |
| 252 | } |
| 253 | |
| 254 | func CallStaticObjectMethod(e *Env, cls Class, method MethodID, args ...Value) (Object, error) { |
| 255 | res := C.jni_CallStaticObjectMethodA(env(e), C.jclass(cls), C.jmethodID(method), varArgs(args)) |
| 256 | return Object(res), exception(e) |
| 257 | } |
| 258 | |
| 259 | func CallObjectMethod(e *Env, obj Object, method MethodID, args ...Value) (Object, error) { |
| 260 | res := C.jni_CallObjectMethodA(env(e), C.jobject(obj), C.jmethodID(method), varArgs(args)) |
| 261 | return Object(res), exception(e) |
| 262 | } |
| 263 | |
| 264 | func CallBooleanMethod(e *Env, obj Object, method MethodID, args ...Value) (bool, error) { |
| 265 | res := C.jni_CallBooleanMethodA(env(e), C.jobject(obj), C.jmethodID(method), varArgs(args)) |
| 266 | return res == C.JNI_TRUE, exception(e) |
| 267 | } |
| 268 | |
| 269 | func CallIntMethod(e *Env, obj Object, method MethodID, args ...Value) (int32, error) { |
| 270 | res := C.jni_CallIntMethodA(env(e), C.jobject(obj), C.jmethodID(method), varArgs(args)) |
| 271 | return int32(res), exception(e) |
| 272 | } |
| 273 | |
| 274 | // GetByteArrayElements returns the contents of the byte array. |
| 275 | func GetByteArrayElements(e *Env, jarr ByteArray) []byte { |
| 276 | if jarr == 0 { |
| 277 | return nil |
| 278 | } |
| 279 | size := C.jni_GetArrayLength(env(e), C.jarray(jarr)) |
| 280 | elems := C.jni_GetByteArrayElements(env(e), C.jbyteArray(jarr)) |
| 281 | defer C.jni_ReleaseByteArrayElements(env(e), C.jbyteArray(jarr), elems, 0) |
| 282 | backing := (*(*[1 << 30]byte)(unsafe.Pointer(elems)))[:size:size] |
| 283 | s := make([]byte, len(backing)) |
| 284 | copy(s, backing) |
| 285 | return s |
| 286 | } |
| 287 | |
| 288 | // GetBooleanArrayElements returns the contents of the boolean array. |
| 289 | func GetBooleanArrayElements(e *Env, jarr BooleanArray) []bool { |
| 290 | if jarr == 0 { |
| 291 | return nil |
| 292 | } |
| 293 | size := C.jni_GetArrayLength(env(e), C.jarray(jarr)) |
| 294 | elems := C.jni_GetBooleanArrayElements(env(e), C.jbooleanArray(jarr)) |
| 295 | defer C.jni_ReleaseBooleanArrayElements(env(e), C.jbooleanArray(jarr), elems, 0) |
| 296 | backing := (*(*[1 << 30]C.jboolean)(unsafe.Pointer(elems)))[:size:size] |
| 297 | r := make([]bool, len(backing)) |
| 298 | for i, b := range backing { |
| 299 | r[i] = b == C.JNI_TRUE |
| 300 | } |
| 301 | return r |
| 302 | } |
| 303 | |
| 304 | // GetStringArrayElements returns the contents of the String array. |
| 305 | func GetStringArrayElements(e *Env, jarr ObjectArray) []string { |
| 306 | var strings []string |
| 307 | iterateObjectArray(e, jarr, func(e *Env, idx int, item Object) { |
| 308 | s := GoString(e, String(item)) |
| 309 | strings = append(strings, s) |
| 310 | }) |
| 311 | return strings |
| 312 | } |
| 313 | |
| 314 | // GetIntArrayElements returns the contents of the int array. |
| 315 | func GetIntArrayElements(e *Env, jarr IntArray) []int { |
| 316 | if jarr == 0 { |
| 317 | return nil |
| 318 | } |
| 319 | size := C.jni_GetArrayLength(env(e), C.jarray(jarr)) |
| 320 | elems := C.jni_GetIntArrayElements(env(e), C.jintArray(jarr)) |
| 321 | defer C.jni_ReleaseIntArrayElements(env(e), C.jintArray(jarr), elems, 0) |
| 322 | backing := (*(*[1 << 27]C.jint)(unsafe.Pointer(elems)))[:size:size] |
| 323 | r := make([]int, len(backing)) |
| 324 | for i, l := range backing { |
| 325 | r[i] = int(l) |
| 326 | } |
| 327 | return r |
| 328 | } |
| 329 | |
| 330 | // GetLongArrayElements returns the contents of the long array. |
| 331 | func GetLongArrayElements(e *Env, jarr LongArray) []int64 { |
| 332 | if jarr == 0 { |
| 333 | return nil |
| 334 | } |
| 335 | size := C.jni_GetArrayLength(env(e), C.jarray(jarr)) |
| 336 | elems := C.jni_GetLongArrayElements(env(e), C.jlongArray(jarr)) |
| 337 | defer C.jni_ReleaseLongArrayElements(env(e), C.jlongArray(jarr), elems, 0) |
| 338 | backing := (*(*[1 << 27]C.jlong)(unsafe.Pointer(elems)))[:size:size] |
| 339 | r := make([]int64, len(backing)) |
| 340 | for i, l := range backing { |
| 341 | r[i] = int64(l) |
| 342 | } |
| 343 | return r |
| 344 | } |
| 345 | |
| 346 | func iterateObjectArray(e *Env, jarr ObjectArray, f func(e *Env, idx int, item Object)) { |
| 347 | if jarr == 0 { |
| 348 | return |
| 349 | } |
| 350 | size := C.jni_GetArrayLength(env(e), C.jarray(jarr)) |
| 351 | for i := 0; i < int(size); i++ { |
| 352 | item := C.jni_GetObjectArrayElement(env(e), C.jobjectArray(jarr), C.jint(i)) |
| 353 | f(e, i, Object(item)) |
| 354 | C.jni_DeleteLocalRef(env(e), item) |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | // NewByteArray allocates a Java byte array with the content. It |
| 359 | // panics if the allocation fails. |
| 360 | func NewByteArray(e *Env, content []byte) ByteArray { |
| 361 | jarr := C.jni_NewByteArray(env(e), C.jsize(len(content))) |
| 362 | if jarr == 0 { |
| 363 | panic(fmt.Errorf("jni: NewByteArray(%d) failed", len(content))) |
| 364 | } |
| 365 | elems := C.jni_GetByteArrayElements(env(e), jarr) |
| 366 | defer C.jni_ReleaseByteArrayElements(env(e), jarr, elems, 0) |
| 367 | backing := (*(*[1 << 30]byte)(unsafe.Pointer(elems)))[:len(content):len(content)] |
| 368 | copy(backing, content) |
| 369 | return ByteArray(jarr) |
| 370 | } |
| 371 | |
| 372 | // ClassLoader returns a reference to the Java ClassLoader associated |
| 373 | // with obj. |
| 374 | func ClassLoaderFor(e *Env, obj Object) Object { |
| 375 | cls := GetObjectClass(e, obj) |
| 376 | getClassLoader := GetMethodID(e, cls, "getClassLoader", "()Ljava/lang/ClassLoader;") |
| 377 | clsLoader, err := CallObjectMethod(e, Object(obj), getClassLoader) |
| 378 | if err != nil { |
| 379 | // Class.getClassLoader should never fail. |
| 380 | panic(err) |
| 381 | } |
| 382 | return Object(clsLoader) |
| 383 | } |
| 384 | |
| 385 | // LoadClass invokes the underlying ClassLoader's loadClass method and |
| 386 | // returns the class. |
| 387 | func LoadClass(e *Env, loader Object, class string) (Class, error) { |
| 388 | cls := GetObjectClass(e, loader) |
| 389 | loadClass := GetMethodID(e, cls, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;") |
| 390 | name := JavaString(e, class) |
| 391 | loaded, err := CallObjectMethod(e, loader, loadClass, Value(name)) |
| 392 | if err != nil { |
| 393 | return 0, err |
| 394 | } |
| 395 | return Class(loaded), exception(e) |
| 396 | } |
| 397 | |
| 398 | // exception returns an error corresponding to the pending |
| 399 | // exception, and clears it. exceptionError returns nil if no |
| 400 | // exception is pending. |
| 401 | func exception(e *Env) error { |
| 402 | thr := C.jni_ExceptionOccurred(env(e)) |
| 403 | if thr == 0 { |
| 404 | return nil |
| 405 | } |
| 406 | C.jni_ExceptionClear(env(e)) |
| 407 | cls := GetObjectClass(e, Object(thr)) |
| 408 | toString := GetMethodID(e, cls, "toString", "()Ljava/lang/String;") |
| 409 | msg, err := CallObjectMethod(e, Object(thr), toString) |
| 410 | if err != nil { |
| 411 | return err |
| 412 | } |
| 413 | return errors.New(GoString(e, String(msg))) |
| 414 | } |
| 415 | |
| 416 | // GetObjectClass returns the Java Class for an Object. |
| 417 | func GetObjectClass(e *Env, obj Object) Class { |
| 418 | if obj == 0 { |
| 419 | panic("null object") |
| 420 | } |
| 421 | cls := C.jni_GetObjectClass(env(e), C.jobject(obj)) |
| 422 | if err := exception(e); err != nil { |
| 423 | // GetObjectClass should never fail. |
| 424 | panic(err) |
| 425 | } |
| 426 | return Class(cls) |
| 427 | } |
| 428 | |
| 429 | // GetStaticMethodID returns the id for a static method. It panics if the method |
| 430 | // wasn't found. |
| 431 | func GetStaticMethodID(e *Env, cls Class, name, signature string) MethodID { |
| 432 | mname := C.CString(name) |
| 433 | defer C.free(unsafe.Pointer(mname)) |
| 434 | msig := C.CString(signature) |
| 435 | defer C.free(unsafe.Pointer(msig)) |
| 436 | m := C.jni_GetStaticMethodID(env(e), C.jclass(cls), mname, msig) |
| 437 | if err := exception(e); err != nil { |
| 438 | panic(err) |
| 439 | } |
| 440 | return MethodID(m) |
| 441 | } |
| 442 | |
| 443 | // GetMethodID returns the id for a method. It panics if the method |
| 444 | // wasn't found. |
| 445 | func GetMethodID(e *Env, cls Class, name, signature string) MethodID { |
| 446 | mname := C.CString(name) |
| 447 | defer C.free(unsafe.Pointer(mname)) |
| 448 | msig := C.CString(signature) |
| 449 | defer C.free(unsafe.Pointer(msig)) |
| 450 | m := C.jni_GetMethodID(env(e), C.jclass(cls), mname, msig) |
| 451 | if err := exception(e); err != nil { |
| 452 | panic(err) |
| 453 | } |
| 454 | return MethodID(m) |
| 455 | } |
| 456 | |
| 457 | func NewGlobalRef(e *Env, obj Object) Object { |
| 458 | return Object(C.jni_NewGlobalRef(env(e), C.jobject(obj))) |
| 459 | } |
| 460 | |
| 461 | func DeleteGlobalRef(e *Env, obj Object) { |
| 462 | C.jni_DeleteGlobalRef(env(e), C.jobject(obj)) |
| 463 | } |
| 464 | |
| 465 | // JavaString converts the string to a JVM jstring. |
| 466 | func JavaString(e *Env, str string) String { |
| 467 | if str == "" { |
| 468 | return 0 |
| 469 | } |
| 470 | utf16Chars := utf16.Encode([]rune(str)) |
| 471 | res := C.jni_NewString(env(e), (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars))) |
| 472 | return String(res) |
| 473 | } |
| 474 | |
| 475 | // GoString converts the JVM jstring to a Go string. |
| 476 | func GoString(e *Env, str String) string { |
| 477 | if str == 0 { |
| 478 | return "" |
| 479 | } |
| 480 | strlen := C.jni_GetStringLength(env(e), C.jstring(str)) |
| 481 | chars := C.jni_GetStringChars(env(e), C.jstring(str)) |
| 482 | var utf16Chars []uint16 |
| 483 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars)) |
| 484 | hdr.Data = uintptr(unsafe.Pointer(chars)) |
| 485 | hdr.Cap = int(strlen) |
| 486 | hdr.Len = int(strlen) |
| 487 | utf8 := utf16.Decode(utf16Chars) |
| 488 | return string(utf8) |
| 489 | } |