mirror of
https://github.com/fluencelabs/wasmer
synced 2025-06-13 17:11:21 +00:00
Update imports and add func_new
This commit is contained in:
@ -3,6 +3,7 @@ extern crate wasmer_runtime_core;
|
|||||||
|
|
||||||
use libc::{c_char, c_int, int32_t, int64_t, uint32_t, uint8_t};
|
use libc::{c_char, c_int, int32_t, int64_t, uint32_t, uint8_t};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
@ -99,8 +100,9 @@ pub struct wasmer_func_signature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone)]
|
pub struct wasmer_import_t {
|
||||||
pub struct wasmer_import {
|
module_name: wasmer_byte_array,
|
||||||
|
import_name: wasmer_byte_array,
|
||||||
tag: wasmer_import_export_kind,
|
tag: wasmer_import_export_kind,
|
||||||
value: wasmer_import_export_value,
|
value: wasmer_import_export_value,
|
||||||
}
|
}
|
||||||
@ -389,18 +391,63 @@ pub unsafe extern "C" fn wasmer_instantiate(
|
|||||||
mut instance: *mut *mut wasmer_instance_t,
|
mut instance: *mut *mut wasmer_instance_t,
|
||||||
wasm_bytes: *mut uint8_t,
|
wasm_bytes: *mut uint8_t,
|
||||||
wasm_bytes_len: uint32_t,
|
wasm_bytes_len: uint32_t,
|
||||||
import_object: *mut wasmer_import_object_t,
|
imports: *mut wasmer_import_t,
|
||||||
|
imports_len: c_int,
|
||||||
) -> wasmer_result_t {
|
) -> wasmer_result_t {
|
||||||
let import_object = unsafe { Box::from_raw(import_object as *mut ImportObject) };
|
|
||||||
if wasm_bytes.is_null() {
|
if wasm_bytes.is_null() {
|
||||||
update_last_error(CApiError {
|
update_last_error(CApiError {
|
||||||
msg: "wasm bytes ptr is null".to_string(),
|
msg: "wasm bytes ptr is null".to_string(),
|
||||||
});
|
});
|
||||||
return wasmer_result_t::WASMER_ERROR;
|
return wasmer_result_t::WASMER_ERROR;
|
||||||
}
|
}
|
||||||
|
let imports: &[wasmer_import_t] = slice::from_raw_parts(imports, imports_len as usize);
|
||||||
|
let mut import_object = ImportObject::new();
|
||||||
|
let mut namespaces = HashMap::new();
|
||||||
|
for import in imports {
|
||||||
|
let module_name = slice::from_raw_parts(
|
||||||
|
import.module_name.bytes,
|
||||||
|
import.module_name.bytes_len as usize,
|
||||||
|
);
|
||||||
|
let module_name = if let Ok(s) = std::str::from_utf8(module_name) {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
update_last_error(CApiError {
|
||||||
|
msg: "error converting module name to string".to_string(),
|
||||||
|
});
|
||||||
|
return wasmer_result_t::WASMER_ERROR;
|
||||||
|
};
|
||||||
|
let import_name = slice::from_raw_parts(
|
||||||
|
import.import_name.bytes,
|
||||||
|
import.import_name.bytes_len as usize,
|
||||||
|
);
|
||||||
|
let import_name = if let Ok(s) = std::str::from_utf8(import_name) {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
update_last_error(CApiError {
|
||||||
|
msg: "error converting import_name to string".to_string(),
|
||||||
|
});
|
||||||
|
return wasmer_result_t::WASMER_ERROR;
|
||||||
|
};
|
||||||
|
|
||||||
|
let namespace = namespaces
|
||||||
|
.entry(module_name)
|
||||||
|
.or_insert_with(|| Namespace::new());
|
||||||
|
|
||||||
|
let export = match import.tag {
|
||||||
|
wasmer_import_export_kind::WASM_MEMORY => import.value.memory as *mut Export,
|
||||||
|
wasmer_import_export_kind::WASM_FUNCTION => import.value.func as *mut Export,
|
||||||
|
wasmer_import_export_kind::WASM_GLOBAL => import.value.global as *mut Export,
|
||||||
|
wasmer_import_export_kind::WASM_TABLE => import.value.table as *mut Export,
|
||||||
|
};
|
||||||
|
namespace.insert(import_name, unsafe { *Box::from_raw(export) });
|
||||||
|
}
|
||||||
|
for (module_name, namespace) in namespaces.into_iter() {
|
||||||
|
import_object.register(module_name, namespace);
|
||||||
|
}
|
||||||
|
|
||||||
let bytes: &[u8] =
|
let bytes: &[u8] =
|
||||||
unsafe { ::std::slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize) };
|
unsafe { ::std::slice::from_raw_parts_mut(wasm_bytes, wasm_bytes_len as usize) };
|
||||||
let result = wasmer_runtime::instantiate(bytes, &*import_object);
|
let result = wasmer_runtime::instantiate(bytes, &import_object);
|
||||||
let new_instance = match result {
|
let new_instance = match result {
|
||||||
Ok(instance) => instance,
|
Ok(instance) => instance,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
@ -413,7 +460,7 @@ pub unsafe extern "C" fn wasmer_instantiate(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
unsafe { *instance = Box::into_raw(Box::new(new_instance)) as *mut wasmer_instance_t };
|
unsafe { *instance = Box::into_raw(Box::new(new_instance)) as *mut wasmer_instance_t };
|
||||||
Box::into_raw(import_object);
|
Box::into_raw(Box::new(import_object));
|
||||||
wasmer_result_t::WASMER_OK
|
wasmer_result_t::WASMER_OK
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +610,40 @@ pub unsafe extern "C" fn wasmer_export_kind(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates new func
|
||||||
|
///
|
||||||
|
/// The caller owns the object and should call `wasmer_func_destroy` to free it.
|
||||||
|
#[no_mangle]
|
||||||
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
|
pub unsafe extern "C" fn wasmer_func_new(
|
||||||
|
func: extern "C" fn(data: *mut c_void),
|
||||||
|
params: *const wasmer_value_tag,
|
||||||
|
params_len: c_int,
|
||||||
|
returns: *const wasmer_value_tag,
|
||||||
|
returns_len: c_int,
|
||||||
|
) -> *const wasmer_func_t {
|
||||||
|
let params: &[wasmer_value_tag] = slice::from_raw_parts(params, params_len as usize);
|
||||||
|
let params: Vec<Type> = params.iter().cloned().map(|x| x.into()).collect();
|
||||||
|
let returns: &[wasmer_value_tag] = slice::from_raw_parts(returns, returns_len as usize);
|
||||||
|
let returns: Vec<Type> = returns.iter().cloned().map(|x| x.into()).collect();
|
||||||
|
|
||||||
|
let export = Box::new(Export::Function {
|
||||||
|
func: unsafe { FuncPointer::new(func as _) },
|
||||||
|
ctx: Context::Internal,
|
||||||
|
signature: Arc::new(FuncSig::new(params, returns)),
|
||||||
|
});
|
||||||
|
Box::into_raw(export) as *mut wasmer_func_t
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frees memory for the given Func
|
||||||
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn wasmer_func_destroy(func: *mut wasmer_func_t) {
|
||||||
|
if !func.is_null() {
|
||||||
|
drop(unsafe { Box::from_raw(func as *mut Export) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets func from wasm_export
|
/// Gets func from wasm_export
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
wasmer_import_object_t *import_object = wasmer_import_object_new();
|
// wasmer_import_object_t *import_object = wasmer_import_object_new();
|
||||||
|
|
||||||
// Read the wasm file bytes
|
// Read the wasm file bytes
|
||||||
FILE *file = fopen("sum.wasm", "r");
|
FILE *file = fopen("sum.wasm", "r");
|
||||||
@ -16,8 +16,9 @@ int main()
|
|||||||
fread(bytes, 1, len, file);
|
fread(bytes, 1, len, file);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
|
wasmer_import_t imports[] = {};
|
||||||
wasmer_instance_t *instance = NULL;
|
wasmer_instance_t *instance = NULL;
|
||||||
wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, import_object);
|
wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 0);
|
||||||
printf("Compile result: %d\n", compile_result);
|
printf("Compile result: %d\n", compile_result);
|
||||||
assert(compile_result == WASMER_OK);
|
assert(compile_result == WASMER_OK);
|
||||||
|
|
||||||
@ -61,8 +62,6 @@ int main()
|
|||||||
|
|
||||||
printf("Destroy instance\n");
|
printf("Destroy instance\n");
|
||||||
wasmer_instance_destroy(instance);
|
wasmer_instance_destroy(instance);
|
||||||
printf("Destroy import object\n");
|
|
||||||
wasmer_import_object_destroy(import_object);
|
|
||||||
printf("Destroy exports\n");
|
printf("Destroy exports\n");
|
||||||
wasmer_exports_destroy(exports);
|
wasmer_exports_destroy(exports);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "../wasmer.h"
|
#include "../wasmer.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
static print_str_called = false;
|
static print_str_called = false;
|
||||||
static memory_len = 0;
|
static memory_len = 0;
|
||||||
@ -26,10 +27,32 @@ void print_str(int32_t ptr, int32_t len, wasmer_instance_context_t *ctx)
|
|||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
wasmer_import_object_t *import_object = wasmer_import_object_new();
|
// wasmer_import_object_t *import_object = wasmer_import_object_new();
|
||||||
wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32};
|
wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32};
|
||||||
wasmer_value_tag returns_sig[] = {};
|
wasmer_value_tag returns_sig[] = {};
|
||||||
wasmer_imports_set_import_func(import_object, "env", "print_str", print_str, params_sig, 2, returns_sig, 0);
|
|
||||||
|
printf("Creating new func\n");
|
||||||
|
wasmer_func_t *func = wasmer_func_new(print_str, params_sig, 2, returns_sig, 0);
|
||||||
|
wasmer_import_t import;
|
||||||
|
|
||||||
|
char *module_name = "env";
|
||||||
|
wasmer_byte_array module_name_bytes;
|
||||||
|
module_name_bytes.bytes = module_name;
|
||||||
|
module_name_bytes.bytes_len = strlen(module_name);
|
||||||
|
char *import_name = "print_str";
|
||||||
|
wasmer_byte_array import_name_bytes;
|
||||||
|
import_name_bytes.bytes = import_name;
|
||||||
|
import_name_bytes.bytes_len = strlen(import_name);
|
||||||
|
|
||||||
|
import.module_name = module_name_bytes;
|
||||||
|
import.import_name = import_name_bytes;
|
||||||
|
import.tag = WASM_FUNCTION;
|
||||||
|
import.value.func = func;
|
||||||
|
wasmer_import_t imports[] = {import};
|
||||||
|
|
||||||
|
|
||||||
|
// wasmer_imports_set_import_func(import_object, "env", "print_str", print_str, params_sig, 2, returns_sig, 0);
|
||||||
|
|
||||||
|
|
||||||
// Read the wasm file bytes
|
// Read the wasm file bytes
|
||||||
FILE *file = fopen("wasm_sample_app.wasm", "r");
|
FILE *file = fopen("wasm_sample_app.wasm", "r");
|
||||||
@ -40,9 +63,15 @@ int main()
|
|||||||
fread(bytes, 1, len, file);
|
fread(bytes, 1, len, file);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
|
printf("Instantiating\n");
|
||||||
wasmer_instance_t *instance = NULL;
|
wasmer_instance_t *instance = NULL;
|
||||||
wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, import_object);
|
wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 1);
|
||||||
printf("Compile result: %d\n", compile_result);
|
printf("Compile result: %d\n", compile_result);
|
||||||
|
int error_len = wasmer_last_error_length();
|
||||||
|
printf("Error len: `%d`\n", error_len);
|
||||||
|
char *error_str = malloc(error_len);
|
||||||
|
wasmer_last_error_message(error_str, error_len);
|
||||||
|
printf("Error str: `%s`\n", error_str);
|
||||||
assert(compile_result == WASMER_OK);
|
assert(compile_result == WASMER_OK);
|
||||||
|
|
||||||
wasmer_value_t params[] = {};
|
wasmer_value_t params[] = {};
|
||||||
@ -56,9 +85,11 @@ int main()
|
|||||||
assert(ptr_len == 13);
|
assert(ptr_len == 13);
|
||||||
assert(0 == strcmp(actual_str, "Hello, World!"));
|
assert(0 == strcmp(actual_str, "Hello, World!"));
|
||||||
|
|
||||||
|
printf("Destroying func\n");
|
||||||
|
// wasmer_func_destroy(func);
|
||||||
// printf("Destroy instance\n");
|
// printf("Destroy instance\n");
|
||||||
// wasmer_instance_destroy(instance);
|
// wasmer_instance_destroy(instance);
|
||||||
printf("Destroy import object\n");
|
// printf("Destroy import object\n");
|
||||||
wasmer_import_object_destroy(import_object);
|
// wasmer_import_object_destroy(import_object);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
wasmer_import_object_t *import_object = wasmer_import_object_new();
|
|
||||||
|
|
||||||
// Read the wasm file bytes
|
// Read the wasm file bytes
|
||||||
FILE *file = fopen("sum.wasm", "r");
|
FILE *file = fopen("sum.wasm", "r");
|
||||||
@ -16,8 +15,9 @@ int main()
|
|||||||
fread(bytes, 1, len, file);
|
fread(bytes, 1, len, file);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
|
wasmer_import_t imports[] = {};
|
||||||
wasmer_instance_t *instance = NULL;
|
wasmer_instance_t *instance = NULL;
|
||||||
wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, import_object);
|
wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 0);
|
||||||
printf("Compile result: %d\n", compile_result);
|
printf("Compile result: %d\n", compile_result);
|
||||||
assert(compile_result == WASMER_OK);
|
assert(compile_result == WASMER_OK);
|
||||||
|
|
||||||
@ -53,7 +53,5 @@ int main()
|
|||||||
|
|
||||||
printf("Destroy instance\n");
|
printf("Destroy instance\n");
|
||||||
wasmer_instance_destroy(instance);
|
wasmer_instance_destroy(instance);
|
||||||
printf("Destroy import object\n");
|
|
||||||
wasmer_import_object_destroy(import_object);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
@ -75,22 +75,36 @@ typedef struct {
|
|||||||
|
|
||||||
} wasmer_memory_t;
|
} wasmer_memory_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} wasmer_table_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
const wasmer_func_t *func;
|
||||||
|
const wasmer_table_t *table;
|
||||||
|
const wasmer_memory_t *memory;
|
||||||
|
const wasmer_global_t *global;
|
||||||
|
} wasmer_import_export_value;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
wasmer_byte_array module_name;
|
||||||
|
wasmer_byte_array import_name;
|
||||||
|
wasmer_import_export_kind tag;
|
||||||
|
wasmer_import_export_value value;
|
||||||
|
} wasmer_import_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t min;
|
uint32_t min;
|
||||||
uint32_t max;
|
uint32_t max;
|
||||||
} wasmer_limits_t;
|
} wasmer_limits_t;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
} wasmer_table_t;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets wasmer_export kind
|
* Gets wasmer_export kind
|
||||||
*/
|
*/
|
||||||
wasmer_import_export_kind wasmer_export_kind(wasmer_export_t *export_);
|
wasmer_import_export_kind wasmer_export_kind(wasmer_export_t *export_);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets func from wasm_export
|
* Gets name from wasmer_export
|
||||||
*/
|
*/
|
||||||
wasmer_byte_array wasmer_export_name(wasmer_export_t *export_);
|
wasmer_byte_array wasmer_export_name(wasmer_export_t *export_);
|
||||||
|
|
||||||
@ -127,6 +141,21 @@ wasmer_result_t wasmer_func_call(wasmer_func_t *func,
|
|||||||
wasmer_value_t *results,
|
wasmer_value_t *results,
|
||||||
int results_len);
|
int results_len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory for the given Func
|
||||||
|
*/
|
||||||
|
void wasmer_func_destroy(wasmer_func_t *func);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new func
|
||||||
|
* The caller owns the object and should call `wasmer_func_destroy` to free it.
|
||||||
|
*/
|
||||||
|
const wasmer_func_t *wasmer_func_new(void (*func)(void *data),
|
||||||
|
const wasmer_value_tag *params,
|
||||||
|
int params_len,
|
||||||
|
const wasmer_value_tag *returns,
|
||||||
|
int returns_len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees memory for the given Global
|
* Frees memory for the given Global
|
||||||
*/
|
*/
|
||||||
@ -220,7 +249,8 @@ void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exp
|
|||||||
wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance,
|
wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance,
|
||||||
uint8_t *wasm_bytes,
|
uint8_t *wasm_bytes,
|
||||||
uint32_t wasm_bytes_len,
|
uint32_t wasm_bytes_len,
|
||||||
wasmer_import_object_t *import_object);
|
wasmer_import_t *imports,
|
||||||
|
int imports_len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the length in bytes of the last error.
|
* Gets the length in bytes of the last error.
|
||||||
|
@ -72,21 +72,35 @@ struct wasmer_memory_t {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct wasmer_table_t {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
union wasmer_import_export_value {
|
||||||
|
const wasmer_func_t *func;
|
||||||
|
const wasmer_table_t *table;
|
||||||
|
const wasmer_memory_t *memory;
|
||||||
|
const wasmer_global_t *global;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wasmer_import_t {
|
||||||
|
wasmer_byte_array module_name;
|
||||||
|
wasmer_byte_array import_name;
|
||||||
|
wasmer_import_export_kind tag;
|
||||||
|
wasmer_import_export_value value;
|
||||||
|
};
|
||||||
|
|
||||||
struct wasmer_limits_t {
|
struct wasmer_limits_t {
|
||||||
uint32_t min;
|
uint32_t min;
|
||||||
uint32_t max;
|
uint32_t max;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct wasmer_table_t {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
/// Gets wasmer_export kind
|
/// Gets wasmer_export kind
|
||||||
wasmer_import_export_kind wasmer_export_kind(wasmer_export_t *export_);
|
wasmer_import_export_kind wasmer_export_kind(wasmer_export_t *export_);
|
||||||
|
|
||||||
/// Gets func from wasm_export
|
/// Gets name from wasmer_export
|
||||||
wasmer_byte_array wasmer_export_name(wasmer_export_t *export_);
|
wasmer_byte_array wasmer_export_name(wasmer_export_t *export_);
|
||||||
|
|
||||||
/// Gets func from wasm_export
|
/// Gets func from wasm_export
|
||||||
@ -112,6 +126,17 @@ wasmer_result_t wasmer_func_call(wasmer_func_t *func,
|
|||||||
wasmer_value_t *results,
|
wasmer_value_t *results,
|
||||||
int results_len);
|
int results_len);
|
||||||
|
|
||||||
|
/// Frees memory for the given Func
|
||||||
|
void wasmer_func_destroy(wasmer_func_t *func);
|
||||||
|
|
||||||
|
/// Creates new func
|
||||||
|
/// The caller owns the object and should call `wasmer_func_destroy` to free it.
|
||||||
|
const wasmer_func_t *wasmer_func_new(void (*func)(void *data),
|
||||||
|
const wasmer_value_tag *params,
|
||||||
|
int params_len,
|
||||||
|
const wasmer_value_tag *returns,
|
||||||
|
int returns_len);
|
||||||
|
|
||||||
/// Frees memory for the given Global
|
/// Frees memory for the given Global
|
||||||
void wasmer_global_destroy(wasmer_global_t *global);
|
void wasmer_global_destroy(wasmer_global_t *global);
|
||||||
|
|
||||||
@ -179,7 +204,8 @@ void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exp
|
|||||||
wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance,
|
wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance,
|
||||||
uint8_t *wasm_bytes,
|
uint8_t *wasm_bytes,
|
||||||
uint32_t wasm_bytes_len,
|
uint32_t wasm_bytes_len,
|
||||||
wasmer_import_object_t *import_object);
|
wasmer_import_t *imports,
|
||||||
|
int imports_len);
|
||||||
|
|
||||||
/// Gets the length in bytes of the last error.
|
/// Gets the length in bytes of the last error.
|
||||||
/// This can be used to dynamically allocate a buffer with the correct number of
|
/// This can be used to dynamically allocate a buffer with the correct number of
|
||||||
|
Reference in New Issue
Block a user