Leokuma
Leokuma2y ago

FFI: How to pass a pointer to a char pointer?

How can I pass a pointer to a char pointer in order to use the C function below via FFI?
my_func(char **strp)
my_func(char **strp)
The external library is supposed to write a string to that buffer, and then Deno reads its content
47 Replies
Deno AI Helper
The current FFI API in Deno does not support passing a pointer to a pointer directly. The supported types for parameters and results in FFI calls are: i8, u8, i16, u16, i32, u32, i64, u64, usize, f32, f64, void, pointer, buffer, function, and { struct: [...] }. However, you can pass a buffer (which is a TypedArray in JavaScript) to a C function. The C function can then treat this buffer as a pointer to a char array (i.e., char*). But for a char**, the current FFI API does not provide a direct way to handle this. Here's an example of how you can pass a buffer to a C function:
// ffi.ts
const library = Deno.dlopen("./mylib.so", {
my_func: {
parameters: ["buffer", "usize"],
result: "void",
},
});

const buffer = new Uint8Array(128); // allocate a buffer
library.symbols.my_func(buffer, buffer.length);
// ffi.ts
const library = Deno.dlopen("./mylib.so", {
my_func: {
parameters: ["buffer", "usize"],
result: "void",
},
});

const buffer = new Uint8Array(128); // allocate a buffer
library.symbols.my_func(buffer, buffer.length);
In this example, my_func is expected to fill the buffer with a string. After the call, you can convert the buffer to a string in JavaScript. Please note that this is a workaround and may not work for all use cases. If your C function specifically requires a char**, you might need to modify the C function to work with a char* or wait for more advanced features in Deno's FFI API.
AapoAlas
AapoAlas2y ago
Allocate 8 bytes of Uint8Array and pass that as parameter type "buffer" to the C call. After the call read the value from the Uint8Array as an unsigned 64 bit BigInt integer using either a DataView or a BigUint64Array create from the ArrayBuffer of the Uint8Array (uint8arr.buffer), and finally create a new pointer object using Deno.UnsafePointer.create(). Sorry it's a bit convoluted, FFI is a bit like that 🙂
Leokuma
LeokumaOP2y ago
Thank you so much!
b0yediti0n
b0yediti0n4w ago
i try but dont work
TangJieHao
TangJieHao4w ago
try looking at lino's raylib bindings maybe that will help you understand
TangJieHao
TangJieHao4w ago
GitHub
GitHub - lino-levan/raylib-deno: Deno FFI bindings for Raylib
Deno FFI bindings for Raylib. Contribute to lino-levan/raylib-deno development by creating an account on GitHub.
b0yediti0n
b0yediti0n4w ago
Thank you for helping me, but I didn't find an example of passing a parameter by reference with a string. The value changes when using a number, but not when using a string buffer.
TangJieHao
TangJieHao4w ago
I see
AapoAlas
AapoAlas3w ago
You'll need to give a bit more information than that; what is the C API / function type you're calling? What is the Deno FFI declaration you're using to call it?
b0yediti0n
b0yediti0n3w ago
i try call .so, there are 3 parameters but the second and thirth is a pointer, the second receive the string and third len of string
No description
AapoAlas
AapoAlas3w ago
"Integer" and "String" are not sufficient for me to know how this method should be called. Do you have any better documentation, eg. int or long uint or uintptr_t or such for the parameters? Also, what is your Deno FFI declaration for the method like? My guess would be that something like this would be correct:
const DECL = {
method: {
parameters: [
"i32",
"buf",
"buf"
],
result: "void",
}
};

// ...

const AIndex = 12;

const outPtrs = new ArrayBuffer(16);
const outPtrValues = new BigUint64Array(outPtrs);
const sResposta = new Uint8Array(outPtrs, 0, 8);
const esTamanho = new Uint8Array(outPtrs, 8, 8);

lib.symbols.method(AIndex, sReposta, esTamanho);

console.log("sResposta address:", outPtrValues[0]);
console.log("es Tamanho value:", Number(outPtrValues[1]));
const sRepostaPtr = Deno.UnsafePointer.create(outPtrValues[0]);
const sRepostaBuffer = Deno.UnsafePointerView.getArrayBuffer(sRepostaPtr, Number(outPtrValues[1]));
const sRespotaString = new TextDecoder().decode(new Uint8Array(sRepostaBuffer));
const DECL = {
method: {
parameters: [
"i32",
"buf",
"buf"
],
result: "void",
}
};

// ...

const AIndex = 12;

const outPtrs = new ArrayBuffer(16);
const outPtrValues = new BigUint64Array(outPtrs);
const sResposta = new Uint8Array(outPtrs, 0, 8);
const esTamanho = new Uint8Array(outPtrs, 8, 8);

lib.symbols.method(AIndex, sReposta, esTamanho);

console.log("sResposta address:", outPtrValues[0]);
console.log("es Tamanho value:", Number(outPtrValues[1]));
const sRepostaPtr = Deno.UnsafePointer.create(outPtrValues[0]);
const sRepostaBuffer = Deno.UnsafePointerView.getArrayBuffer(sRepostaPtr, Number(outPtrValues[1]));
const sRespotaString = new TextDecoder().decode(new Uint8Array(sRepostaBuffer));
@b0yediti0n fixed the above; I was hallucinating odd things about esTamanho; this should now look a bit more reasonable. We first get the sReposta address and the es Tamanho integer values using out pointers, then we turn sReposta into a pointer and create an ArrayBuffer from that pointer based on the es Tamanho length. Then we use TextDecoder to decode that buffer's byte contents into a string (this assumes the contents is UTF-8).
b0yediti0n
b0yediti0n3w ago
i wil try thankyou i was work in node with ffi,
this.NFE_ObterXml = this.lib.func("int16_t NFE_ObterXml(int16_t AIndex, char **sResposta, int16_t *esTamanho)")
obtemXml(lista){
let aloc_sResposta = Buffer.alloc(256);
let aloc_esTamanho = ref.alloc("int", 256);

this.NFe.NFE_ObterXml(
0,
aloc_sResposta,
aloc_esTamanho
)

if(256 < aloc_esTamanho.readInt32LE(0)){
aloc_sResposta = Buffer.alloc(aloc_esTamanho.readInt32LE(0));
this.NFe.NFE_UltimoRetorno(aloc_sResposta, aloc_esTamanho)
console.log(aloc_sResposta.toString('utf-8', 0, aloc_esTamanho.readInt32LE(0)))
}

return aloc_sResposta.toString('utf-8', 0, aloc_esTamanho.readInt32LE(0))
this.NFE_ObterXml = this.lib.func("int16_t NFE_ObterXml(int16_t AIndex, char **sResposta, int16_t *esTamanho)")
obtemXml(lista){
let aloc_sResposta = Buffer.alloc(256);
let aloc_esTamanho = ref.alloc("int", 256);

this.NFe.NFE_ObterXml(
0,
aloc_sResposta,
aloc_esTamanho
)

if(256 < aloc_esTamanho.readInt32LE(0)){
aloc_sResposta = Buffer.alloc(aloc_esTamanho.readInt32LE(0));
this.NFe.NFE_UltimoRetorno(aloc_sResposta, aloc_esTamanho)
console.log(aloc_sResposta.toString('utf-8', 0, aloc_esTamanho.readInt32LE(0)))
}

return aloc_sResposta.toString('utf-8', 0, aloc_esTamanho.readInt32LE(0))
i'm sorry i don't see your message previous
b0yediti0n
b0yediti0n3w ago
thankyou for helping, but the pointer string is not changed thankyou.
No description
AapoAlas
AapoAlas3w ago
Okay, based on this the code should look a bit more like this:
const outPtrs = new ArrayBuffer(10); // Allocate 8 + 2 bytes, that is one 64 bit pointer's worth + one 16 bit integer's worth.
const sResposta = new Uint8Array(outPtrs, 0, 8); // Create an out-pointer buffer pointing to the first 8 bytes (64 bits).
const esTamanho = new Uint8Array(outPtrs, 8, 2); // Create an out-pointer buffer pointing to the last 2 bytes (16 bits).

lib.NFe.symbols.NFE_ObterXML(
0, sResposta, esTamanho
);

console.log(sResposta);
console.log(new Uint16Array(outPtrs, 8, 1)[0]); // es Tamanho is a Uint16
const outPtrs = new ArrayBuffer(10); // Allocate 8 + 2 bytes, that is one 64 bit pointer's worth + one 16 bit integer's worth.
const sResposta = new Uint8Array(outPtrs, 0, 8); // Create an out-pointer buffer pointing to the first 8 bytes (64 bits).
const esTamanho = new Uint8Array(outPtrs, 8, 2); // Create an out-pointer buffer pointing to the last 2 bytes (16 bits).

lib.NFe.symbols.NFE_ObterXML(
0, sResposta, esTamanho
);

console.log(sResposta);
console.log(new Uint16Array(outPtrs, 8, 1)[0]); // es Tamanho is a Uint16
But I don't exactly understand how the API is supposed work, so I don't really know if it's correct that the sReposta bytes stay as zero.
b0yediti0n
b0yediti0n3w ago
if you want i send the dll or so the api create and send fiscal notes (brasil), create XML, i try get the xml in sResposta
AapoAlas
AapoAlas3w ago
My apologies, but accepting DLLs over the Internet isn't really a thing I'm willing to do 🙂 I'm also not quite idle enough to start debugging this on my own.
b0yediti0n
b0yediti0n3w ago
i undertand, thankyou for helpme with your time
AapoAlas
AapoAlas3w ago
Based on this int16_t NFE_ObterXml(int16_t AIndex, char **sResposta, int16_t *esTamanho) it seems like the API expects to also return an i16 so that's something to consider / fix in the Deno FFI API declaration. What is that return value for, what does it mean? If it is an error code, then checking that might be useful. Perhaps the API is returning you an error that would explain why it's not writing to sReposta?
b0yediti0n
b0yediti0n3w ago
there are 3 int return, to flag sucess or error first arg, is queue of list xml third arg, is the len total of sResposta, if the aloced sized is not enough to realoqued
AapoAlas
AapoAlas3w ago
Oh, ... hmm.... looking at this https://github.com/frones/ACBr/blob/master/Projetos/ACBrLib/Demos/Harbour/NFe/ACBrNFe.prg#L184 it seems like perhaps the C API string you used in Node is mistaken...? Or maybe I'm just misunderstanding it, but; it seems like the API expects you to preallocate a string buffer (which would normally be a char *sReposta) and tell the length of that buffer in esTamanho. It will then write into that buffer IF it can fit all the data in it, and it tells you how many bytes it wants to write by writing to esTamanho.
GitHub
ACBr/Projetos/ACBrLib/Demos/Harbour/NFe/ACBrNFe.prg at master · fr...
Projeto ACBr - O branch master é um repositório espelho do SVN original (svn://svn.code.sf.net/p/acbr/code/trunk2), criado e mantido com git-svn. - frones/ACBr
b0yediti0n
b0yediti0n3w ago
is this lib you find exectly i'm sorry but not work
AapoAlas
AapoAlas3w ago
Right, that would've been useful information in the very beginning 🙂 So it's not a simple out-pointer but an in-out pointer. In that case this is how you work it:
const DEFAULT_LENGTH = 256; // choose how much you want to pre-allocate; this should be large enough to cover the usual cases but not so large that you waste memory. Althought! If you can bear it, you can just do a single allocation and reuse that on every call, and that can then be pretty big.
const outPtrs = new ArrayBuffer(2 + DEFAULT_LENGTH); // Allocate 2 bytes for the 16 bit integer and then the above pre-allocation size.
const sResposta = new Uint8Array(outPtrs, 2, DEFAULT_LENGTH); // Out pointer for the string to be written into. Note that this is now offset by two bytes from outPtrs; the first two bytes are used for esTamanho.
const esTamanho = new Uint8Array(outPtrs, 0, 2); // Int/out pointer for the buffer length length to be written into, out-pointer for the string length that should be written in. Note that this is now at the beginning of outPtrs.
const esTamanhoView = new Uint16Array(outPtrs, 0, 1);
esTamanhoView[0] = DEFAULT_LENGTH;

lib.NFesymbols.NFE_ObterXML(
0, sResposta, esTamanho
);

const sRespostaLength = esTamanhoView[0];
if (sRespostaLength > DEFAULT_LEGTH) {
// library needed more room; need to reallocate.
const realloc = new Uint8Array(sRespostaLength);
this.NFe.NFE_UltimoRetorno(realloc, esTamanho);
console.log(new TextDecoder().decode(realloc));
} else {
const sRespostaString = new TextDecoder().decode(sResposta.subarray(0, sRespostaLength));
console.log(sRespostaString);
}
const DEFAULT_LENGTH = 256; // choose how much you want to pre-allocate; this should be large enough to cover the usual cases but not so large that you waste memory. Althought! If you can bear it, you can just do a single allocation and reuse that on every call, and that can then be pretty big.
const outPtrs = new ArrayBuffer(2 + DEFAULT_LENGTH); // Allocate 2 bytes for the 16 bit integer and then the above pre-allocation size.
const sResposta = new Uint8Array(outPtrs, 2, DEFAULT_LENGTH); // Out pointer for the string to be written into. Note that this is now offset by two bytes from outPtrs; the first two bytes are used for esTamanho.
const esTamanho = new Uint8Array(outPtrs, 0, 2); // Int/out pointer for the buffer length length to be written into, out-pointer for the string length that should be written in. Note that this is now at the beginning of outPtrs.
const esTamanhoView = new Uint16Array(outPtrs, 0, 1);
esTamanhoView[0] = DEFAULT_LENGTH;

lib.NFesymbols.NFE_ObterXML(
0, sResposta, esTamanho
);

const sRespostaLength = esTamanhoView[0];
if (sRespostaLength > DEFAULT_LEGTH) {
// library needed more room; need to reallocate.
const realloc = new Uint8Array(sRespostaLength);
this.NFe.NFE_UltimoRetorno(realloc, esTamanho);
console.log(new TextDecoder().decode(realloc));
} else {
const sRespostaString = new TextDecoder().decode(sResposta.subarray(0, sRespostaLength));
console.log(sRespostaString);
}
b0yediti0n
b0yediti0n3w ago
i'm sorry
AapoAlas
AapoAlas3w ago
No worries 🙂 C APIs are really prickly friends; as a rule of thumb, every single piece of information possible is usually needed to figure out what on earth they mean 😄
b0yediti0n
b0yediti0n3w ago
thankyou for helping but don't work
No description
b0yediti0n
b0yediti0n3w ago
sResposta is not filled after call funcion lib
AapoAlas
AapoAlas3w ago
Oh sorry, I forgot: gotta write the DEFAULT_LENGTH into esTamanho before calling. I'll fix the text above. Now it should hopefully work.
b0yediti0n
b0yediti0n3w ago
I saw and corrected afeter print this cause the break, but in the same way, the sResposta is not changeded, the responde of function its not captured i have the same code in python do you wante see?
AapoAlas
AapoAlas3w ago
If it goes into the if branch then it means that it needed more memory to write the data out, hence why it doesn't write anything into sReposta. Sure
b0yediti0n
b0yediti0n3w ago
it not goes into the if the size is bigger
AapoAlas
AapoAlas3w ago
Bere? Yeah, if the size is bigger then it should just write the data into sReposta.
b0yediti0n
b0yediti0n3w ago
GitHub
GitHub - B0yEditi0n/HERA_API: vim fazer um backup desse arquivo, a ...
vim fazer um backup desse arquivo, a documetnação e a criação estão ossos então cada passo é prezervado - B0yEditi0n/HERA_API
AapoAlas
AapoAlas3w ago
That is a repository
b0yediti0n
b0yediti0n3w ago
the response is empty its my code the same lib is calling but in python
AapoAlas
AapoAlas3w ago
Yes; if you want to show me where you use this particular API, please link it. Please don't make me search through the repository for that information.
b0yediti0n
b0yediti0n3w ago
No description
b0yediti0n
b0yediti0n3w ago
sorry
b0yediti0n
b0yediti0n3w ago
No description
AapoAlas
AapoAlas3w ago
Thanks, looks reasonable.
b0yediti0n
b0yediti0n3w ago
i'm brasilian do you need that i translate the comment or copy and past the code here?
AapoAlas
AapoAlas3w ago
Nah, I think I have it. Thank you though ❤️
b0yediti0n
b0yediti0n3w ago
heiy, It is really buffer type or pointer type in the call ffi??
AapoAlas
AapoAlas3w ago
const DEFAULT_LENGTH = 9048;
const outPtrs = new ArrayBuffer(2 + DEFAULT_LENGTH);
const sResposta = new Uint8Array(outPtrs, 2, DEFAULT_LENGTH);
const esTamanho = new Uint8Array(outPtrs, 0, 2);
const esTamanhoView = new Uint16Array(outPtrs, 0, 1);
esTamanhoView[0] = DEFAULT_LENGTH;

lib.NFesymbols.NFE_ObterXML(
0, sResposta, esTamanho
);

const esTamanhoValue = esTamanhoView[0];
if (esTamanhoValue > DEFAULT_LEGTH) {
// library needed more room; need to reallocate.
const realloc = new Uint8Array(sRespostaLength);
this.NFe.NFE_UltimoRetorno(realloc, esTamanho);
const sRespostaString = new TextDecoder().decode(realloc);
console.log(sRespostaString);
} else {
const sRespostaString = new TextDecoder().decode(sResposta.subarray(0, esTamanhoValue));
console.log(sRespostaString);
}
const DEFAULT_LENGTH = 9048;
const outPtrs = new ArrayBuffer(2 + DEFAULT_LENGTH);
const sResposta = new Uint8Array(outPtrs, 2, DEFAULT_LENGTH);
const esTamanho = new Uint8Array(outPtrs, 0, 2);
const esTamanhoView = new Uint16Array(outPtrs, 0, 1);
esTamanhoView[0] = DEFAULT_LENGTH;

lib.NFesymbols.NFE_ObterXML(
0, sResposta, esTamanho
);

const esTamanhoValue = esTamanhoView[0];
if (esTamanhoValue > DEFAULT_LEGTH) {
// library needed more room; need to reallocate.
const realloc = new Uint8Array(sRespostaLength);
this.NFe.NFE_UltimoRetorno(realloc, esTamanho);
const sRespostaString = new TextDecoder().decode(realloc);
console.log(sRespostaString);
} else {
const sRespostaString = new TextDecoder().decode(sResposta.subarray(0, esTamanhoValue));
console.log(sRespostaString);
}
"buffer" for the second and third.
b0yediti0n
b0yediti0n3w ago
thank you
AapoAlas
AapoAlas3w ago
The above code hasn't really changed ~at all compared to the previous situation, but I at least cannot see what would be going wrong with it. If it still does not work, can you tell me what the return value is and what does esTamanhoValue evaluate into?
b0yediti0n
b0yediti0n3w ago
i don't belive, Work THANK YOU you save me

Did you find this page helpful?