D
Deno

help

Pass string from TS to dll and return the passed string. (Like echo)

MMqx5/1/2023
Hey I am currently trying to understand the communication between a dll and my TS program. Currently I am simply trying to pass a string to the dll and have the dll give me the string back. Similar to an echo. I have been trying to follow this guide for this: https://medium.com/deno-the-complete-reference/calling-c-functions-from-windows-dll-in-deno-part-2-buffers-131226acd3d2 only somehow I can't pass the buffer in my TS program. Can someone give me a simple example of how to pass a string from TS to dll and from dll to TS?
const dll = Deno.dlopen('./test.dll', {
'echo': {
parameters: ['pointer'],
result: 'pointer'
}
})

const buffer = new TextEncoder().encode('Hello World!')

console.log('echo:', dll.symbols.echo(buffer));

dll.close()
const dll = Deno.dlopen('./test.dll', {
'echo': {
parameters: ['pointer'],
result: 'pointer'
}
})

const buffer = new TextEncoder().encode('Hello World!')

console.log('echo:', dll.symbols.echo(buffer));

dll.close()
Medium
Calling C functions from Windows DLL in Denoβ€Šβ€”β€ŠPart 2 Buffers
Learn how to call C functions from Windows DLL in Deno using arbitrary buffers
AAapoAlas5/1/2023
Change your parameter type to "buffer" to have the function take a Uint8Array: "pointer" expects a pointer object (Deno.PointerObject). In return values buffer and pointer are the same thing. Aside from that you'll need a null character at the end of the string: Otherwise C cannot know where your string ends. (Though this depends on the API of course.)
MMqx5/1/2023
Ahh okay thanks a lot I tried it before using a buffer but I forgot the Null character So I need to use a buffer as Argument and as return value
AAapoAlas5/1/2023
Return value can be either: there's no difference between the two for return values. You can kind of think of buffer and pointer like this: A buffer is (generally) memory owned by JavaScript, so backed by an ArrayBuffer. A pointer is (generally) memory owned by the foreign library. With return values we just allow both for... Convenience? Confusion? But they both just mean "pointer".
MMqx5/1/2023
okay now I am getting an empty object. How can I get the string from the function? I have changed the code to this:
const dll = Deno.dlopen('./build/test.dll', {
'echo': {
parameters: ['buffer'],
result: 'buffer'
}
})

const buffer = new TextEncoder().encode('Hello World!\0')

console.log('echo:', dll.symbols.echo(buffer));

dll.close()
const dll = Deno.dlopen('./build/test.dll', {
'echo': {
parameters: ['buffer'],
result: 'buffer'
}
})

const buffer = new TextEncoder().encode('Hello World!\0')

console.log('echo:', dll.symbols.echo(buffer));

dll.close()
My test.h looks like this:
#pragma once
#ifdef TEST_EXPORTS
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif

extern "C" DLLEXPORT char *echo(void *data);
#pragma once
#ifdef TEST_EXPORTS
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllimport)
#endif

extern "C" DLLEXPORT char *echo(void *data);
And my test.cpp looks like this:
#include "test.h"

char* echo(void *data) {
return (char*) data;
}
#include "test.h"

char* echo(void *data) {
return (char*) data;
}
CMakeLists.txt looks like this:
cmake_minimum_required(VERSION 3.10)

set(PROJECT_N test)
project(${PROJECT_N} VERSION 1.0)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

set(LIB true)

file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp)

# a macro that gets all of the header containing directories.
MACRO(header_directories return_list includes_base_folder extention )
FILE(GLOB_RECURSE new_list ${includes_base_folder}/*.${extention})
SET(dir_list "")
FOREACH(file_path ${new_list})
GET_FILENAME_COMPONENT(dir_path ${file_path} PATH)
SET(dir_list ${dir_list} ${dir_path})
ENDFOREACH()
LIST(REMOVE_DUPLICATES dir_list)
SET(${return_list} ${dir_list})
ENDMACRO()

# using said macro.
header_directories(INCLUDES ${PROJECT_SOURCE_DIR}/include/ hpp)

message("src files:")
foreach(file ${SRCS})
message(STATUS ${file})
endforeach()

message("include directories:")
foreach(dir ${INCLUDES})
message(STATUS ${dir})
endforeach()

if(LIB)
add_library(${PROJECT_N} SHARED ${SRCS})
else()
add_executable(${PROJECT_N} ${SRCS})
endif(LIB)

target_include_directories(${PROJECT_N} PUBLIC include)
cmake_minimum_required(VERSION 3.10)

set(PROJECT_N test)
project(${PROJECT_N} VERSION 1.0)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

set(LIB true)

file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp)

# a macro that gets all of the header containing directories.
MACRO(header_directories return_list includes_base_folder extention )
FILE(GLOB_RECURSE new_list ${includes_base_folder}/*.${extention})
SET(dir_list "")
FOREACH(file_path ${new_list})
GET_FILENAME_COMPONENT(dir_path ${file_path} PATH)
SET(dir_list ${dir_list} ${dir_path})
ENDFOREACH()
LIST(REMOVE_DUPLICATES dir_list)
SET(${return_list} ${dir_list})
ENDMACRO()

# using said macro.
header_directories(INCLUDES ${PROJECT_SOURCE_DIR}/include/ hpp)

message("src files:")
foreach(file ${SRCS})
message(STATUS ${file})
endforeach()

message("include directories:")
foreach(dir ${INCLUDES})
message(STATUS ${dir})
endforeach()

if(LIB)
add_library(${PROJECT_N} SHARED ${SRCS})
else()
add_executable(${PROJECT_N} ${SRCS})
endif(LIB)

target_include_directories(${PROJECT_N} PUBLIC include)
AAapoAlas5/1/2023
Deno.UnsafePointerView.getCString(pointer)
MMqx5/1/2023
Okay thanks I will try that This does not work:
const dll = Deno.dlopen('./build/test.dll', {
'echo': {
parameters: ['buffer'],
result: 'buffer'
}
})

const buffer = new TextEncoder().encode('Hello World!\0')

const result = dll.symbols.echo(buffer)

const dataView = Deno.UnsafePointerView.getCString(result)

console.log('echo:', dataView);

dll.close()
const dll = Deno.dlopen('./build/test.dll', {
'echo': {
parameters: ['buffer'],
result: 'buffer'
}
})

const buffer = new TextEncoder().encode('Hello World!\0')

const result = dll.symbols.echo(buffer)

const dataView = Deno.UnsafePointerView.getCString(result)

console.log('echo:', dataView);

dll.close()
Argument of type 'PointerValue' is not assignable to parameter of type 'PointerObject'.
Type 'null' is not assignable to type 'PointerObject'.
Argument of type 'PointerValue' is not assignable to parameter of type 'PointerObject'.
Type 'null' is not assignable to type 'PointerObject'.
AAapoAlas5/1/2023
So that's a type error, not a runtime error. It's telling you that echo returns null | Deno.PointerObject but Deno.UnsafePointerView.getCString takes only Deno.PointerObject. null stands for a null pointer (0) which is not a valid pointer to read a string from so it's not a valid parameter value for getCString. If this were production code, you'd need to check that the pointer you receive from echo is not null before you try to read a string from it. In this case, however, you can just do getCString(result!) to let TypeScript know you're sure result is non-null.
MMqx5/1/2023
Ahh okay thanks I try that. Is this the official good way to do that? Or are there better methods? For passing a string from the dll to TS?
AAapoAlas5/1/2023
This is exactly the way to do it. There is no other way, at least at present. "A string" is not really all that simple a thing, after all. "How to pass a string from TypeScript to C" is a really complicated question, not least because "TypeScript" doesn't really have a set string format but instead it depends on the JavaScript engine running the code. For Deno specifically, V8 has two different internal string representations. And then on C side there is no such thing as a "string", really. Instead it depends on the library etc. on what a "string" truly is. But for C strings at least we have the common ground with TextEncoder() and char *.
MMqx5/1/2023
Okay. Thanks for your support! I appreciate it!
AAapoAlas5/1/2023
No problem. By the way, be sure to check out Denonomicon: https://denonomicon.deno.dev/ It's a sort of in-depth look into what FFI does and how it does it. Written by me, so expect errors but it should still be helpful πŸ™‚
MMqx5/1/2023
Thanks a lot! I did not know these exists. Thanks for sharing! Okay I can now successfully receive data from the dll. Only problem that I now have is that the data I receive from the device that is connected to the dll is somehow gibberish xD I am currently trying to talk to my arduino.
AAapoAlas5/1/2023
Is the C library written by yourself or is it some known library?
MMqx5/1/2023
My friend did write it but it is all self written. No third party lib.
AAapoAlas5/1/2023
Hm, okay. One possibility is always that the char * pointer you're using doesn't have a null character at the end which then leads to some valid data followed by some invalid gibberish. Another possibility is that you accidentally mixed up a char * pointer and some other pointer and are thus reading essentially random data as strings.
MMqx5/1/2023
Its currently a mess xD
AAapoAlas5/1/2023
πŸ™‚
MMqx5/1/2023
Thats all on the arduino side:
void setup() {
Serial.begin(9600);
Serial.println("Hello World!");
}

void loop() {
}
void setup() {
Serial.begin(9600);
Serial.println("Hello World!");
}

void loop() {
}
MMqx5/1/2023
And I get something like this xD
AAapoAlas5/1/2023
Yeah okay, that seems ... seems like it's missing the null byte at the end of the read: 1024 message maybe?
MMqx5/1/2023
I tried sending it from the arduino... same result You mean like this:
Serial.print("Hello World!\0");
Serial.print("Hello World!\0");
?
AAapoAlas5/1/2023
I mean this. I presume this is you receiving a pointer from the library and then doing something like console.log(Deno.UnsafePointerView.getCString(pointer)).
MMqx5/1/2023
no no this is the log.log file from the c++ code It gets already either wrong send by the arduino or wrong received by the c++ code I go to bed for today I'll be back tomorrow Thanks for all the help!
AAapoAlas5/1/2023
No problem.
UUUnknown User5/1/2023
2 Messages Not Public
Sign In & Join Server To View
AAapoAlas5/1/2023
OneByte and UTF-16, isn't it? Though I guess there's also OneByteExternal and UTF-16 External. Anything else?
UUUnknown User5/1/2023
3 Messages Not Public
Sign In & Join Server To View
MMqx5/6/2023
@aapoalas Okay we have looked at the whole thing again, however the data from the Arduino is still being received incorrectly.
MMqx5/6/2023
This is currently the way we read and write from the com port to the log.log file
MMqx5/6/2023
log.log looks like this. So far so good but after a while we got gibberish
MMqx5/6/2023
Some times the gibberish also starts at the beginning I know this is more a C++ problem than it is a deno problem. But I hope someone can still help. This is how I open and call a function in deno:
const dll = Deno.dlopen('./build/serialport.dll', {
'open': {
parameters: ['buffer', 'i32', 'i32', 'i32', 'i32'],
result: 'i32'
},
'Read': {
parameters: ['i32'],
result: 'buffer'
},
'ReadLine': {
parameters: [],
result: 'buffer',

}
})

// const buffer = new TextEncoder().encode('Hello World!\0')
// dll.symbols.logMe(new TextEncoder().encode('COM5\0'), 9600, 8, 1, 1);
console.log(dll.symbols.open(new TextEncoder().encode('COM5\0'), 9600, 8, 0, 0));
const line = dll.symbols.Read(1024);

const dataView = Deno.UnsafePointerView.getCString(line!)

console.log('echo:', dataView);

dll.close()
const dll = Deno.dlopen('./build/serialport.dll', {
'open': {
parameters: ['buffer', 'i32', 'i32', 'i32', 'i32'],
result: 'i32'
},
'Read': {
parameters: ['i32'],
result: 'buffer'
},
'ReadLine': {
parameters: [],
result: 'buffer',

}
})

// const buffer = new TextEncoder().encode('Hello World!\0')
// dll.symbols.logMe(new TextEncoder().encode('COM5\0'), 9600, 8, 1, 1);
console.log(dll.symbols.open(new TextEncoder().encode('COM5\0'), 9600, 8, 0, 0));
const line = dll.symbols.Read(1024);

const dataView = Deno.UnsafePointerView.getCString(line!)

console.log('echo:', dataView);

dll.close()
MMqx5/6/2023
MMqx5/6/2023
I get a 0 so the opening works without a problem.
AAapoAlas5/6/2023
Sorry, I don't know too much about reading from Arduino 😦
MMqx5/6/2023
No problem we found the error πŸ‘ But now I have an other question. Haw can I pass a string buffer to the dll so that the dll can modify the buffer? Similar to the Deno.read(buffer) function Currently I have this:
const buffer : Uint8Array = new Uint8Array(100)

const errorCode = dll.symbols.readLine(buffer, buffer.length, 1000);

console.log(errorCode, 'echo:', new TextDecoder().decode(buffer));
const buffer : Uint8Array = new Uint8Array(100)

const errorCode = dll.symbols.readLine(buffer, buffer.length, 1000);

console.log(errorCode, 'echo:', new TextDecoder().decode(buffer));
AAapoAlas5/6/2023
Yeah, that seems, at its core, exactly correct: You prepare a buffer, send it to the lib with a length to let the lib know how much it can write to it. The lib gets a void* and eg. u32 length, and turns that into something like uint8_t[len]. Then just write into it.
MMqx5/6/2023
Okay I'll try that Currently I do this in the readLine() function:
auto readLine(void* buffer, const int bufferSize, const int timeout) -> int {

if (hSerialPort == INVALID_HANDLE_VALUE) {
return static_cast<int>(ErrorCodes::INVALID);
}

timeouts.ReadIntervalTimeout = timeout;
timeouts.ReadTotalTimeoutConstant = timeout;
timeouts.ReadTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hSerialPort, &timeouts)) {
return static_cast<int>(ErrorCodes::CONFIGURATION_SET);
}

data = "";

for (int i{0}; i < bufferSize && data.find('\n') == std::string::npos; i++) {
DWORD dwBytesRead;
char bufferChar[1];
ReadFile(hSerialPort, bufferChar, sizeof(bufferChar), &dwBytesRead, NULL);

if (dwBytesRead == 0) {
break;
}
data.append(std::string(bufferChar, dwBytesRead));
}

memcpy(&buffer, data.c_str(), data.length() + 1);

return 0;
}
auto readLine(void* buffer, const int bufferSize, const int timeout) -> int {

if (hSerialPort == INVALID_HANDLE_VALUE) {
return static_cast<int>(ErrorCodes::INVALID);
}

timeouts.ReadIntervalTimeout = timeout;
timeouts.ReadTotalTimeoutConstant = timeout;
timeouts.ReadTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hSerialPort, &timeouts)) {
return static_cast<int>(ErrorCodes::CONFIGURATION_SET);
}

data = "";

for (int i{0}; i < bufferSize && data.find('\n') == std::string::npos; i++) {
DWORD dwBytesRead;
char bufferChar[1];
ReadFile(hSerialPort, bufferChar, sizeof(bufferChar), &dwBytesRead, NULL);

if (dwBytesRead == 0) {
break;
}
data.append(std::string(bufferChar, dwBytesRead));
}

memcpy(&buffer, data.c_str(), data.length() + 1);

return 0;
}
But that does not seem to modify the buffer on my ts side
AAapoAlas5/6/2023
Hmm... I'm not sure but I think `&buffer might now be a pointer to a pointer. ie. Turning &buffer to just buffer might fix it.
MMqx5/6/2023
at wich line?
AAapoAlas5/6/2023
memcpy(&buffer, ....
MMqx5/6/2023
Thanks now it prints something!
AAapoAlas5/6/2023
Performance wise, I also think it'd probably be possible to read directly into the buffer with
ReadFile(hSerialPort, buffer, bufferSize, &dwBytesRead, NULL);
ReadFile(hSerialPort, buffer, bufferSize, &dwBytesRead, NULL);
MMqx5/6/2023
Okay but how can I check for the \n when using the ReadFile ☝🏼 Also this way I read in the full buffer and I only want to read to the \n How about this:
auto readLine(void* buffer, const int bufferSize, const int timeout) -> int {

if (hSerialPort == INVALID_HANDLE_VALUE) {
return static_cast<int>(ErrorCodes::INVALID);
}

timeouts.ReadIntervalTimeout = timeout;
timeouts.ReadTotalTimeoutConstant = timeout;
timeouts.ReadTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hSerialPort, &timeouts)) {
return static_cast<int>(ErrorCodes::CONFIGURATION_SET);
}

data = "";

for (int i{0}; i < bufferSize; i++) {
DWORD dwBytesRead;
char bufferChar[1];
ReadFile(hSerialPort, bufferChar, sizeof(bufferChar), &dwBytesRead, NULL);

if (dwBytesRead == 0 || bufferChar[0] == '\n') {
break;
}
static_cast<char*>(buffer)[i] = bufferChar[0];
}

return 0;
}
auto readLine(void* buffer, const int bufferSize, const int timeout) -> int {

if (hSerialPort == INVALID_HANDLE_VALUE) {
return static_cast<int>(ErrorCodes::INVALID);
}

timeouts.ReadIntervalTimeout = timeout;
timeouts.ReadTotalTimeoutConstant = timeout;
timeouts.ReadTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hSerialPort, &timeouts)) {
return static_cast<int>(ErrorCodes::CONFIGURATION_SET);
}

data = "";

for (int i{0}; i < bufferSize; i++) {
DWORD dwBytesRead;
char bufferChar[1];
ReadFile(hSerialPort, bufferChar, sizeof(bufferChar), &dwBytesRead, NULL);

if (dwBytesRead == 0 || bufferChar[0] == '\n') {
break;
}
static_cast<char*>(buffer)[i] = bufferChar[0];
}

return 0;
}
We have tried again a little bit and have discovered that the Arduinos (Mega/Uno) with which I have worked behaved differently when sending the data. Now everything works so far :D Thanks a lot @aapoalas !
AAapoAlas5/6/2023
Glad to hear it. You're welcome! I'm always happy to help with FFI stuff πŸ™‚
MMqx5/14/2023
πŸ‘πŸ» @aapoalas Hey I have an other question. Is it possible to return an array from the dll? And if it is, how can I work with this in TS? I want to return a string array from the dll
AAapoAlas5/14/2023
An array of strings can be returned as a pointer that points to an array (vector) of pointers that each point to a string. Reading through it requires using reading pointers through a pointer and then reading string through that... I think.
MMqx5/14/2023
Sounds to complicated. xD I think we will build a string and than parse that string.
Jjavi5/15/2023
Here's how I would do it. Seems complicated, it is not that complicated. As Aapo said, return a pointer to the array, and then dereference and access elements one by one. This C program just takes a string and splits it, doing a lot of assumptions.
// split.c -> split a string into elements by delim
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

size_t count_delim(char const *string, char const delim)
{
size_t length = 0;

while (*string) {
if (*string++ == delim)
length++;
}
return length;
}

char **split(char const *string, char const delim)
{
char *pool = NULL;
char **result = NULL;
char const *aux_str = string;

size_t elem_count = 0;
size_t max_elements = 0;
size_t string_len = strlen(string);

if (!string) {
perror("no string");
return NULL;
}

max_elements = count_delim(string, delim);

if (!((result = malloc(sizeof(*result) * (max_elements + 2))))) {
perror("no result");
return NULL;
}
if (!((pool = malloc(string_len + 1)))) {
free(result);
perror("no pool");
return NULL;
}

while (*string) {
if (*string == delim || *(string + 1) == '\0') {
size_t substring_len =
string - aux_str + ((*string == delim) ? 0 : 1);
strncpy(pool, aux_str, substring_len);
*(pool + substring_len) = '\0';
*(result + elem_count++) = pool;
pool = pool + substring_len + 1;
aux_str = ++string;
} else {
string++;
}
}

*(result + elem_count) = NULL;
return result;
}
// split.c -> split a string into elements by delim
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

size_t count_delim(char const *string, char const delim)
{
size_t length = 0;

while (*string) {
if (*string++ == delim)
length++;
}
return length;
}

char **split(char const *string, char const delim)
{
char *pool = NULL;
char **result = NULL;
char const *aux_str = string;

size_t elem_count = 0;
size_t max_elements = 0;
size_t string_len = strlen(string);

if (!string) {
perror("no string");
return NULL;
}

max_elements = count_delim(string, delim);

if (!((result = malloc(sizeof(*result) * (max_elements + 2))))) {
perror("no result");
return NULL;
}
if (!((pool = malloc(string_len + 1)))) {
free(result);
perror("no pool");
return NULL;
}

while (*string) {
if (*string == delim || *(string + 1) == '\0') {
size_t substring_len =
string - aux_str + ((*string == delim) ? 0 : 1);
strncpy(pool, aux_str, substring_len);
*(pool + substring_len) = '\0';
*(result + elem_count++) = pool;
pool = pool + substring_len + 1;
aux_str = ++string;
} else {
string++;
}
}

*(result + elem_count) = NULL;
return result;
}
And after buildind the lib (can provide the Makefile if needed), here's the typescript code
const extension: { [k in typeof Deno.build.os]?: string } = {
darwin: "dylib",
linux: "so",
windows: "dll",
};

const sizeof_pointer = 8; // x64

if (!extension[Deno.build.os]) {
throw new Error("Invalid `OS`");
}

const lib = Deno.dlopen(`./libexample.${extension[Deno.build.os]}`, {
split: {
parameters: ["buffer", "i8"],
result: "pointer",
},
free: {
parameters: ["pointer"],
result: "void",
} as const,
});

const buff = new TextEncoder().encode("This will be split inside C!" + "\0");
const delim = " ".charCodeAt(0);

const result = lib.symbols.split(buff, delim);

if (!result) {
throw new Error("<split> returned NULL");
}

const result_view = new Deno.UnsafePointerView(result);

for (let i = 0;; i += sizeof_pointer) {
const str_pointer = result_view.getPointer(i);
if (!str_pointer) break;
console.log(
`-> %s`,
Deno.UnsafePointerView.getCString(str_pointer),
);
}

lib.symbols.free(result_view.getPointer());
lib.symbols.free(result);

lib.close();
const extension: { [k in typeof Deno.build.os]?: string } = {
darwin: "dylib",
linux: "so",
windows: "dll",
};

const sizeof_pointer = 8; // x64

if (!extension[Deno.build.os]) {
throw new Error("Invalid `OS`");
}

const lib = Deno.dlopen(`./libexample.${extension[Deno.build.os]}`, {
split: {
parameters: ["buffer", "i8"],
result: "pointer",
},
free: {
parameters: ["pointer"],
result: "void",
} as const,
});

const buff = new TextEncoder().encode("This will be split inside C!" + "\0");
const delim = " ".charCodeAt(0);

const result = lib.symbols.split(buff, delim);

if (!result) {
throw new Error("<split> returned NULL");
}

const result_view = new Deno.UnsafePointerView(result);

for (let i = 0;; i += sizeof_pointer) {
const str_pointer = result_view.getPointer(i);
if (!str_pointer) break;
console.log(
`-> %s`,
Deno.UnsafePointerView.getCString(str_pointer),
);
}

lib.symbols.free(result_view.getPointer());
lib.symbols.free(result);

lib.close();
MMqx5/15/2023
Thanks for sharing! I will look into this πŸ‘πŸ»
Jjavi5/15/2023
welcome <:cookie_deno:1002977285734932480>
MMqx5/28/2023
Hey I have an other question. Is it possible to pass a callback function from typescript to c++ and then call the function in c++ and return it with parameters? To make it more clear, I want to pass for example this error function to c++:
const dl = Deno.dlopen('path/to/dll', {
'error': {
parameters: ['function'],
result: 'void'
}
})
const dl = Deno.dlopen('path/to/dll', {
'error': {
parameters: ['function'],
result: 'void'
}
})
In c++ I then want to call this function with parameters (code, message) so that I can receive these parameters in typescript:
dl.symbold.error((code, message) => {
console.log(code, message);
});
dl.symbold.error((code, message) => {
console.log(code, message);
});
Is this possible and if so how can I implement this?
AAapoAlas5/28/2023
Yes, what you're looking for is Deno.UnsafeCallback. Something like this then:
const cstringT = "buffer";
const func = (_: unknown) => "function";
const ErrorCallbackDefinition = {
parameters: ["u32", cstringT],
result: "void",
} as const;

const dl = Deno.dlopen("path/to/dll", {
error: {
parameters: [func(ErrorCallbackDefinition)],
result: "void",
},
});

const CB = new Deno.UnsafeCallback(ErrorCallbackDefinition, (code, message) => console.log(code, Deno.UnsafePointerView.getCString(message)));

dl.symbold.error(CB.pointer);
const cstringT = "buffer";
const func = (_: unknown) => "function";
const ErrorCallbackDefinition = {
parameters: ["u32", cstringT],
result: "void",
} as const;

const dl = Deno.dlopen("path/to/dll", {
error: {
parameters: [func(ErrorCallbackDefinition)],
result: "void",
},
});

const CB = new Deno.UnsafeCallback(ErrorCallbackDefinition, (code, message) => console.log(code, Deno.UnsafePointerView.getCString(message)));

dl.symbold.error(CB.pointer);
(I added a bunch of "intermediate type definitions" like cstringT, func() and ErrorCallbackDefinition because I like having those visible from the symbol definitions even if they do not, unfortunately, end up in the TypeScript typings.)
MMqx5/28/2023
Okay how does my c++ code need to look like?
AAapoAlas5/28/2023
Eeh, I'm not too great with C++ but something like this:
extern "C" void error(void (*callback)(u32 code, char* message)) {
callback(3, 'i think this makes a c string?\0');
}
extern "C" void error(void (*callback)(u32 code, char* message)) {
callback(3, 'i think this makes a c string?\0');
}
(Not sure if you actually need to \0 on C++ side...)
MMqx5/28/2023
Thanks it works!
AAapoAlas5/28/2023
There's some more callback stuff in this video as well: https://www.youtube.com/watch?v=kk9PMDJL_10 And Denonomicon goes into a bit more in-depth discussion about the underlying code and its requirements: https://denonomicon.deno.dev/callbacks
Deno
YouTube
WTF is FFI? Pt. 2 β€” Managing threads and asynchronicity by creating...
Foreign function interface, or "FFI", is a way to access data and call functions from native dynamic libraries (like C, C++, and Rust). In this episode, we create an RSA function using Deno and C. In so doing, we explore approaches towards writing synchronous and asynchronous functions while using FFI. 1:00: Defining is_prime function 9:38: ...
MMqx5/28/2023
πŸ‘πŸ» Thanks man!
AAapoAlas5/28/2023
np
MMqx5/28/2023
What is the best way to modify a buffer? Currently I am passing a bufferSource (Uint8Array) and as a separate parameter the size of the buffer to the c++ function and than I modify the buffer in the c++ function. Is there a better way to do this? Can I pass a buffer without the size as a separate argument?
AAapoAlas5/28/2023
There's nothing saying that you must pass a size parameter to your function but if the size of the buffer isn't static then yeah you'll either have to pass a size parameter or alternatively your buffer will somehow have to be able to tell where it ends. Usually this would be a null byte (\0) but that's of course not always an option. ie. Sending a buffer (Uint8Array) and a size parameter is pretty much the best there is in the general case.
MMqx5/29/2023
Okay Hey I have a question about relative paths. I have a windows.dll in my repository under "root of the repo"/ts/bin/windows.dll which I try to load over my Serial.ts which is under "root of the repo"/ts/Serial.ts. When I try to load the dll via the following relative path then deno doesn't seem to find it: ./bin/windows.dll. What am I doing wrong? I think that the relative path is somehow wrong. but how can i reference the file?
AAapoAlas5/30/2023
Deno.dlopen(new URL("./ts/bin/windows.dll", import.meta.url), { ... })
MMqx5/30/2023
Does also not work πŸ˜• But I will try again today
AAapoAlas5/30/2023
That... I have somewhat hard a time believing. The dlopen does accept URLs and it should believe a properly formed URL for where to load from.
MMqx5/30/2023
Okay
AAapoAlas5/30/2023
But yeah, best try it and maybe log the URL to check that it looks reasonable.
MMqx5/30/2023
Yea Thanks
AAapoAlas5/30/2023
np

Looking for more? Join the community!

Recommended Posts
deno-deploy limits on file storage?When deploying static files via deployctl, what are the storage limits / costs per GB? Couldn't findDoes anyone have a working OpenCL deno example?How can I use opencl with deno? Any examples?absolute imports in fresh?how do you get this accomplished?PhpStorm not resolving local import with import_map.jsonDeno Version : deno 1.33.0 (release, aarch64-apple-darwin) v8 11.4.183.1 typescript 5.0.3 PhpStorm how to cancel test watchon linux. ran `deno test -A --watch tools/json_tools/examples/*.ts` and `ctrl+c` does not seems to Sentry DenoOsUptime is not a function issueI'm using `npm:@sentry/node@7.49.0` and the following script to test the integration ```TypeScript /Property 'openKv' does not exist on type 'typeof Deno'. VSCodeSuch as the title say, I cant get the LSP to recognize openKv in the Deno namespace... deno 1.32.5+Import from direct URL works fine, but NOT via deps.ts``` // ./lib/foobar1.ts // everything is fine import { z } from "https://deno.land/x/zod@v3.21.4/modSmall Deno Script to upload to my server not working due to not being able to be run on CentOSAll my clients run Ubuntu or Debian, and the script will not run on CentOS due to the problem of a GEmpty output in Deno.CommandI’m trying to run the command `git log β€”grep='.'` from Deno. However, it always returns an empty outPreventing `Deno.stdin` reads from blockingIn a module for reading `Deno.stdin` input, there is a loop that reads and parses the returned bytesCan I pre-cache dependencies that I specify using npm:?Hi! Trying to figure out the right way to combine Deno's module support to make our deploys consisteJSON Schema Core, $dynamicAnchor, and $vocabularySlightly off-topic, but I'm writing a Deno library. Anyone deeply knowledgeable on JSON Schema CoreTest case is leaking resourcesHi, all my tests keeps failing because of ```json error: AssertionError: Test case is leaking 2 reso[have workaround] "deno" and "deno repl" aren't producing outputI typed in "deno" and nothing happened, and I thought it was finally doing the right thing when no aServe Fresh over HTTPS locallyHow can I serve Fresh over HTTPS locally? In node I might generate certs and do ``` https.createSercli/tsc crashes with Uncaught TypeErrorUsing latest Deno (1.32.4), this only happens on certain files, but it effectively means that I cannFresh: <Head> component is not supportedWhen running `deno task start` with this as my `pages/index.tsx` file (as in the demo) I'm seeing tIs there a way to vendor npm specifier imports?> deno vendor npm:express > Vendored 0 modules into scripts/vendor directoryHow can I import with npm: specifiers through a proxy?My company blocks npmjs.com and they have setup an internal Artifactory instance. With nodejs I can