Mqx
Mqxβ€’2y 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β€’2y 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β€’2y 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β€’2y 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β€’2y 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β€’2y ago
Deno.UnsafePointerView.getCString(pointer)
Mqx
Mqxβ€’2y 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β€’2y 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β€’2y 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β€’2y 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β€’2y ago
Okay. Thanks for your support! I appreciate it!
AapoAlas
AapoAlasβ€’2y 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β€’2y 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β€’2y ago
Is the C library written by yourself or is it some known library?
Mqx
Mqxβ€’2y ago
My friend did write it but it is all self written. No third party lib.
AapoAlas
AapoAlasβ€’2y 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.