Fix kotlin wallet struct access via JNA opaque pointer
This commit is contained in:
parent
8deb39ac76
commit
a5ad4cd0a5
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ Cargo.lock
|
|||||||
/local.properties
|
/local.properties
|
||||||
.gradle
|
.gradle
|
||||||
wallet_db
|
wallet_db
|
||||||
|
bdk_ffi_test
|
||||||
|
72
bdk_ffi_test.c
Normal file
72
bdk_ffi_test.c
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "bdk_ffi.h"
|
||||||
|
|
||||||
|
int main (int argc, char const * const argv[])
|
||||||
|
{
|
||||||
|
// test print_string
|
||||||
|
print_string("hello 123");
|
||||||
|
|
||||||
|
// test concat_string
|
||||||
|
char const * string1 = "string1";
|
||||||
|
char const * string2 = "string2";
|
||||||
|
char * string3 = concat_string(string1, string2);
|
||||||
|
print_string(string3);
|
||||||
|
free_string(string3);
|
||||||
|
// verify free_string after free_string fails
|
||||||
|
////free_string(string3);
|
||||||
|
|
||||||
|
// test print_config with c created config
|
||||||
|
Config_t config1 = { .name = "test", .count = 101 };
|
||||||
|
print_config(&config1);
|
||||||
|
|
||||||
|
// test new_config
|
||||||
|
Config_t * config2 = new_config("test test", 202);
|
||||||
|
print_config(config2);
|
||||||
|
|
||||||
|
// test free_config
|
||||||
|
free_config(config2);
|
||||||
|
// verify print_config after free_config fails (invalid data)
|
||||||
|
////print_config(config2);
|
||||||
|
// verify free_config after free_config fails (double free detected, core dumped)
|
||||||
|
////free_config(config2);
|
||||||
|
|
||||||
|
char const * name = "test_wallet";
|
||||||
|
char const * desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)";
|
||||||
|
char const * change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)";
|
||||||
|
//char const * change = NULL;
|
||||||
|
|
||||||
|
// test new_wallet
|
||||||
|
{
|
||||||
|
WalletPtr_t * wallet = new_wallet(name, desc, change);
|
||||||
|
|
||||||
|
// test sync_wallet
|
||||||
|
sync_wallet(wallet);
|
||||||
|
printf("after sync_wallet\n");
|
||||||
|
sync_wallet(wallet);
|
||||||
|
printf("after sync_wallet\n");
|
||||||
|
|
||||||
|
// test new_address
|
||||||
|
char * address1 = new_address(wallet);
|
||||||
|
printf("address1: %s\n", address1);
|
||||||
|
free_string(address1);
|
||||||
|
assert(address1 != NULL);
|
||||||
|
char * address2 = new_address(wallet);
|
||||||
|
printf("address2: %s\n", address2);
|
||||||
|
assert(address2 != NULL);
|
||||||
|
free_string(address2);
|
||||||
|
|
||||||
|
// test free_wallet
|
||||||
|
free_wallet(wallet);
|
||||||
|
printf("after free_wallet\n");
|
||||||
|
|
||||||
|
// test free_wallet NULL doesn't crash
|
||||||
|
free_wallet(NULL);
|
||||||
|
|
||||||
|
// verify sync_wallet after sync_wallet fails (double free detected, core dumped)
|
||||||
|
////sync_wallet(&wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
10
build.sh
10
build.sh
@ -1,9 +1,13 @@
|
|||||||
|
# rust
|
||||||
cargo build
|
cargo build
|
||||||
cargo test --features c-headers -- generate_headers
|
cargo test --features c-headers -- generate_headers
|
||||||
cc main.c -o main -L target/debug -l bdk_ffi -l pthread -l dl -l m
|
export LD_LIBRARY_PATH=`pwd`/target/debug
|
||||||
./main
|
|
||||||
|
# cc
|
||||||
|
cc bdk_ffi_test.c -o bdk_ffi_test -L target/debug -l bdk_ffi -l pthread -l dl -l m
|
||||||
|
#valgrind --leak-check=full ./bdk_ffi_test
|
||||||
|
./bdk_ffi_test
|
||||||
|
|
||||||
# jvm
|
# jvm
|
||||||
mkdir -p jvm/build/jniLibs/x86_64_linux
|
mkdir -p jvm/build/jniLibs/x86_64_linux
|
||||||
cp target/debug/libbdk_ffi.so jvm/build/jniLibs/x86_64_linux
|
cp target/debug/libbdk_ffi.so jvm/build/jniLibs/x86_64_linux
|
||||||
export LD_LIBRARY_PATH=`pwd`/target/debug
|
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
@ -7,9 +7,9 @@ plugins {
|
|||||||
test {
|
test {
|
||||||
systemProperty "java.library.path", file("${buildDir}/jniLibs/x86_64_linux").absolutePath
|
systemProperty "java.library.path", file("${buildDir}/jniLibs/x86_64_linux").absolutePath
|
||||||
environment "LD_LIBRARY_PATH", file("${buildDir}/jniLibs/x86_64_linux").absolutePath
|
environment "LD_LIBRARY_PATH", file("${buildDir}/jniLibs/x86_64_linux").absolutePath
|
||||||
// testLogging {
|
testLogging {
|
||||||
// events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR"
|
events "PASSED", "SKIPPED", "FAILED", "STANDARD_OUT", "STANDARD_ERROR"
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildRust(type: Exec) {
|
task buildRust(type: Exec) {
|
||||||
|
@ -10,18 +10,19 @@ import com.sun.jna.ptr.PointerByReference
|
|||||||
// int32_t count;
|
// int32_t count;
|
||||||
//
|
//
|
||||||
// } Config_t;
|
// } Config_t;
|
||||||
//@Structure.FieldOrder("x", "y")
|
@Structure.FieldOrder("name", "count")
|
||||||
class Config_t : Structure() {
|
class Config_t : Structure() {
|
||||||
@JvmField
|
@JvmField
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
@JvmField
|
@JvmField
|
||||||
var count: NativeLong? = null
|
var count: NativeLong? = null
|
||||||
|
|
||||||
override fun getFieldOrder() = listOf("name", "count")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// typedef struct WalletPtr WalletPtr_t;
|
// typedef struct WalletPtr WalletPtr_t;
|
||||||
//class WalletPtr_t : PointerType()
|
class WalletPtr_t : PointerType {
|
||||||
|
constructor(): super()
|
||||||
|
constructor(pointer: Pointer): super(pointer)
|
||||||
|
}
|
||||||
|
|
||||||
interface Lib : Library {
|
interface Lib : Library {
|
||||||
|
|
||||||
@ -38,10 +39,6 @@ interface Lib : Library {
|
|||||||
// char * string);
|
// char * string);
|
||||||
fun free_string(string: String)
|
fun free_string(string: String)
|
||||||
|
|
||||||
// void print_int (
|
|
||||||
// int64_t number);
|
|
||||||
fun print_int(number: Int)
|
|
||||||
|
|
||||||
// void print_config (
|
// void print_config (
|
||||||
// Config_t const * config);
|
// Config_t const * config);
|
||||||
fun print_config(config: Config_t)
|
fun print_config(config: Config_t)
|
||||||
@ -59,18 +56,18 @@ interface Lib : Library {
|
|||||||
// char const * name,
|
// char const * name,
|
||||||
// char const * descriptor,
|
// char const * descriptor,
|
||||||
// char const * change_descriptor);
|
// char const * change_descriptor);
|
||||||
//fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t
|
fun new_wallet(name: String, descriptor: String, changeDescriptor: String?): WalletPtr_t
|
||||||
|
|
||||||
// void sync_wallet (
|
// void sync_wallet (
|
||||||
// WalletPtr_t * const * wallet);
|
// WalletPtr_t * const * wallet);
|
||||||
//fun sync_wallet(wallet: WalletPtr_t)
|
//fun sync_wallet(wallet: WalletPtr_t)
|
||||||
//fun sync_wallet(wallet: WalletPtr_t)
|
fun sync_wallet(wallet: WalletPtr_t)
|
||||||
|
|
||||||
// char * new_address (
|
// char * new_address (
|
||||||
// WalletPtr_t * const * wallet);
|
// WalletPtr_t * const * wallet);
|
||||||
//fun new_address(wallet: WalletPtr_t): String
|
fun new_address(wallet: WalletPtr_t): String
|
||||||
|
|
||||||
// void free_wallet (
|
// void free_wallet (
|
||||||
// WalletPtr_t * wallet);
|
// WalletPtr_t * wallet);
|
||||||
//fun free_wallet(wallet: WalletPtr_t)
|
fun free_wallet(wallet: WalletPtr_t)
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package org.bitcoindevkit.bdkjni
|
|||||||
|
|
||||||
import com.sun.jna.Native
|
import com.sun.jna.Native
|
||||||
import com.sun.jna.NativeLong
|
import com.sun.jna.NativeLong
|
||||||
import com.sun.jna.Pointer
|
|
||||||
import com.sun.jna.ptr.PointerByReference
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,26 +41,26 @@ class LibTest {
|
|||||||
lib.free_config(config)
|
lib.free_config(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
// fun new_sync_free_wallet() {
|
fun new_sync_free_wallet() {
|
||||||
// val name = "test_wallet"
|
val name = "test_wallet"
|
||||||
// val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
||||||
// val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
||||||
//
|
|
||||||
// val wallet = lib.new_wallet(name, desc, change)
|
|
||||||
// println("wallet created in kotlin: $wallet")
|
|
||||||
// lib.sync_wallet(wallet)
|
|
||||||
// //lib.free_wallet(wallet)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Test
|
val wallet = lib.new_wallet(name, desc, change)
|
||||||
// fun new_newaddress_wallet() {
|
lib.sync_wallet(wallet)
|
||||||
// val name = "test_wallet"
|
lib.free_wallet(wallet)
|
||||||
// val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
}
|
||||||
// val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
|
||||||
//
|
@Test
|
||||||
// val config = lib.new_config("test test", NativeLong(Long.MAX_VALUE))
|
fun new_newaddress_wallet() {
|
||||||
// lib.print_config(config)
|
val name = "test_wallet"
|
||||||
// lib.free_config(config)
|
val desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
|
||||||
// }
|
val change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
|
||||||
|
|
||||||
|
val wallet = lib.new_wallet(name, desc, change)
|
||||||
|
val address = lib.new_address(wallet)
|
||||||
|
println("address created from kotlin: $address")
|
||||||
|
lib.free_wallet(wallet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
55
main.c
55
main.c
@ -1,55 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "bdk_ffi.h"
|
|
||||||
|
|
||||||
int main (int argc, char const * const argv[])
|
|
||||||
{
|
|
||||||
// test print_string
|
|
||||||
print_string("hello 123");
|
|
||||||
|
|
||||||
// test concat_string
|
|
||||||
char const * string1 = "string1";
|
|
||||||
char const * string2 = "string2";
|
|
||||||
char * string3 = concat_string(string1, string2);
|
|
||||||
print_string(string3);
|
|
||||||
free_string(string3);
|
|
||||||
// verify free_string after free_string fails
|
|
||||||
////free_string(string3);
|
|
||||||
|
|
||||||
// test print_config with c created config
|
|
||||||
Config_t config1 = { .name = "test", .count = 101 };
|
|
||||||
print_config(&config1);
|
|
||||||
|
|
||||||
// test new_config
|
|
||||||
Config_t * config2 = new_config("test test", 202);
|
|
||||||
print_config(config2);
|
|
||||||
|
|
||||||
// test free_config
|
|
||||||
free_config(config2);
|
|
||||||
// verify print_config after free_config fails (invalid data)
|
|
||||||
////print_config(config2);
|
|
||||||
// verify free_config after free_config fails (double free detected, core dumped)
|
|
||||||
////free_config(config2);
|
|
||||||
|
|
||||||
//char const * name = "test_wallet";
|
|
||||||
//char const * desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)";
|
|
||||||
//char const * change = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)";
|
|
||||||
|
|
||||||
////printf("wallet name: %s\n", name);
|
|
||||||
////printf("descriptor: %s\n", desc);
|
|
||||||
////printf("change descriptor: %s\n", change);
|
|
||||||
//WalletPtr_t * wallet = new_wallet(name, desc, change);
|
|
||||||
|
|
||||||
//sync_wallet(&wallet);
|
|
||||||
//sync_wallet(&wallet);
|
|
||||||
|
|
||||||
//char const * address1 = new_address(&wallet);
|
|
||||||
//printf("address1: %s\n", address1);
|
|
||||||
//char const * address2 = new_address(&wallet);
|
|
||||||
//printf("address: %s\n", address2);
|
|
||||||
|
|
||||||
//free_wallet(wallet);
|
|
||||||
////sync_wallet(&wallet);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
119
src/lib.rs
119
src/lib.rs
@ -64,73 +64,68 @@ fn free_config(config: Box<Config>) {
|
|||||||
drop(config)
|
drop(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[derive_ReprC]
|
#[derive_ReprC]
|
||||||
//#[ReprC::opaque]
|
#[ReprC::opaque]
|
||||||
//pub struct WalletPtr {
|
pub struct WalletPtr {
|
||||||
// raw: Wallet<ElectrumBlockchain, Tree>,
|
raw: Wallet<ElectrumBlockchain, Tree>,
|
||||||
//}
|
}
|
||||||
|
|
||||||
//impl From<Wallet<ElectrumBlockchain, Tree>> for WalletPtr {
|
impl From<Wallet<ElectrumBlockchain, Tree>> for WalletPtr {
|
||||||
// fn from(wallet: Wallet<ElectrumBlockchain, Tree>) -> Self {
|
fn from(wallet: Wallet<ElectrumBlockchain, Tree>) -> Self {
|
||||||
// WalletPtr {
|
WalletPtr {
|
||||||
// raw: wallet,
|
raw: wallet,
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
//#[ffi_export]
|
#[ffi_export]
|
||||||
//fn new_wallet<'a>(
|
fn new_wallet(
|
||||||
// name: char_p_ref<'a>,
|
name: char_p_ref,
|
||||||
// descriptor: char_p_ref<'a>,
|
descriptor: char_p_ref,
|
||||||
// change_descriptor: Option<char_p_ref<'a>>,
|
change_descriptor: Option<char_p_ref>,
|
||||||
//) -> Box<WalletPtr> {
|
) -> Box<WalletPtr> {
|
||||||
// let name = name.to_string();
|
let name = name.to_string();
|
||||||
// let descriptor = descriptor.to_string();
|
let descriptor = descriptor.to_string();
|
||||||
// let change_descriptor = change_descriptor.map(|s| s.to_string());
|
let change_descriptor = change_descriptor.map(|s| s.to_string());
|
||||||
//
|
|
||||||
// let database = sled::open("./wallet_db").unwrap();
|
|
||||||
// let tree = database.open_tree(name.clone()).unwrap();
|
|
||||||
//
|
|
||||||
// let descriptor: &str = descriptor.as_str();
|
|
||||||
// let change_descriptor: Option<&str> = change_descriptor.as_deref();
|
|
||||||
//
|
|
||||||
// let electrum_url = "ssl://electrum.blockstream.info:60002";
|
|
||||||
// let client = Client::new(&electrum_url).unwrap();
|
|
||||||
//
|
|
||||||
// let wallet = Wallet::new(
|
|
||||||
// descriptor,
|
|
||||||
// change_descriptor,
|
|
||||||
// Testnet,
|
|
||||||
// tree,
|
|
||||||
// ElectrumBlockchain::from(client),
|
|
||||||
// )
|
|
||||||
// .unwrap();
|
|
||||||
//
|
|
||||||
// Box::new(WalletPtr::from(wallet))
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[ffi_export]
|
let database = sled::open("./wallet_db").unwrap();
|
||||||
//fn sync_wallet( wallet: &Box<WalletPtr>) {
|
let tree = database.open_tree(name.clone()).unwrap();
|
||||||
// println!("before sync");
|
|
||||||
// let _r = wallet.raw.sync(log_progress(), Some(100));
|
|
||||||
// println!("after sync");
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[ffi_export]
|
let descriptor: &str = descriptor.as_str();
|
||||||
//fn new_address( wallet: &Box<WalletPtr>) -> char_p_boxed {
|
let change_descriptor: Option<&str> = change_descriptor.as_deref();
|
||||||
// println!("before new_address");
|
|
||||||
// let new_address = wallet.raw.get_address(New);
|
|
||||||
// println!("after new_address: {:?}", new_address);
|
|
||||||
// let new_address = new_address.unwrap();
|
|
||||||
// let new_address = new_address.to_string();
|
|
||||||
// println!("new address: ${}", new_address);
|
|
||||||
// new_address.try_into().unwrap()
|
|
||||||
//}
|
|
||||||
|
|
||||||
//#[ffi_export]
|
let electrum_url = "ssl://electrum.blockstream.info:60002";
|
||||||
//fn free_wallet( wallet: Box<WalletPtr>) {
|
let client = Client::new(&electrum_url).unwrap();
|
||||||
// drop(wallet)
|
|
||||||
//}
|
let wallet = Wallet::new(
|
||||||
|
descriptor,
|
||||||
|
change_descriptor,
|
||||||
|
Testnet,
|
||||||
|
tree,
|
||||||
|
ElectrumBlockchain::from(client),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
println!("created wallet");
|
||||||
|
Box::new(WalletPtr::from(wallet))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn sync_wallet( wallet: &WalletPtr) {
|
||||||
|
let _r = wallet.raw.sync(log_progress(), Some(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn new_address( wallet: &WalletPtr) -> char_p_boxed {
|
||||||
|
let new_address = wallet.raw.get_address(New);
|
||||||
|
let new_address = new_address.unwrap();
|
||||||
|
let new_address = new_address.to_string();
|
||||||
|
new_address.try_into().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ffi_export]
|
||||||
|
fn free_wallet( wallet: Option<Box<WalletPtr>>) {
|
||||||
|
drop(wallet)
|
||||||
|
}
|
||||||
|
|
||||||
/// The following test function is necessary for the header generation.
|
/// The following test function is necessary for the header generation.
|
||||||
#[::safer_ffi::cfg_headers]
|
#[::safer_ffi::cfg_headers]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user