Update CI references to 0.0.122
[ldk-java] / csharp_strings.py
index 5361302be675bc3268437769b18c0f48a584b9c5..a73ee2035d310be2568fa48f71b94ab9994ea3e4 100644 (file)
@@ -3,21 +3,38 @@ from enum import Enum
 import sys
 
 class Target(Enum):
-    CSHARP = 1,
+    WINDOWS = 1,
+    LINUX = 2,
+    PTHREAD = 3,
+
+def first_to_lower(string: str) -> str:
+    first = string[0]
+    return first.lower() + string[1:]
+
+def safe_arg_name(arg_name):
+    return "_" + arg_name if arg_name == "lock" or arg_name == "event" or arg_name == "params" else arg_name
+
+def arg_name_repl(s, arg_name):
+    return s.replace(arg_name, "_" + arg_name) if arg_name == "lock" or arg_name == "event" or arg_name == "params" else s
 
 class Consts:
-    def __init__(self, DEBUG: bool, target: Target, **kwargs):
+    def __init__(self, DEBUG: bool, target: Target, outdir: str, **kwargs):
+        self.outdir = outdir
         self.target = target
         self.c_array_class_caches = set()
+        self.function_ptr_counter = 0
+        self.function_ptrs = {}
         self.c_type_map = dict(
-            bool = ['bool'],
-            uint8_t = ['byte'],
-            uint16_t = ['short'],
-            uint32_t = ['int'],
-            uint64_t = ['long'],
+            bool = ['bool', 'long', 'bool[]'],
+            uint8_t = ['byte', 'long', 'byte[]'],
+            uint16_t = ['short', 'long', 'short[]'],
+            uint32_t = ['int', 'long', 'int[]'],
+            uint64_t = ['long', 'long', 'long[]'],
+            int64_t = ['long', 'long', 'long[]'],
+            double = ['double', 'long', 'double[]'],
         )
         self.java_type_map = dict(
-            String = "string"
+            String = "long"
         )
         self.java_hu_type_map = dict(
             String = "string"
@@ -31,25 +48,21 @@ class Consts:
         self.bindings_header = """
 using org.ldk.enums;
 using org.ldk.impl;
+using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 
 namespace org { namespace ldk { namespace impl {
 
 internal class bindings {
-       /*static {
-               init(java.lang.Enum.class, VecOrSliceDef.class);
-               init_class_cache();
-               if (!get_lib_version_string().equals(version.get_ldk_java_bindings_version()))
-                       throw new ArgumentException("Compiled LDK library and LDK class failes do not match");
-               // Fetching the LDK versions from C also checks that the header and binaries match
-               Console.Error.WriteLine("Loaded LDK-Java Bindings " + version.get_ldk_java_bindings_version() + " with LDK " + get_ldk_version() + " and LDK-C-Bindings " + get_ldk_c_bindings_version());
-       }*/
-       //static extern void init(java.lang.Class c);
-       //static native void init_class_cache();
+       static List<WeakReference> js_objs = new List<WeakReference>();
+
 """
         self.bindings_header += self.native_meth_decl("get_lib_version_string", "string") + "();\n"
         self.bindings_header += self.native_meth_decl("get_ldk_c_bindings_version", "string") + "();\n"
         self.bindings_header += self.native_meth_decl("get_ldk_version", "string") + "();\n\n"
+        self.bindings_header += self.native_meth_decl("allocate_buffer", "long") + "(long buflen);\n\n"
+        self.bindings_header += self.native_meth_decl("free_buffer", "void") + "(long buf);\n\n"
 
         self.bindings_version_file = """
 
@@ -79,6 +92,33 @@ public class CommonBase {
 }
 """
 
+        self.txin_defn = """public class TxIn : CommonBase {
+       /** The witness in this input, in serialized form */
+       public readonly byte[] witness;
+       /** The script_sig in this input */
+       public readonly byte[] script_sig;
+       /** The transaction output's sequence number */
+       public readonly int sequence;
+       /** The txid this input is spending */
+       public readonly byte[] previous_txid;
+       /** The output index within the spent transaction of the output this input is spending */
+       public readonly int previous_vout;
+
+       internal TxIn(object _dummy, long ptr) : base(ptr) {
+               this.witness = InternalUtils.decodeUint8Array(bindings.TxIn_get_witness(ptr));
+               this.script_sig = InternalUtils.decodeUint8Array(bindings.TxIn_get_script_sig(ptr));
+               this.sequence = bindings.TxIn_get_sequence(ptr);
+               this.previous_txid = InternalUtils.decodeUint8Array(bindings.TxIn_get_previous_txid(ptr));
+               this.previous_vout = bindings.TxIn_get_previous_vout(ptr);
+       }
+       public TxIn(byte[] witness, byte[] script_sig, int sequence, byte[] previous_txid, int previous_vout)
+       : this(null, bindings.TxIn_new(InternalUtils.encodeUint8Array(witness), InternalUtils.encodeUint8Array(script_sig), sequence, InternalUtils.encodeUint8Array(previous_txid), previous_vout)) {}
+
+       ~TxIn() {
+               if (ptr != 0) { bindings.TxIn_free(ptr); }
+       }
+}"""
+
         self.txout_defn = """public class TxOut : CommonBase {
        /** The script_pubkey in this output */
        public readonly byte[] script_pubkey;
@@ -86,13 +126,10 @@ public class CommonBase {
        public readonly long value;
 
     internal TxOut(object _dummy, long ptr) : base(ptr) {
-               this.script_pubkey = bindings.TxOut_get_script_pubkey(ptr);
-               this.value = bindings.TxOut_get_value(ptr);
-       }
-    public TxOut(long value, byte[] script_pubkey) : base(bindings.TxOut_new(script_pubkey, value)) {
-               this.script_pubkey = bindings.TxOut_get_script_pubkey(ptr);
+               this.script_pubkey = InternalUtils.decodeUint8Array(bindings.TxOut_get_script_pubkey(ptr));
                this.value = bindings.TxOut_get_value(ptr);
        }
+    public TxOut(long value, byte[] script_pubkey) : this(null, bindings.TxOut_new(InternalUtils.encodeUint8Array(script_pubkey), value)) {}
 
        ~TxOut() {
                if (ptr != 0) { bindings.TxOut_free(ptr); }
@@ -104,10 +141,10 @@ public class CommonBase {
        public readonly byte[] scalar_bytes;
 
     internal BigEndianScalar(object _dummy, long ptr) : base(ptr) {
-               this.scalar_bytes = bindings.BigEndianScalar_get_bytes(ptr);
+               this.scalar_bytes = InternalUtils.decodeUint8Array(bindings.BigEndianScalar_get_bytes(ptr));
        }
-    public BigEndianScalar(byte[] scalar_bytes) : base(bindings.BigEndianScalar_new(scalar_bytes)) {
-               this.scalar_bytes = bindings.BigEndianScalar_get_bytes(ptr);
+    public BigEndianScalar(byte[] scalar_bytes) : base(bindings.BigEndianScalar_new(InternalUtils.encodeUint8Array(scalar_bytes))) {
+               this.scalar_bytes = InternalUtils.decodeUint8Array(bindings.BigEndianScalar_get_bytes(ptr));
        }
 
        ~BigEndianScalar() {
@@ -115,6 +152,28 @@ public class CommonBase {
        }
 }"""
 
+        self.witness_program_defn = """public class WitnessProgram : CommonBase {
+       /** The witness program bytes themselves */
+       public readonly byte[] program;
+       /** The witness version */
+       public readonly WitnessVersion version;
+
+       internal WitnessProgram(object _dummy, long ptr) : base(ptr) {
+               this.program = InternalUtils.decodeUint8Array(bindings.WitnessProgram_get_program(ptr));
+               this.version = new WitnessVersion(bindings.WitnessProgram_get_version(ptr));
+       }
+       static private long check_args(byte[] program, WitnessVersion version) {
+               if (program.Length < 2 || program.Length > 40) throw new ArgumentException();
+               if (version.getVal() == 0 && program.Length != 20 && program.Length != 32) throw new ArgumentException();
+               return InternalUtils.encodeUint8Array(program);
+       }
+       public WitnessProgram(byte[] program, WitnessVersion version) :
+               this(null, bindings.WitnessProgram_new(version.getVal(), check_args(program, version))) {}
+
+       ~WitnessProgram() {
+               if (ptr != 0) { bindings.WitnessProgram_free(ptr); }
+       }
+}"""
 
         self.c_file_pfx = """
 // On OSX jlong (ie long long) is not equivalent to int64_t, so we override here
@@ -131,8 +190,27 @@ public class CommonBase {
 
         self.c_file_pfx = self.c_file_pfx + "#include <stdio.h>\n#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)\n"
 
-        if not DEBUG or sys.platform == "darwin":
-            self.c_file_pfx = self.c_file_pfx + """#define MALLOC(a, _) malloc(a)
+        if self.target == Target.WINDOWS:
+            self.c_file_pfx = self.c_file_pfx + """#include <heapapi.h>
+static HANDLE process_heap = NULL;
+static inline void* init_heap() {
+       if (UNLIKELY(process_heap == NULL)) {
+               // Assume pointer writes wont tear, which is true where we need it.
+               process_heap = GetProcessHeap();
+       }
+}
+static inline void* MALLOC(size_t a, const char* _) {
+       init_heap();
+       return HeapAlloc(process_heap, HEAP_ZERO_MEMORY, a);
+}
+#define do_MALLOC(a, b, _c) MALLOC(a, b)
+#define FREE(p) if ((uint64_t)(p) > 4096) { init_heap(); HeapFree(process_heap, 0, p); }
+#define CHECK_ACCESS(p)
+#define CHECK_INNER_FIELD_ACCESS_OR_NULL(v)
+"""
+        elif not DEBUG or self.target != Target.LINUX:
+            self.c_file_pfx = self.c_file_pfx + """#define do_MALLOC(a, _b, _c) malloc(a)
+#define MALLOC(a, _) malloc(a)
 #define FREE(p) if ((uint64_t)(p) > 4096) { free(p); }
 #define CHECK_ACCESS(p)
 #define CHECK_INNER_FIELD_ACCESS_OR_NULL(v)
@@ -156,7 +234,7 @@ void __attribute__((constructor)) debug_log_version() {
 }
 """
 
-            if sys.platform != "darwin":
+            if self.target == Target.LINUX:
                 self.c_file_pfx += """
 // Running a leak check across all the allocations and frees of the JDK is a mess,
 // so instead we implement our own naive leak checker here, relying on the -wrap
@@ -199,11 +277,13 @@ static void new_allocation(void* res, const char* struct_name, size_t len) {
        allocation_ll = new_alloc;
        DO_ASSERT(!pthread_mutex_unlock(&allocation_mtx));
 }
-static void* MALLOC(size_t len, const char* struct_name) {
+static void* do_MALLOC(size_t len, const char* struct_name, int lineno) {
        void* res = __real_malloc(len);
-       new_allocation(res, struct_name, len);
+       new_allocation(res, struct_name, lineno);
        return res;
 }
+#define MALLOC(len, struct_name) do_MALLOC(len, struct_name, __LINE__)
+
 void __real_free(void* ptr);
 static void alloc_freed(void* ptr) {
        allocation* p = NULL;
@@ -289,7 +369,7 @@ void __attribute__((destructor)) check_leaks() {
        unsigned long alloc_count = 0;
        unsigned long alloc_size = 0;
        DEBUG_PRINT("The following LDK-allocated blocks still remain.\\n");
-       DEBUG_PRINT("Note that this is only accurate if System.gc(); System.runFinalization()\\n");
+       DEBUG_PRINT("Note that this is only accurate if System.GC.Collect(); GC.WaitForPendingFinalizers();\\n");
        DEBUG_PRINT("was called prior to exit after all LDK objects were out of scope.\\n");
        for (allocation* a = allocation_ll; a != NULL; a = a->next) {
                DEBUG_PRINT("%s %p (%lu bytes) remains:\\n", a->struct_name, a->ptr, a->alloc_len);
@@ -299,7 +379,7 @@ void __attribute__((destructor)) check_leaks() {
                alloc_size += a->alloc_len;
        }
        DEBUG_PRINT("%lu allocations remained for %lu bytes.\\n", alloc_count, alloc_size);
-       DEBUG_PRINT("Note that this is only accurate if System.gc(); System.runFinalization()\\n");
+       DEBUG_PRINT("Note that this is only accurate if System.GC.Collect(); GC.WaitForPendingFinalizers()\\n");
        DEBUG_PRINT("was called prior to exit after all LDK objects were out of scope.\\n");
 }
 """
@@ -312,38 +392,71 @@ _Static_assert(offsetof(LDKCVec_u8Z, datalen) == offsetof(LDKu8slice, datalen),
 
 _Static_assert(sizeof(void*) <= 8, "Pointers must fit into 64 bits");
 
-typedef jlongArray int64_tArray;
-typedef jbyteArray int8_tArray;
-
-static inline jstring str_ref_to_java(JNIEnv *env, const char* chars, size_t len) {
-       // Sadly we need to create a temporary because Java can't accept a char* without a 0-terminator
-       char* conv_buf = MALLOC(len + 1, "str conv buf");
-       memcpy(conv_buf, chars, len);
-       conv_buf[len] = 0;
-       jstring ret = (*env)->NewStringUTF(env, conv_buf);
-       FREE(conv_buf);
-       return ret;
+// Int types across Windows/Linux are different, so make sure we're using the right headers.
+_Static_assert(sizeof(void*) == sizeof(uintptr_t), "stdints must be correct");
+_Static_assert(sizeof(void*) == sizeof(intptr_t), "stdints must be correct");
+_Static_assert(sizeof(uint64_t) == 8, "stdints must be correct");
+_Static_assert(sizeof(int64_t) == 8, "stdints must be correct");
+_Static_assert(sizeof(uint32_t) == 4, "stdints must be correct");
+_Static_assert(sizeof(int32_t) == 4, "stdints must be correct");
+_Static_assert(sizeof(uint16_t) == 2, "stdints must be correct");
+_Static_assert(sizeof(int16_t) == 2, "stdints must be correct");
+_Static_assert(sizeof(uint8_t) == 1, "stdints must be correct");
+_Static_assert(sizeof(int8_t) == 1, "stdints must be correct");
+
+#define DECL_ARR_TYPE(ty, name) \\
+       struct name##array { \\
+               uint64_t arr_len; /* uint32_t would suffice but we want to align uint64_ts as well */ \\
+               ty elems[]; \\
+       }; \\
+       typedef struct name##array * name##Array; \\
+       static inline name##Array init_##name##Array(size_t arr_len, int lineno) { \\
+               name##Array arr = (name##Array)do_MALLOC(arr_len * sizeof(ty) + sizeof(uint64_t), #name" array init", lineno); \\
+               arr->arr_len = arr_len; \\
+               return arr; \\
+       }
+
+DECL_ARR_TYPE(int64_t, int64_t);
+DECL_ARR_TYPE(uint64_t, uint64_t);
+DECL_ARR_TYPE(int8_t, int8_t);
+DECL_ARR_TYPE(int16_t, int16_t);
+DECL_ARR_TYPE(uint32_t, uint32_t);
+DECL_ARR_TYPE(void*, ptr);
+DECL_ARR_TYPE(char, char);
+typedef charArray jstring;
+
+static inline jstring str_ref_to_cs(const char* chars, size_t len) {
+       charArray arr = init_charArray(len, __LINE__);
+       memcpy(arr->elems, chars, len);
+       return arr;
 }
-static inline LDKStr java_to_owned_str(JNIEnv *env, jstring str) {
-       uint64_t str_len = (*env)->GetStringUTFLength(env, str);
-       char* newchars = MALLOC(str_len + 1, "String chars");
-       const char* jchars = (*env)->GetStringUTFChars(env, str, NULL);
-       memcpy(newchars, jchars, str_len);
-       newchars[str_len] = 0;
-       (*env)->ReleaseStringUTFChars(env, str, jchars);
+static inline LDKStr str_ref_to_owned_c(const jstring str) {
+       char* newchars = MALLOC(str->arr_len + 1, "String chars");
+       memcpy(newchars, str->elems, str->arr_len);
+       newchars[str->arr_len] = 0;
        LDKStr res = {
                .chars = newchars,
-               .len = str_len,
+               .len = str->arr_len,
                .chars_is_owned = true
        };
        return res;
 }
 
-const char* CS_LDK_get_ldk_c_bindings_version() {
-       return str_ref_to_java(check_get_ldk_bindings_version(), strlen(check_get_ldk_bindings_version()));
+typedef bool jboolean;
+
+int64_t CS_LDK_allocate_buffer(int64_t len) {
+       return (int64_t)MALLOC(len, "C#-requested buffer");
 }
-const char* CS_LDK_get_ldk_version() {
-       return str_ref_to_java(check_get_ldk_version(), strlen(check_get_ldk_version()));
+
+void CS_LDK_free_buffer(int64_t buf) {
+       FREE((void*)buf);
+}
+
+jstring CS_LDK_get_ldk_c_bindings_version() {
+       return str_ref_to_cs(check_get_ldk_bindings_version(), strlen(check_get_ldk_bindings_version()));
+}
+jstring CS_LDK_get_ldk_version() {
+       return str_ref_to_cs(check_get_ldk_version(), strlen(check_get_ldk_version()));
 }
 #include "version.c"
 """
@@ -369,14 +482,14 @@ namespace org { namespace ldk { namespace structs {
         self.usize_c_ty = "int64_t"
         self.usize_native_ty = "long"
         self.native_zero_ptr = "0"
-        self.result_c_ty = "jclass"
-        self.ptr_arr = "jobjectArray"
+        self.unitary_enum_c_ty = "int32_t"
+        self.ptr_arr = "ptrArray"
         self.is_arr_some_check = ("", " != NULL")
-        self.get_native_arr_len_call = ("(*env)->GetArrayLength(env, ", ")")
+        self.get_native_arr_len_call = ("", "->arr_len")
 
         self.bindings_footer_wip = "\tstatic bindings() {\n"
     def bindings_footer(self):
-        return self.bindings_footer_wip + "\t}\n}\n} } }\n"
+        return ""
 
     def native_meth_decl(self, meth_name, ret_ty_str):
         return "\t[DllImport (\"ldkcsharp\", EntryPoint=\"CS_LDK_" + meth_name + "\")] public static extern " + ret_ty_str + " " + meth_name
@@ -384,95 +497,94 @@ namespace org { namespace ldk { namespace structs {
     def c_fn_name_define_pfx(self, fn_name, have_args):
         return " CS_LDK_" + fn_name + "("
 
-    def construct_jenv(self):
-        res =  "JNIEnv *env;\n"
-        res += "jint get_jenv_res = (*j_calls->vm)->GetEnv(j_calls->vm, (void**)&env, JNI_VERSION_1_6);\n"
-        res += "if (get_jenv_res == JNI_EDETACHED) {\n"
-        res += "\tDO_ASSERT((*j_calls->vm)->AttachCurrentThread(j_calls->vm, (void**)&env, NULL) == JNI_OK);\n"
-        res += "} else {\n"
-        res += "\tDO_ASSERT(get_jenv_res == JNI_OK);\n"
-        res += "}\n"
-        return res
-    def deconstruct_jenv(self):
-        res = "if (get_jenv_res == JNI_EDETACHED) {\n"
-        res += "\tDO_ASSERT((*j_calls->vm)->DetachCurrentThread(j_calls->vm) == JNI_OK);\n"
-        res += "}\n"
-        return res
-
     def release_native_arr_ptr_call(self, ty_info, arr_var, arr_ptr_var):
-        if ty_info.subty is None or not ty_info.subty.c_ty.endswith("Array"):
-            return "(*env)->ReleasePrimitiveArrayCritical(env, " + arr_var + ", " + arr_ptr_var + ", 0)"
         return None
     def create_native_arr_call(self, arr_len, ty_info):
-        if ty_info.c_ty == "int8_tArray":
-            return "(*env)->NewByteArray(env, " + arr_len + ")"
-        elif ty_info.subty.c_ty.endswith("Array"):
-            clz_var = ty_info.java_fn_ty_arg[1:].replace("[", "arr_of_")
-            self.c_array_class_caches.add(clz_var)
-            return "(*env)->NewObjectArray(env, " + arr_len + ", " + clz_var + "_clz, NULL);\n"
-        else:
-            return "(*env)->New" + ty_info.java_ty.strip("[]").title() + "Array(env, " + arr_len + ")"
+        if ty_info.c_ty == "ptrArray":
+            assert ty_info.rust_obj == "LDKCVec_U5Z" or (ty_info.subty is not None and (ty_info.subty.c_ty.endswith("Array") or ty_info.subty.rust_obj == "LDKStr"))
+        return "init_" + ty_info.c_ty + "(" + arr_len + ", __LINE__)"
     def set_native_arr_contents(self, arr_name, arr_len, ty_info):
         if ty_info.c_ty == "int8_tArray":
-            return ("(*env)->SetByteArrayRegion(env, " + arr_name + ", 0, " + arr_len + ", ", ")")
+            return ("memcpy(" + arr_name + "->elems, ", ", " + arr_len + ")")
+        elif ty_info.c_ty == "int16_tArray":
+            return ("memcpy(" + arr_name + "->elems, ", ", " + arr_len + " * 2)")
         else:
             assert False
     def get_native_arr_contents(self, arr_name, dest_name, arr_len, ty_info, copy):
-        if ty_info.c_ty == "int8_tArray":
+        if ty_info.c_ty == "int8_tArray" or ty_info.c_ty == "int16_tArray":
             if copy:
-                return "(*env)->GetByteArrayRegion(env, " + arr_name + ", 0, " + arr_len + ", " + dest_name + ")"
-            else:
-                return "(*env)->GetByteArrayElements (env, " + arr_name + ", NULL)"
-        elif not ty_info.java_ty[:len(ty_info.java_ty) - 2].endswith("[]"):
-            return "(*env)->Get" + ty_info.subty.java_ty.title() + "ArrayElements (env, " + arr_name + ", NULL)"
+                byte_len = arr_len
+                if ty_info.c_ty == "int16_tArray":
+                    byte_len = arr_len + " * 2"
+                return "memcpy(" + dest_name + ", " + arr_name + "->elems, " + byte_len + "); FREE(" + arr_name + ")"
+        assert not copy
+        if ty_info.c_ty == "ptrArray":
+            return "(void*) " + arr_name + "->elems"
         else:
-            return None
+            return arr_name + "->elems"
     def get_native_arr_elem(self, arr_name, idxc, ty_info):
-        if self.get_native_arr_contents(arr_name, "", "", ty_info, False) is None:
-            return "(*env)->GetObjectArrayElement(env, " + arr_name + ", " + idxc + ")"
-        else:
-            assert False # Only called if above is None
+        assert False # Only called if above is None
     def get_native_arr_ptr_call(self, ty_info):
-        if ty_info.subty is not None and ty_info.subty.c_ty.endswith("Array"):
-            return None
-        return ("(*env)->GetPrimitiveArrayCritical(env, ", ", NULL)")
+        if ty_info.subty is not None:
+            return "(" + ty_info.subty.c_ty + "*)(((uint8_t*)", ") + 8)"
+        return "(" + ty_info.c_ty + "*)(((uint8_t*)", ") + 8)"
     def get_native_arr_entry_call(self, ty_info, arr_name, idxc, entry_access):
-        if ty_info.subty is None or not ty_info.subty.c_ty.endswith("Array"):
-            return None
-        return "(*env)->SetObjectArrayElement(env, " + arr_name + ", " + idxc + ", " + entry_access + ")"
+        return None
     def cleanup_native_arr_ref_contents(self, arr_name, dest_name, arr_len, ty_info):
         if ty_info.c_ty == "int8_tArray":
-            return "(*env)->ReleaseByteArrayElements(env, " + arr_name + ", (int8_t*)" + dest_name + ", 0);"
+            return "FREE(" + arr_name + ");"
         else:
-            return "(*env)->Release" + ty_info.java_ty.strip("[]").title() + "ArrayElements(env, " + arr_name + ", " + dest_name + ", 0)"
+            return "FREE(" + arr_name + ")"
 
-    def map_hu_array_elems(self, arr_name, conv_name, arr_ty, elem_ty):
+    def map_hu_array_elems(self, arr_name, conv_name, arr_ty, elem_ty, is_nullable):
         if elem_ty.java_hu_ty == "UInt5":
-            return arr_name + " != null ? InternalUtils.convUInt5Array(" + arr_name + ") : null"
+            return "InternalUtils.convUInt5Array(" + arr_name + ")"
         elif elem_ty.java_hu_ty == "WitnessVersion":
-            return arr_name + " != null ? InternalUtils.convWitnessVersionArray(" + arr_name + ") : null"
+            return "InternalUtils.convWitnessVersionArray(" + arr_name + ")"
         else:
-            return arr_name + " != null ? InternalUtils.mapArray(" + arr_name + ", " + conv_name + " => " + elem_ty.from_hu_conv[0] + ") : null"
+            return "InternalUtils.mapArray(" + arr_name + ", " + conv_name + " => " + elem_ty.from_hu_conv[0] + ")"
 
     def str_ref_to_native_call(self, var_name, str_len):
-        return "str_ref_to_java(env, " + var_name + ", " + str_len + ")"
+        return "str_ref_to_cs(" + var_name + ", " + str_len + ")"
     def str_ref_to_c_call(self, var_name):
-        return "java_to_owned_str(env, " + var_name + ")"
+        return "str_ref_to_owned_c(" + var_name + ")"
     def str_to_hu_conv(self, var_name):
-        return None
+        return "string " + var_name + "_conv = InternalUtils.decodeString(" + var_name + ");"
     def str_from_hu_conv(self, var_name):
-        return None
+        return ("InternalUtils.encodeString(" + var_name + ")", "")
 
     def init_str(self):
-        return ""
+        ret = ""
+        for fn_suffix in self.function_ptrs:
+            cret = self.function_ptrs[fn_suffix]["ret"][1]
+            cargs = self.function_ptrs[fn_suffix]["args"][1]
+            ret += f"""
+typedef {cret} (*invoker_{fn_suffix})(int obj_ptr, int fn_id{cargs});
+static invoker_{fn_suffix} js_invoke_function_{fn_suffix};
+int CS_LDK_register_{fn_suffix}_invoker(invoker_{fn_suffix} invoker) {{
+       js_invoke_function_{fn_suffix} = invoker;
+       return 0;
+}}
+"""
+
+        return ret
 
     def var_decl_statement(self, ty_string, var_name, statement):
         return ty_string + " " + var_name + " = " + statement
 
     def get_java_arr_len(self, arr_name):
-        return arr_name + ".Length"
+        return "InternalUtils.getArrayLength(" + arr_name + ")"
+
     def get_java_arr_elem(self, elem_ty, arr_name, idx):
-        return arr_name + "[" + idx + "]"
+        if elem_ty.c_ty == "int64_t" or elem_ty.c_ty == "uint64_t":
+            return "InternalUtils.getU64ArrayElem(" + arr_name + ", " + idx + ")"
+        elif elem_ty.c_ty.endswith("Array") or elem_ty.c_ty == "uintptr_t" or elem_ty.rust_obj == "LDKStr":
+            return "InternalUtils.getU64ArrayElem(" + arr_name + ", " + idx + ")"
+        elif elem_ty.rust_obj == "LDKU5":
+            return "InternalUtils.getU8ArrayElem(" + arr_name + ", " + idx + ")"
+        else:
+            assert False
+
     def constr_hu_array(self, ty_info, arr_len):
         base_ty = ty_info.subty.java_hu_ty.split("[")[0].split("<")[0]
         conv = "new " + base_ty + "[" + arr_len + "]"
@@ -481,22 +593,44 @@ namespace org { namespace ldk { namespace structs {
             conv += "[" + ty_info.subty.java_hu_ty.split("<")[0].split("[")[1]
         return conv
     def cleanup_converted_native_array(self, ty_info, arr_name):
-        return None
+        return "bindings.free_buffer(" + arr_name + ");"
 
     def primitive_arr_from_hu(self, arr_ty, fixed_len, arr_name):
         mapped_ty = arr_ty.subty
+        inner = arr_name
         if arr_ty.rust_obj == "LDKU128":
-            return ("" + arr_name + ".getLEBytes()", "")
+            return ("InternalUtils.encodeUint8Array(" + arr_name + ".getLEBytes())", "")
         if fixed_len is not None:
-            return ("InternalUtils.check_arr_len(" + arr_name + ", " + fixed_len + ")", "")
-        return None
+            inner = "InternalUtils.check_arr_len(" + arr_name + ", " + fixed_len + ")"
+        if mapped_ty.c_ty.endswith("Array"):
+            return ("InternalUtils.encodeUint64Array(" + inner + ")", "")
+        elif mapped_ty.c_ty == "uint8_t" or mapped_ty.c_ty == "int8_t":
+            return ("InternalUtils.encodeUint8Array(" + inner + ")", "")
+        elif mapped_ty.c_ty == "uint16_t" or mapped_ty.c_ty == "int16_t":
+            return ("InternalUtils.encodeUint16Array(" + inner + ")", "")
+        elif mapped_ty.c_ty == "uint32_t":
+            return ("InternalUtils.encodeUint32Array(" + inner + ")", "")
+        elif mapped_ty.c_ty == "int64_t" or mapped_ty.c_ty == "uint64_t" or mapped_ty.rust_obj == "LDKStr":
+            return ("InternalUtils.encodeUint64Array(" + inner + ")", "")
+        else:
+            print(mapped_ty.c_ty)
+            assert False
+
     def primitive_arr_to_hu(self, arr_ty, fixed_len, arr_name, conv_name):
+        mapped_ty = arr_ty.subty
         if arr_ty.rust_obj == "LDKU128":
             return "org.ldk.util.UInt128 " + conv_name + " = new org.ldk.util.UInt128(" + arr_name + ");"
-        return None
+        elif mapped_ty.c_ty == "uint8_t" or mapped_ty.c_ty == "int8_t":
+            return "byte[] " + conv_name + " = InternalUtils.decodeUint8Array(" + arr_name + ");"
+        elif mapped_ty.c_ty == "uint16_t" or mapped_ty.c_ty == "int16_t":
+            return "short[] " + conv_name + " = InternalUtils.decodeUint16Array(" + arr_name + ");"
+        elif mapped_ty.c_ty == "uint64_t" or mapped_ty.c_ty == "int64_t":
+            return "long[] " + conv_name + " = InternalUtils.decodeUint64Array(" + arr_name + ");"
+        else:
+            assert False
 
     def java_arr_ty_str(self, elem_ty_str):
-        return elem_ty_str + "[]"
+        return "long"
 
     def for_n_in_range(self, n, minimum, maximum):
         return "for (int " + n + " = " + minimum + "; " + n + " < " + maximum + "; " + n + "++) {"
@@ -584,89 +718,69 @@ namespace org { namespace ldk { namespace structs {
             ret = ret + ", " + param
         return ret + ")"
 
-    def native_c_map_trait(self, struct_name, field_vars, flattened_field_vars, field_fns, trait_doc_comment):
-        out_java_trait = ""
-        out_java = ""
-
-        # First generate most of the Java code, note that we need information about java method argument strings for C
-        out_java_trait += self.hu_struct_file_prefix
-        if trait_doc_comment is not None:
-            out_java_trait += "/**\n * " + trait_doc_comment.replace("\n", "\n * ") + "\n */\n"
-        out_java_trait = out_java_trait + "public class " + struct_name.replace("LDK","") + " : CommonBase {\n"
-        out_java_trait = out_java_trait + "\tinternal readonly bindings." + struct_name + " bindings_instance;\n"
-        out_java_trait = out_java_trait + "\tinternal " + struct_name.replace("LDK", "") + "(object _dummy, long ptr) : base(ptr) { bindings_instance = null; }\n"
-        out_java_trait = out_java_trait + "\tprivate " + struct_name.replace("LDK", "") + "(bindings." + struct_name + " arg"
-        for var in flattened_field_vars:
-            if isinstance(var, ConvInfo):
-                out_java_trait += ", " + var.java_hu_ty + " " + var.arg_name
-            else:
-                out_java_trait += ", bindings." + var[0] + " " + var[1]
-        out_java_trait += ") : base(bindings." + struct_name + "_new(arg"
-        for var in flattened_field_vars:
+    def native_c_map_trait(self, struct_name, field_var_conversions, flattened_field_var_conversions, field_function_lines, trait_doc_comment):
+        out_typescript_bindings = ""
+        super_instantiator = ""
+        bindings_instantiator = ""
+        pointer_to_adder = ""
+        impl_constructor_arguments = ""
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo):
+                impl_constructor_arguments += f", {var.java_hu_ty} {var.arg_name}"
                 if var.from_hu_conv is not None:
-                    out_java_trait = out_java_trait + ", " + var.from_hu_conv[0]
+                    bindings_instantiator += ", " + var.from_hu_conv[0]
+                    if var.from_hu_conv[1] != "":
+                        pointer_to_adder += "\t\t\t" + var.from_hu_conv[1] + ";\n"
                 else:
-                    out_java_trait = out_java_trait + ", " + var.arg_name
+                    bindings_instantiator += ", " + first_to_lower(var.arg_name)
             else:
-                out_java_trait = out_java_trait + ", " + var[1]
-        out_java_trait = out_java_trait + ")) {\n"
-        out_java_trait = out_java_trait + "\t\tthis.ptrs_to.AddLast(arg);\n"
-        for var in flattened_field_vars:
+                bindings_instantiator += ", " + first_to_lower(var[1]) + ".instance_idx"
+                pointer_to_adder += "\t\timpl_holder.held.ptrs_to.AddLast(" + first_to_lower(var[1]) + ");\n"
+                impl_constructor_arguments += f", {var[0].replace('LDK', '')}Interface {first_to_lower(var[1])}_impl"
+
+        super_constructor_statements = ""
+        trait_constructor_arguments = ""
+        for var in field_var_conversions:
             if isinstance(var, ConvInfo):
-                if var.from_hu_conv is not None and var.from_hu_conv[1] != "":
-                    out_java_trait = out_java_trait + "\t\t" + var.from_hu_conv[1].replace("\n", "\n\t\t") + ";\n"
+                trait_constructor_arguments += ", " + var.arg_name
             else:
-                out_java_trait = out_java_trait + "\t\tthis.ptrs_to.AddLast(" + var[1] + ");\n"
-        out_java_trait = out_java_trait + "\t\tthis.bindings_instance = arg;\n"
-        out_java_trait = out_java_trait + "\t}\n"
-        out_java_trait = out_java_trait + "\t~" + struct_name.replace("LDK","") + "() {\n"
-        out_java_trait = out_java_trait + "\t\tif (ptr != 0) { bindings." + struct_name.replace("LDK","") + "_free(ptr); }\n"
-        out_java_trait = out_java_trait + "\t}\n\n"
+                super_constructor_statements += "\t\t" + var[1] + " " + first_to_lower(var[1]) + " = " + var[1] + ".new_impl(" + first_to_lower(var[1]) + "_impl"
+                super_instantiator = ""
+                for suparg in var[2]:
+                    if isinstance(suparg, ConvInfo):
+                        super_instantiator += ", " + suparg.arg_name
+                    else:
+                        super_instantiator += ", " + first_to_lower(suparg[1]) + "_impl"
+                super_constructor_statements += super_instantiator + ");\n"
+                trait_constructor_arguments += ", " + first_to_lower(var[1]) + ".instance_idx"
+                for suparg in var[2]:
+                    if isinstance(suparg, ConvInfo):
+                        trait_constructor_arguments += ", " + suparg.arg_name
+                    else:
+                        # Blindly assume that we can just strip the first arg to build the args for the supertrait
+                        super_constructor_statements += "\t\t" + suparg[1] + " " + first_to_lower(suparg[1]) + " = " + suparg[1] + ".new_impl(" + super_instantiator.split(", ", 1)[1] + ");\n"
+                        trait_constructor_arguments += ", " + suparg[1]
+
+        # BUILD INTERFACE METHODS
 
         java_trait_wrapper = "\tprivate class " + struct_name + "Holder { internal " + struct_name.replace("LDK", "") + " held; }\n"
         java_trait_wrapper += "\tprivate class " + struct_name + "Impl : bindings." + struct_name + " {\n"
         java_trait_wrapper += "\t\tinternal " + struct_name + "Impl(" + struct_name.replace("LDK", "") + "Interface arg, " + struct_name + "Holder impl_holder) { this.arg = arg; this.impl_holder = impl_holder; }\n"
         java_trait_wrapper += "\t\tprivate " + struct_name.replace("LDK", "") + "Interface arg;\n"
         java_trait_wrapper += "\t\tprivate " + struct_name + "Holder impl_holder;\n"
-        java_trait_constr = "\tpublic static " + struct_name.replace("LDK", "") + " new_impl(" + struct_name.replace("LDK", "") + "Interface arg"
-        for var in flattened_field_vars:
-            if isinstance(var, ConvInfo):
-                java_trait_constr += ", " + var.java_hu_ty + " " + var.arg_name
-            else:
-                # Ideally we'd be able to take any instance of the interface, but our C code can only represent
-                # Java-implemented version, so we require users pass a Java implementation here :/
-                java_trait_constr += ", " + var[0].replace("LDK", "") + "." + var[0].replace("LDK", "") + "Interface " + var[1] + "_impl"
-        java_trait_constr = java_trait_constr + ") {\n\t\t" + struct_name + "Holder impl_holder = new " + struct_name + "Holder();\n"
-        java_trait_constr = java_trait_constr + "\t\timpl_holder.held = new " + struct_name.replace("LDK", "") + "(new " + struct_name + "Impl(arg, impl_holder)"
-        out_java_trait += "\tpublic interface " + struct_name.replace("LDK", "") + "Interface {\n"
-        out_java += "\tpublic interface " + struct_name + " {\n"
-        java_meths = []
-        for fn_line in field_fns:
-            java_meth_descr = "("
+
+        for fn_line in field_function_lines:
             if fn_line.fn_name != "free" and fn_line.fn_name != "cloned":
                 fn_name = fn_line.fn_name
                 if fn_name == "lock": # reserved symbol
                     fn_name = "do_lock"
-                out_java += "\t\t" + fn_line.ret_ty_info.java_ty + " " + fn_name + "("
                 java_trait_wrapper += "\t\tpublic " + fn_line.ret_ty_info.java_ty + " " + fn_name + "("
-                out_java_trait += "\t\t/**\n\t\t * " + fn_line.docs.replace("\n", "\n\t\t * ") + "\n\t\t */\n"
-                out_java_trait += "\t\t" + fn_line.ret_ty_info.java_hu_ty + " " + fn_name + "("
 
                 for idx, arg_conv_info in enumerate(fn_line.args_ty):
                     if idx >= 1:
-                        out_java += ", "
                         java_trait_wrapper += ", "
-                        out_java_trait += ", "
-                    out_java += arg_conv_info.java_ty + " _" + arg_conv_info.arg_name
-                    out_java_trait += arg_conv_info.java_hu_ty + " _" + arg_conv_info.arg_name
                     java_trait_wrapper += arg_conv_info.java_ty + " _" + arg_conv_info.arg_name
-                    java_meth_descr = java_meth_descr + arg_conv_info.java_fn_ty_arg
-                java_meth_descr = java_meth_descr + ")" + fn_line.ret_ty_info.java_fn_ty_arg
-                java_meths.append((fn_line.fn_name, java_meth_descr))
 
-                out_java += ");\n"
-                out_java_trait += ");\n"
                 java_trait_wrapper += ") {\n"
 
                 for arg_info in fn_line.args_ty:
@@ -698,64 +812,133 @@ namespace org { namespace ldk { namespace structs {
                         java_trait_wrapper += "\t\t\treturn ret;\n"
                 java_trait_wrapper += "\t\t}\n"
         java_trait_wrapper += "\t}"
-        for var in field_vars:
-            if isinstance(var, ConvInfo):
-                java_trait_constr = java_trait_constr + ", " + var.arg_name
-            else:
-                java_trait_constr += ", " + var[1] + ".new_impl(" + var[1] + "_impl"
-                for suparg in var[2]:
-                    if isinstance(suparg, ConvInfo):
-                        java_trait_constr += ", " + suparg.arg_name
-                    else:
-                        java_trait_constr += ", " + suparg[1]
-                java_trait_constr += ").bindings_instance"
-                for suparg in var[2]:
-                    if isinstance(suparg, ConvInfo):
-                        java_trait_constr += ", " + suparg.arg_name
-                    else:
-                        java_trait_constr += ", " + suparg[1]
-        out_java_trait += "\t}\n" + java_trait_wrapper + "\n"
-        out_java_trait += java_trait_constr + ");\n\t\treturn impl_holder.held;\n\t}\n"
 
-        out_java += "\t}\n"
+        out_java_interface = ""
+        java_methods = []
+        for fn_line in field_function_lines:
+            java_method_descriptor = ""
+            if fn_line.fn_name != "free" and fn_line.fn_name != "cloned":
+                out_java_interface += "\t/**" + fn_line.docs.replace("\n", "\n\t * ") + "\n\t */\n"
+                out_java_interface += "\t" + fn_line.ret_ty_info.java_hu_ty + " " + fn_line.fn_name + "("
+
+                for idx, arg_conv_info in enumerate(fn_line.args_ty):
+                    if idx >= 1:
+                        out_java_interface += ", "
+                    out_java_interface += f"{arg_conv_info.java_hu_ty} {safe_arg_name(arg_conv_info.arg_name)}"
+                    java_method_descriptor += arg_conv_info.java_fn_ty_arg
+                out_java_interface += f");\n"
+                java_method_descriptor += ")" + fn_line.ret_ty_info.java_fn_ty_arg
+                java_methods.append((fn_line.fn_name, java_method_descriptor))
+
+        formatted_trait_docs = trait_doc_comment.replace("\n", "\n * ")
+        out_typescript_human = f"""
+{self.hu_struct_file_prefix}
+
+/** An implementation of {struct_name.replace("LDK","")} */
+public interface {struct_name.replace("LDK", "")}Interface {{
+{out_java_interface}}}
+
+/**
+ * {formatted_trait_docs}
+ */
+public class {struct_name.replace("LDK","")} : CommonBase {{
+       internal bindings.{struct_name} bindings_instance;
+       internal long instance_idx;
+
+       internal {struct_name.replace("LDK","")}(object _dummy, long ptr) : base(ptr) {{ bindings_instance = null; }}
+       ~{struct_name.replace("LDK","")}() {{
+               if (ptr != 0) {{ bindings.{struct_name.replace("LDK","")}_free(ptr); }}
+       }}
+
+{java_trait_wrapper}
+
+       /** Creates a new instance of {struct_name.replace("LDK","")} from a given implementation */
+       public static {struct_name.replace("LDK", "")} new_impl({struct_name.replace("LDK", "")}Interface arg{impl_constructor_arguments}) {{
+               {struct_name}Holder impl_holder = new {struct_name}Holder();
+               {struct_name}Impl impl = new {struct_name}Impl(arg, impl_holder);
+{super_constructor_statements}         long[] ptr_idx = bindings.{struct_name}_new(impl{bindings_instantiator});
+
+               impl_holder.held = new {struct_name.replace("LDK", "")}(null, ptr_idx[0]);
+               impl_holder.held.instance_idx = ptr_idx[1];
+               impl_holder.held.bindings_instance = impl;
+{pointer_to_adder}             return impl_holder.held;
+       }}
+
+"""
 
-        out_java += self.native_meth_decl(struct_name + "_new", "long") + "(" + struct_name + " impl"
-        for var in flattened_field_vars:
+        out_typescript_bindings += "\tpublic interface " + struct_name + " {\n"
+        java_meths = []
+        for fn_line in field_function_lines:
+            if fn_line.fn_name != "free" and fn_line.fn_name != "cloned":
+                out_typescript_bindings += f"\t\t{fn_line.ret_ty_info.java_ty} {fn_line.fn_name}("
+
+                for idx, arg_conv_info in enumerate(fn_line.args_ty):
+                    if idx >= 1:
+                        out_typescript_bindings = out_typescript_bindings + ", "
+                    out_typescript_bindings += f"{arg_conv_info.java_ty} {safe_arg_name(arg_conv_info.arg_name)}"
+
+                out_typescript_bindings += f");\n"
+
+        out_typescript_bindings += "\t}\n"
+
+        c_call_extra_args = ""
+        native_fn_args = "long impl_idx"
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo):
-                out_java += ", " + var.java_ty + " " + var.arg_name
+                native_fn_args += ", " + var.java_ty + " " + var.arg_name
             else:
-                out_java += ", " + var[0] + " " + var[1]
-        out_java += ");\n"
+                native_fn_args += ", long " + var[1]
+        out_typescript_bindings += self.native_meth_decl(struct_name + "_new", "long") + "_native(" + native_fn_args + ");\n"
+        out_typescript_bindings += f"\tpublic static long[] {struct_name}_new({struct_name} impl"
+        for var in flattened_field_var_conversions:
+            if isinstance(var, ConvInfo):
+                out_typescript_bindings += f", {var.java_ty} {var.arg_name}"
+                c_call_extra_args += f", {var.arg_name}"
+            else:
+                out_typescript_bindings += f", long {var[1]}"
+                c_call_extra_args += f", {var[1]}"
+
+
+        out_typescript_bindings += f""") {{
+               long new_obj_idx = js_objs.Count;
+               int i = 0;
+               for (; i < js_objs.Count; i++) {{
+                       if (js_objs[i] == null || !js_objs[i].IsAlive) {{ new_obj_idx = i; break; }}
+               }}
+               if (i == js_objs.Count) {{
+                       js_objs.Add(new WeakReference(impl));
+               }} else {{
+                       js_objs[i] = new WeakReference(impl);
+               }}
+               long[] ret = new long[2];
+               ret[0] = {struct_name}_new_native(i{c_call_extra_args});
+               ret[1] = i;
+               return ret;
+       }}
+"""
 
         # Now that we've written out our java code (and created java_meths), generate C
         out_c = "typedef struct " + struct_name + "_JCalls {\n"
-        out_c = out_c + "\tatomic_size_t refcnt;\n"
-        out_c = out_c + "\tJavaVM *vm;\n"
-        out_c = out_c + "\tjweak o;\n"
-        for var in flattened_field_vars:
+        out_c += "\tatomic_size_t refcnt;\n"
+        out_c += "\tuint32_t instance_ptr;\n"
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo):
                 # We're a regular ol' field
                 pass
             else:
                 # We're a supertrait
                 out_c = out_c + "\t" + var[0] + "_JCalls* " + var[1] + ";\n"
-        for fn in field_fns:
-            if fn.fn_name != "free" and fn.fn_name != "cloned":
-                out_c = out_c + "\tjmethodID " + fn.fn_name + "_meth;\n"
         out_c = out_c + "} " + struct_name + "_JCalls;\n"
 
-        for fn_line in field_fns:
+        for fn_line in field_function_lines:
             if fn_line.fn_name == "free":
                 out_c = out_c + "static void " + struct_name + "_JCalls_free(void* this_arg) {\n"
                 out_c = out_c + "\t" + struct_name + "_JCalls *j_calls = (" + struct_name + "_JCalls*) this_arg;\n"
                 out_c = out_c + "\tif (atomic_fetch_sub_explicit(&j_calls->refcnt, 1, memory_order_acquire) == 1) {\n"
-                out_c += "\t\t" + self.construct_jenv().replace("\n", "\n\t\t").strip() + "\n"
-                out_c = out_c + "\t\t(*env)->DeleteWeakGlobalRef(env, j_calls->o);\n"
-                out_c += "\t\t" + self.deconstruct_jenv().replace("\n", "\n\t\t").strip() + "\n"
                 out_c = out_c + "\t\tFREE(j_calls);\n"
                 out_c = out_c + "\t}\n}\n"
 
-        for idx, fn_line in enumerate(field_fns):
+        for idx, fn_line in enumerate(field_function_lines):
             if fn_line.fn_name != "free" and fn_line.fn_name != "cloned":
                 assert fn_line.ret_ty_info.ty_info.get_full_rust_ty()[1] == ""
                 out_c = out_c + fn_line.ret_ty_info.ty_info.get_full_rust_ty()[0] + " " + fn_line.fn_name + "_" + struct_name + "_jcall("
@@ -769,7 +952,6 @@ namespace org { namespace ldk { namespace structs {
 
                 out_c = out_c + ") {\n"
                 out_c = out_c + "\t" + struct_name + "_JCalls *j_calls = (" + struct_name + "_JCalls*) this_arg;\n"
-                out_c += "\t" + self.construct_jenv().replace("\n", "\n\t").strip() + "\n"
 
                 for arg_info in fn_line.args_ty:
                     if arg_info.ret_conv is not None:
@@ -777,89 +959,98 @@ namespace org { namespace ldk { namespace structs {
                         out_c = out_c + arg_info.arg_name
                         out_c = out_c + arg_info.ret_conv[1].replace('\n', '\n\t') + "\n"
 
-                out_c = out_c + "\tjobject obj = (*env)->NewLocalRef(env, j_calls->o);\n\tCHECK(obj != NULL);\n"
+                ty_to_c = lambda jty, ty: "b" if jty == "bool" else "c" if jty == "char" else "s" if jty == "short" else "i" if jty == "int" else "l" if jty == "long" else "void" if jty == "void" else jty
+
+                fn_java_callback_args = ""
+                fn_c_callback_args = ""
+                fn_callback_call_args = ""
+                fn_suffix = ty_to_c(fn_line.ret_ty_info.java_ty, fn_line.ret_ty_info) + "_"
+                idx = 0
+                for arg_info in fn_line.args_ty:
+                    fn_suffix += ty_to_c(arg_info.java_ty, arg_info)
+                    fn_java_callback_args += ", " + arg_info.java_ty + " " + chr(ord("a") + idx)
+                    if arg_info.c_ty.endswith("Array") or arg_info.c_ty == "jstring":
+                        fn_c_callback_args += ", int64_t " + chr(ord("a") + idx)
+                    else:
+                        fn_c_callback_args += ", " + arg_info.c_ty + " " + chr(ord("a") + idx)
+                    if idx != 0:
+                        fn_callback_call_args += ", "
+                    fn_callback_call_args += chr(ord("a") + idx)
+                    idx += 1
                 if fn_line.ret_ty_info.c_ty.endswith("Array"):
-                    out_c = out_c + "\t" + fn_line.ret_ty_info.c_ty + " ret = (*env)->CallObjectMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
-                elif fn_line.ret_ty_info.c_ty == "void":
-                    out_c += "\t(*env)->CallVoidMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
-                elif fn_line.ret_ty_info.java_hu_ty == "string" or "org/ldk/enums" in fn_line.ret_ty_info.java_fn_ty_arg:
-                    # Manually write out string methods as they're just an Object
-                    out_c += "\t" + fn_line.ret_ty_info.c_ty + " ret = (*env)->CallObjectMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
-                elif not fn_line.ret_ty_info.passed_as_ptr:
-                    out_c += "\t" + fn_line.ret_ty_info.c_ty + " ret = (*env)->Call" + fn_line.ret_ty_info.java_ty.title() + "Method(env, obj, j_calls->" + fn_line.fn_name + "_meth"
+                    out_c += "\t" + fn_line.ret_ty_info.c_ty + " ret = (" + fn_line.ret_ty_info.c_ty + ")"
+                    out_c += "js_invoke_function_" + fn_suffix + "(j_calls->instance_ptr, " + str(self.function_ptr_counter)
+                elif fn_line.ret_ty_info.java_ty == "void":
+                    out_c = out_c + "\tjs_invoke_function_" + fn_suffix + "(j_calls->instance_ptr, " + str(self.function_ptr_counter)
+                elif fn_line.ret_ty_info.java_hu_ty == "string":
+                    out_c += "\tjstring ret = (jstring)js_invoke_function_" + fn_suffix + "(j_calls->instance_ptr, " + str(self.function_ptr_counter)
+                elif fn_line.ret_ty_info.arg_conv is None:
+                    out_c += "\treturn js_invoke_function_" + fn_suffix + "(j_calls->instance_ptr, " + str(self.function_ptr_counter)
                 else:
-                    out_c = out_c + "\tuint64_t ret = (*env)->CallLongMethod(env, obj, j_calls->" + fn_line.fn_name + "_meth"
+                    out_c += "\tuint64_t ret = js_invoke_function_" + fn_suffix + "(j_calls->instance_ptr, " + str(self.function_ptr_counter)
+
+                if fn_suffix not in self.function_ptrs:
+                    caller_ret_c_ty = fn_line.ret_ty_info.c_ty
+                    if fn_line.ret_ty_info.c_ty.endswith("Array") or fn_line.ret_ty_info.c_ty == "jstring":
+                        caller_ret_c_ty = "int64_t"
+                    self.function_ptrs[fn_suffix] = {"args": [fn_java_callback_args, fn_c_callback_args], "ret": [fn_line.ret_ty_info.java_ty, caller_ret_c_ty]}
+                self.function_ptrs[fn_suffix][self.function_ptr_counter] = (struct_name, fn_line.fn_name, fn_callback_call_args)
+                self.function_ptr_counter += 1
 
                 for idx, arg_info in enumerate(fn_line.args_ty):
                     if arg_info.ret_conv is not None:
-                        out_c = out_c + ", " + arg_info.ret_conv_name
+                        if arg_info.c_ty.endswith("Array") or arg_info.c_ty == "jstring":
+                            out_c += ", (int64_t)" + arg_info.ret_conv_name
+                        else:
+                            out_c += ", " + arg_info.ret_conv_name
                     else:
-                        out_c = out_c + ", " + arg_info.arg_name
+                        assert False # TODO: Would we need some conversion here?
+                        out_c += ", (int64_t)" + arg_info.arg_name
                 out_c = out_c + ");\n"
-
-                out_c += "\tif (UNLIKELY((*env)->ExceptionCheck(env))) {\n"
-                out_c += "\t\t(*env)->ExceptionDescribe(env);\n"
-                out_c += "\t\t(*env)->FatalError(env, \"A call to " + fn_line.fn_name + " in " + struct_name + " from rust threw an exception.\");\n"
-                out_c += "\t}\n"
-
                 if fn_line.ret_ty_info.arg_conv is not None:
-                    out_c += "\t" + fn_line.ret_ty_info.arg_conv.replace("\n", "\n\t") + "\n"
-                    out_c += "\t" + self.deconstruct_jenv().replace("\n", "\n\t").strip() + "\n"
-                    out_c += "\treturn " + fn_line.ret_ty_info.arg_conv_name + ";\n"
-                else:
-                    out_c += "\t" + self.deconstruct_jenv().replace("\n", "\n\t").strip() + "\n"
-                    if not fn_line.ret_ty_info.passed_as_ptr and fn_line.ret_ty_info.c_ty != "void":
-                        out_c += "\treturn ret;\n"
+                    out_c = out_c + "\t" + fn_line.ret_ty_info.arg_conv.replace("\n", "\n\t") + "\n\treturn " + fn_line.ret_ty_info.arg_conv_name + ";\n"
 
                 out_c = out_c + "}\n"
 
-        # If we can, write out a clone function whether we need one or not, as we use them in moving to rust
-        can_clone_with_ptr = True
-        for var in field_vars:
-            if isinstance(var, ConvInfo):
-                can_clone_with_ptr = False
-        if can_clone_with_ptr:
-            out_c = out_c + "static void " + struct_name + "_JCalls_cloned(" + struct_name + "* new_obj) {\n"
-            out_c = out_c + "\t" + struct_name + "_JCalls *j_calls = (" + struct_name + "_JCalls*) new_obj->this_arg;\n"
-            out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->refcnt, 1, memory_order_release);\n"
-            for var in field_vars:
-                if not isinstance(var, ConvInfo):
-                    out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->" + var[1] + "->refcnt, 1, memory_order_release);\n"
-            out_c = out_c + "}\n"
-
-        out_c = out_c + "static inline " + struct_name + " " + struct_name + "_init (" + self.c_fn_args_pfx + ", jobject o"
-        for var in flattened_field_vars:
+        # Write out a clone function whether we need one or not, as we use them in moving to rust
+        out_c = out_c + "static void " + struct_name + "_JCalls_cloned(" + struct_name + "* new_obj) {\n"
+        out_c = out_c + "\t" + struct_name + "_JCalls *j_calls = (" + struct_name + "_JCalls*) new_obj->this_arg;\n"
+        out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->refcnt, 1, memory_order_release);\n"
+        for var in flattened_field_var_conversions:
+            if not isinstance(var, ConvInfo):
+                out_c = out_c + "\tatomic_fetch_add_explicit(&j_calls->" + var[2].replace(".", "->") + "->refcnt, 1, memory_order_release);\n"
+        out_c = out_c + "}\n"
+
+        out_c = out_c + "static inline " + struct_name + " " + struct_name + "_init (int64_t o"
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo):
                 out_c = out_c + ", " + var.c_ty + " " + var.arg_name
             else:
-                out_c = out_c + ", jobject " + var[1]
+                out_c = out_c + ", int64_t " + var[1]
         out_c = out_c + ") {\n"
 
-        out_c = out_c + "\tjclass c = (*env)->GetObjectClass(env, o);\n"
-        out_c = out_c + "\tCHECK(c != NULL);\n"
         out_c = out_c + "\t" + struct_name + "_JCalls *calls = MALLOC(sizeof(" + struct_name + "_JCalls), \"" + struct_name + "_JCalls\");\n"
         out_c = out_c + "\tatomic_init(&calls->refcnt, 1);\n"
-        out_c = out_c + "\tDO_ASSERT((*env)->GetJavaVM(env, &calls->vm) == 0);\n"
-        out_c = out_c + "\tcalls->o = (*env)->NewWeakGlobalRef(env, o);\n"
+        out_c = out_c + "\tcalls->instance_ptr = o;\n"
 
         for (fn_name, java_meth_descr) in java_meths:
             if fn_name != "free" and fn_name != "cloned":
                 out_c = out_c + "\tcalls->" + fn_name + "_meth = (*env)->GetMethodID(env, c, \"" + fn_name + "\", \"" + java_meth_descr + "\");\n"
                 out_c = out_c + "\tCHECK(calls->" + fn_name + "_meth != NULL);\n"
 
-        for var in flattened_field_vars:
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo) and var.arg_conv is not None:
                 out_c = out_c + "\n\t" + var.arg_conv.replace("\n", "\n\t") +"\n"
         out_c = out_c + "\n\t" + struct_name + " ret = {\n"
         out_c = out_c + "\t\t.this_arg = (void*) calls,\n"
-        for fn_line in field_fns:
+        for fn_line in field_function_lines:
             if fn_line.fn_name != "free" and fn_line.fn_name != "cloned":
                 out_c = out_c + "\t\t." + fn_line.fn_name + " = " + fn_line.fn_name + "_" + struct_name + "_jcall,\n"
             elif fn_line.fn_name == "free":
                 out_c = out_c + "\t\t.free = " + struct_name + "_JCalls_free,\n"
             else:
                 out_c = out_c + "\t\t.cloned = " + struct_name + "_JCalls_cloned,\n"
-        for var in field_vars:
+        for var in field_var_conversions:
             if isinstance(var, ConvInfo):
                 if var.arg_conv_name is not None:
                     out_c = out_c + "\t\t." + var.arg_name + " = " + var.arg_conv_name + ",\n"
@@ -868,30 +1059,30 @@ namespace org { namespace ldk { namespace structs {
                     out_c = out_c + "\t\t." + var.var_name + " = " + var.var_name + ",\n"
                     out_c = out_c + "\t\t.set_" + var.var_name + " = NULL,\n"
             else:
-                out_c += "\t\t." + var[1] + " = " + var[0] + "_init(env, clz, " + var[1]
+                out_c += "\t\t." + var[1] + " = " + var[0] + "_init(" + var[1]
                 for suparg in var[2]:
                     if isinstance(suparg, ConvInfo):
-                        out_c = out_c + ", " + suparg.arg_name
+                        out_c += ", " + suparg.arg_name
                     else:
-                        out_c = out_c + ", " + suparg[1]
+                        out_c += ", " + suparg[1]
                 out_c += "),\n"
         out_c = out_c + "\t};\n"
-        for var in flattened_field_vars:
+        for var in flattened_field_var_conversions:
             if not isinstance(var, ConvInfo):
-                out_c = out_c + "\tcalls->" + var[1] + " = ret." + var[1] + ".this_arg;\n"
+                out_c = out_c + "\tcalls->" + var[1] + " = ret." + var[2] + ".this_arg;\n"
         out_c = out_c + "\treturn ret;\n"
         out_c = out_c + "}\n"
 
-        out_c += "int64_t " + self.c_fn_name_define_pfx(struct_name + "_new", True) + "jobject o"
-        for var in flattened_field_vars:
+        out_c = out_c + self.c_fn_ty_pfx + "uint64_t " + self.c_fn_name_define_pfx(struct_name + "_new", True) + "int32_t o"
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo):
                 out_c = out_c + ", " + var.c_ty + " " + var.arg_name
             else:
-                out_c = out_c + ", jobject " + var[1]
+                out_c = out_c + ", int32_t " + var[1]
         out_c = out_c + ") {\n"
         out_c = out_c + "\t" + struct_name + " *res_ptr = MALLOC(sizeof(" + struct_name + "), \"" + struct_name + "\");\n"
-        out_c = out_c + "\t*res_ptr = " + struct_name + "_init(env, clz, o"
-        for var in flattened_field_vars:
+        out_c = out_c + "\t*res_ptr = " + struct_name + "_init(o"
+        for var in flattened_field_var_conversions:
             if isinstance(var, ConvInfo):
                 out_c = out_c + ", " + var.arg_name
             else:
@@ -900,27 +1091,7 @@ namespace org { namespace ldk { namespace structs {
         out_c = out_c + "\treturn tag_ptr(res_ptr, true);\n"
         out_c = out_c + "}\n"
 
-        for var in flattened_field_vars:
-            if not isinstance(var, ConvInfo):
-                out_java_trait += "\n\t/**\n"
-                out_java_trait += "\t * Gets the underlying " + var[1] + ".\n"
-                out_java_trait += "\t */\n"
-                underscore_name = ''.join('_' + c.lower() if c.isupper() else c for c in var[1]).strip('_')
-                out_java_trait += "\tpublic " + var[1] + " get_" + underscore_name + "() {\n"
-                out_java_trait += "\t\t" + var[1] + " res = new " + var[1] + "(null, bindings." + struct_name + "_get_" + var[1] + "(this.ptr));\n"
-                out_java_trait += "\t\tthis.ptrs_to.AddLast(res);\n"
-                out_java_trait += "\t\treturn res;\n"
-                out_java_trait += "\t}\n"
-                out_java_trait += "\n"
-
-                out_java += self.native_meth_decl(struct_name + "_get_" + var[1], "long") + "(long arg);\n"
-
-                out_c += "int64_t " + self.c_fn_name_define_pfx(struct_name + "_get_" + var[1], True) + "int64_t arg) {\n"
-                out_c += "\t" + struct_name + " *inp = (" + struct_name + " *)untag_ptr(arg);\n"
-                out_c += "\treturn tag_ptr(&inp->" + var[1] + ", false);\n"
-                out_c += "}\n"
-
-        return (out_java, out_java_trait, out_c)
+        return (out_typescript_bindings, out_typescript_human, out_c)
 
     def trait_struct_inc_refcnt(self, ty_info):
         base_conv = "\nif (" + ty_info.var_name + "_conv.free == " + ty_info.rust_obj + "_JCalls_free) {\n"
@@ -984,9 +1155,9 @@ namespace org { namespace ldk { namespace structs {
                 fn_name = f"{struct_name}_{var.var_name}_get_{field_map.arg_name}"
                 out_c += self.c_fn_ty_pfx + field_map.c_ty + self.c_fn_name_define_pfx(fn_name, True) + self.ptr_c_ty + " ptr) {\n"
                 out_c += "\t" + struct_name + " *obj = (" + struct_name + "*)untag_ptr(ptr);\n"
-                out_c += f"\tassert(obj->tag == {struct_name}_{var.var_name});\n"
+                out_c += f"\tCHECK(obj->tag == {struct_name}_{var.var_name});\n"
                 if field_map.ret_conv is not None:
-                    out_c += ("\t\t\t" + field_map.ret_conv[0].replace("\n", "\n\t\t\t"))
+                    out_c += ("\t" + field_map.ret_conv[0].replace("\n", "\n\t"))
                     if var.tuple_variant:
                         out_c += "obj->" + camel_to_snake(var.var_name)
                     else:
@@ -1076,7 +1247,6 @@ namespace org { namespace ldk { namespace structs {
         return java_hu_struct
 
     def map_function(self, argument_types, c_call_string, method_name, meth_n, return_type_info, struct_meth, default_constructor_args, takes_self, takes_self_as_ref, args_known, type_mapping_generator, doc_comment):
-        arg_name_repl = lambda s, arg_name: s.replace(arg_name, "_" + arg_name) if arg_name == "lock" or arg_name == "event" or arg_name == "params" else s
         out_java = ""
         out_c = ""
         out_java_struct = None
@@ -1271,4 +1441,55 @@ namespace org { namespace ldk { namespace structs {
         return (out_java, out_c, out_java_struct + extra_java_struct_out)
 
     def cleanup(self):
-        pass
+        with open(self.outdir + "src/org/ldk/impl/bindings.cs", "a") as bindings:
+            for fn_suffix in self.function_ptrs:
+                jret = self.function_ptrs[fn_suffix]["ret"][0]
+                jargs = self.function_ptrs[fn_suffix]["args"][0]
+
+                bindings.write(f"""
+       static {jret} c_callback_{fn_suffix}(int obj_ptr, int fn_id{jargs}) {{
+               if (obj_ptr >= js_objs.Count) {{
+                       Console.Error.WriteLine("Got function call on unknown/free'd JS object in {fn_suffix}");
+                       Console.Error.Flush();
+                       Environment.Exit(42);
+               }}
+               object obj = js_objs[obj_ptr].Target;
+               if (obj == null) {{
+                       Console.Error.WriteLine("Got function call on GC'd JS object in {fn_suffix}");
+                       Console.Error.Flush();
+                       Environment.Exit(43);
+               }}
+""")
+                bindings.write("\t\tswitch (fn_id) {\n")
+                for f in self.function_ptrs[fn_suffix]:
+                    if f != "ret" and f != "args" and f != "call":
+                        bindings.write(f"""\t\t\tcase {str(f)}:
+                               if (!(obj is {self.function_ptrs[fn_suffix][f][0]})) {{
+                                       Console.Error.WriteLine("Got function call to object that wasn't a {self.function_ptrs[fn_suffix][f][0]} in {fn_suffix}");
+                                       Console.Error.Flush();
+                                       Environment.Exit(44);
+                               }}\n""")
+                        call = f"(({self.function_ptrs[fn_suffix][f][0]})obj).{self.function_ptrs[fn_suffix][f][1]}({self.function_ptrs[fn_suffix][f][2]});"
+                        if jret != "void":
+                            bindings.write("\t\t\t\treturn " + call)
+                        else:
+                            bindings.write("\t\t\t\t" + call + "\n\t\t\t\treturn;")
+                        bindings.write("\n")
+
+                bindings.write(f"""\t\t\tdefault:
+                               Console.Error.WriteLine("Got unknown function call with id " + fn_id + " from C in {fn_suffix}");
+                               Console.Error.Flush();
+                               Environment.Exit(45);
+                               return{" false" if jret == "bool" else " 0" if jret != "void" else ""};
+               }}
+       }}
+       public delegate {jret} {fn_suffix}_callback(int obj_ptr, int fn_id{jargs});
+       static {fn_suffix}_callback {fn_suffix}_callback_inst = c_callback_{fn_suffix};
+""")
+                bindings.write(self.native_meth_decl(f"register_{fn_suffix}_invoker", "int") + f"({fn_suffix}_callback callee);\n")
+                # Easiest way to get a static run is just define a variable, even if we dont care
+                bindings.write(f"\tstatic int _run_{fn_suffix}_registration = register_{fn_suffix}_invoker({fn_suffix}_callback_inst);")
+
+            bindings.write("""
+}
+} } }""")