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?
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
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.)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
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".
okay now I am getting an empty object. How can I get the string from the function?
I have changed the code to this:
My test.h looks like this:
And my test.cpp looks like this:
CMakeLists.txt looks like this:
Deno.UnsafePointerView.getCString(pointer)
Okay thanks I will try that
This does not work:
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.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?
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 *
.Okay. Thanks for your support! I appreciate it!
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 π
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.
Is the C library written by yourself or is it some known library?
My friend did write it but it is all self written. No third party lib.
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.Its currently a mess xD
π
Thats all on the arduino side:
And I get something like this xD
Yeah okay, that seems ... seems like it's missing the null byte at the end of the
read: 1024
message maybe?I tried sending it from the arduino... same result
You mean like this:
?
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))
.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!No problem.
Unknown Userβ’2y ago
Message Not Public
Sign In & Join Server To View
OneByte and UTF-16, isn't it? Though I guess there's also OneByteExternal and UTF-16 External. Anything else?
Unknown Userβ’2y ago
Message Not Public
Sign In & Join Server To View
@aapoalas Okay we have looked at the whole thing again, however the data from the Arduino is still being received incorrectly.
This is currently the way we read and write from the com port to the log.log file
log.log looks like this. So far so good but after a while we got gibberish
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:
I get a 0 so the opening works without a problem.
Sorry, I don't know too much about reading from Arduino π¦
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:
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.Okay I'll try that
Currently I do this in the readLine() function:
But that does not seem to modify the buffer on my ts side
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.at wich line?
memcpy(&buffer, ....
Thanks now it prints something!
Performance wise, I also think it'd probably be possible to read directly into the buffer with
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:
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 !Glad to hear it.
You're welcome! I'm always happy to help with FFI stuff π
ππ»
@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
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.
Sounds to complicated. xD I think we will build a string and than parse that string.
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.
And after buildind the lib (can provide the Makefile if needed), here's the typescript code
Thanks for sharing! I will look into this ππ»
welcome <:cookie_deno:1002977285734932480>
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++:
In c++ I then want to call this function with parameters (code, message) so that I can receive these parameters in typescript:
Is this possible and if so how can I implement this?
Yes, what you're looking for is
Deno.UnsafeCallback
.
Something like this then:
(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.)Okay how does my c++ code need to look like?
Eeh, I'm not too great with C++ but something like this:
(Not sure if you actually need to
\0
on C++ side...)Thanks it works!
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: ...ππ» Thanks man!
np
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?
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.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?Deno.dlopen(new URL("./ts/bin/windows.dll", import.meta.url), { ... })
Does also not work π
But I will try again today
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.Okay
But yeah, best try it and maybe log the URL to check that it looks reasonable.
Yea
Thanks
np