Hello,
I’ve got to integratie a c function that allocaties a string and writes the adres of the allocates memory to the variable, eg inC
Void f(char **s)
Char *v;
f(&v);
V contains the allocated string
I can t seem to find documentation On how to do that
Hi, @hnmdijkema.
I haven't touched the ffi
in quite some time, but I think you might be looking for this section, or an adjacent one?
Pointers and Manual Allocation
I hope that helps.
Hi @bakgatviooldoos ,
I read that part, but it does not handle the case I presented.
As an example, assuming you have this on the C side:
philip@bastet:/tmp$ cat ffi_char_star_star.c
#include <string.h>
void ffi_char_star_star(char **s) {
*s = strdup("Hello, FFI world!");
}
philip@bastet:/tmp$ gcc --shared -fPIC -g -o ffi_char_star_star.so ffi_char_star_star.c
you could do this from Racket:
#lang racket
(require ffi/unsafe
ffi/unsafe/define
rackunit)
(define-ffi-definer define-demo
(ffi-lib "/tmp/ffi_char_star_star.so"
#:custodian (current-custodian)))
(define-demo ffi_char_star_star
(_fun [raw : (_ptr o _pointer)]
-> _void
-> (begin0 (cast raw _pointer _string/utf-8)
(free raw))))
(check-equal? (ffi_char_star_star)
"Hello, FFI world!")
If the pattern of getting allocated memory that you are responsible for freeing recurs frequently, you could abstract it into a custom function type (like _ptr
in the example).
2 Likes
Thanks. I was also creating a test program:
C - Header file:
#ifndef FFI_TEST_H
#define FFI_TEST_H
#include "ffi_test_global.h"
extern "C" {
FFI_TEST_EXPORT int getString(char **s);
FFI_TEST_EXPORT void freeString(char *s);
}
#endif // FFI_TEST_H
C-file:
#include "ffi_test.h"
static int count = 0;
int getString(char **s)
{
count += 1;
char txt[1024];
sprintf(txt, "Hoi, dit is tekst %04d!\n", count);
*s = _strdup(txt);
fprintf(stderr, "getString: %p\n", *s);
fflush(stderr);
return count;
}
void freeString(char *s)
{
fprintf(stderr, "freeString: %p\n", s);
fflush(stderr);
free(s);
}
Racket FFI:
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define
)
(define-ffi-definer define-getstr
(ffi-lib "C:/devel/racket/ffi_test/build/Release/ffi_test.dll"))
; This doesn't crash racket
;(define-getstr getString
; (_fun (s : (_ptr o _pointer)) -> (c : _int)
; -> (values
; (begin0
; (cast s _pointer _string/utf-8)
; (freeString s))
; c)))
;-> (values s c)))
; This crashes Racket
(define-getstr getString
(_fun (s : (_ptr o _pointer)) -> (c : _int)
-> (values
(begin0
(cast s _pointer _string/utf-8)
(free s))
c)))
; freeString 'lives' in the DLL and deallocates it
; with the memory management in the DLL.
(define-getstr freeString
(_fun (s : _pointer) -> _void))
So I bumped against some DLL wall....(windows
)
I tried the same in C:
getstr.exe
#include <stdio.h>
#include <windows.h>
#include <iostream>
int main()
{
HINSTANCE lib_handle = LoadLibrary("C:/devel/racket/ffi_test/build/Release/ffi_test.dll");
if (!lib_handle) {
std::cout << "could not load the dynamic library" << std::endl;
return EXIT_FAILURE;
}
// resolve function address here
int (*getstring)(char **s) = nullptr;
void (*freestring)(char *s) = nullptr;
getstring = (int (*)(char **)) GetProcAddress(lib_handle, "getString");
freestring = (void(*)(char *)) GetProcAddress(lib_handle, "freeString");
if (!getstring) {
std::cout << "cannot load getString" << std::endl;
return EXIT_FAILURE;
}
if (!freestring) {
std::cout << "cannot load getString" << std::endl;
return EXIT_FAILURE;
}
int i;
for(i = 0; i < 10; i++) {
char *s;
int count;
count = getstring(&s);
printf("%d - %s", count, s);
//freestring(s);
free(s) // crashes C program
}
}
00:49:28: Debugging C:\devel\racket\ffi_test\build\Debug\getstr.exe ...
HEAP[getstr.exe]:
Invalid address specified to RtlValidateHeap( 000001DBE8360000, 000001DBE8379AD0 )
00:50:58: Debugging of C:\devel\racket\ffi_test\build\Debug\getstr.exe has finished.
So probably the best thing to do is to provide a library specific "free" function to deallocate the memory? Or is there some way around this?
I am by no means a Windows person, but IIUC different DLLs may be linked with different implementations of malloc
and free
, and a given free
can only handle memory allocated by its corresponding malloc
. (You can use alternate malloc
implementations outside of Windows, too, if you explicitly want to.)
A library-specific free
function is definitely an option. If this is some existing C library, its documentation ought to tell you how to free memory you are responsible for freeing.
If you are writing this C code, you might consider designing the API so that Racket allocates the memory and the C side simply fills it in. That could ease cooperation with the GC and potentially even avoid copying in some cases. But coming up with an ergonomic design would be influenced the specific patterns that come up in your C library.