Mqx
Mqxβ€’14mo ago

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

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
69 Replies
AapoAlas
AapoAlasβ€’14mo ago
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.)
Mqx
Mqxβ€’14mo ago
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
AapoAlas
AapoAlasβ€’14mo ago
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".
Mqx
Mqxβ€’14mo ago
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)
AapoAlas
AapoAlasβ€’14mo ago
Deno.UnsafePointerView.getCString(pointer)
Mqx
Mqxβ€’14mo ago
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'.
AapoAlas
AapoAlasβ€’14mo ago
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.
Mqx
Mqxβ€’14mo ago
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?
AapoAlas
AapoAlasβ€’14mo ago
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 *.
Mqx
Mqxβ€’14mo ago
Okay. Thanks for your support! I appreciate it!
AapoAlas
AapoAlasβ€’14mo ago
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 πŸ™‚
Mqx
Mqxβ€’14mo ago
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.
AapoAlas
AapoAlasβ€’14mo ago
Is the C library written by yourself or is it some known library?
Mqx
Mqxβ€’14mo ago
My friend did write it but it is all self written. No third party lib.
AapoAlas
AapoAlasβ€’14mo ago
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.
Mqx
Mqxβ€’14mo ago
Mqx
Mqxβ€’14mo ago
Its currently a mess xD
AapoAlas
AapoAlasβ€’14mo ago
πŸ™‚
Mqx
Mqxβ€’14mo ago
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() {
}
Mqx
Mqxβ€’14mo ago
And I get something like this xD
AapoAlas
AapoAlasβ€’14mo ago
Yeah okay, that seems ... seems like it's missing the null byte at the end of the read: 1024 message maybe?
Mqx
Mqxβ€’14mo ago
I tried sending it from the arduino... same result You mean like this:
Serial.print("Hello World!\0");
Serial.print("Hello World!\0");
?
AapoAlas
AapoAlasβ€’14mo ago
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)).
Mqx
Mqxβ€’14mo ago
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!
AapoAlas
AapoAlasβ€’14mo ago
No problem.
Unknown User
Unknown Userβ€’14mo ago
Message Not Public
Sign In & Join Server To View
AapoAlas
AapoAlasβ€’14mo ago
OneByte and UTF-16, isn't it? Though I guess there's also OneByteExternal and UTF-16 External. Anything else?
Unknown User
Unknown Userβ€’14mo ago
Message Not Public
Sign In & Join Server To View
Mqx
Mqxβ€’14mo ago
@aapoalas Okay we have looked at the whole thing again, however the data from the Arduino is still being received incorrectly.
Mqx
Mqxβ€’14mo ago
This is currently the way we read and write from the com port to the log.log file
Mqx
Mqxβ€’14mo ago
log.log looks like this. So far so good but after a while we got gibberish
Mqx
Mqxβ€’14mo ago
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()
Mqx
Mqxβ€’14mo ago
Mqx
Mqxβ€’14mo ago
I get a 0 so the opening works without a problem.
AapoAlas
AapoAlasβ€’14mo ago
Sorry, I don't know too much about reading from Arduino 😦
Mqx
Mqxβ€’14mo ago
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));
AapoAlas
AapoAlasβ€’14mo ago
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.
Mqx
Mqxβ€’14mo ago
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
AapoAlas
AapoAlasβ€’14mo ago
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.
Mqx
Mqxβ€’14mo ago
at wich line?
AapoAlas
AapoAlasβ€’14mo ago
memcpy(&buffer, ....
Mqx
Mqxβ€’14mo ago
Thanks now it prints something!
AapoAlas
AapoAlasβ€’14mo ago
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);
Mqx
Mqxβ€’14mo ago
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 !
AapoAlas
AapoAlasβ€’14mo ago
Glad to hear it. You're welcome! I'm always happy to help with FFI stuff πŸ™‚
Mqx
Mqxβ€’14mo ago
πŸ‘πŸ» @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
AapoAlas
AapoAlasβ€’14mo ago
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.
Mqx
Mqxβ€’14mo ago
Sounds to complicated. xD I think we will build a string and than parse that string.
javi
javiβ€’14mo ago
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();
Mqx
Mqxβ€’14mo ago
Thanks for sharing! I will look into this πŸ‘πŸ»
javi
javiβ€’14mo ago
welcome <:cookie_deno:1002977285734932480>
Mqx
Mqxβ€’13mo ago
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?
AapoAlas
AapoAlasβ€’13mo ago
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.)
Mqx
Mqxβ€’13mo ago
Okay how does my c++ code need to look like?
AapoAlas
AapoAlasβ€’13mo ago
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...)
Mqx
Mqxβ€’13mo ago
Thanks it works!
AapoAlas
AapoAlasβ€’13mo ago
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: ...
Mqx
Mqxβ€’13mo ago
πŸ‘πŸ» Thanks man!
AapoAlas
AapoAlasβ€’13mo ago
np
Mqx
Mqxβ€’13mo ago
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?
AapoAlas
AapoAlasβ€’13mo ago
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.
Mqx
Mqxβ€’13mo ago
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?
AapoAlas
AapoAlasβ€’13mo ago
Deno.dlopen(new URL("./ts/bin/windows.dll", import.meta.url), { ... })
Mqx
Mqxβ€’13mo ago
Does also not work πŸ˜• But I will try again today
AapoAlas
AapoAlasβ€’13mo ago
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.
Mqx
Mqxβ€’13mo ago
Okay
AapoAlas
AapoAlasβ€’13mo ago
But yeah, best try it and maybe log the URL to check that it looks reasonable.
Mqx
Mqxβ€’13mo ago
Yea Thanks
AapoAlas
AapoAlasβ€’13mo ago
np