Redirecting shared lib stdout

45 views
Skip to first unread message

Paulo Matos

unread,
May 12, 2021, 3:49:51 AM5/12/21
to Racket Users
Hi,

I have a shared library for which I am creating some bindings:
https://github.com/pmatos/racket-binaryen

There's a function BinaryenModulePrint that prints a WebAssembly module
to stdout.

When I wrap it in racket, if I do something like:
(define mod ...)
(with-output-to-string (lambda () (BinaryenModulePrint mod)))

The return value will be "" and it will still print the module to
stdout. I understand! However, I don't know how to solve it in Racket.

In C, things seem easier because I have access to dup2 and pipe in case
I need to redirect things, however in Racket all my attempts have
failed.

I have created the simple example:
```hello.c
#include <stdio.h>

void hello(void) {
printf("hello world!\n");
}
```

Compile with `gcc -shared -o hello.so hello.c`.

Then:
```hello.rkt
#lang racket/base

(require racket/port
ffi/unsafe
ffi/unsafe/define)

(define libhello (ffi-lib "/home/pmatos/dev/tmp/ffi-hello-world/hello.so"))
(define-ffi-definer define-hello libhello)
(define-hello hello (_fun -> _void))
(with-output-to-string hello)
```

Here's the issue! In C, I can do something like:
```hello-fix.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

extern void hello(void);

int main(void) {
int filefd = open("test.txt", O_WRONLY|O_CREAT, 0666);
dup2(filefd, fileno(stdout));
hello();

return 0;
}
```
Compile with `gcc -o hello hello.c hello-fix.c`

This will, send the output of hello() to a file. Now, in racket
preferably I want to connect what is sent to raw stdout, fd 1, to
current-output-port.

My thought was that I could create a dup2 ffi binding, and use
unsafe-port->file-descriptor to install the racket pipe file descriptor
in stdout using the dup2 ffi binding but it doesn't work.

And it doesn't work because the unsafe-port->file-descriptor returns #f,
even though port-waiting-peer? return #f as well (which sort of hints
that maybe the documentation of unsafe-port->file-descriptor is
incomplete.

The start of my attempt would look like this:

```hello.rkt
#lang racket/base

(require racket/port
ffi/unsafe
ffi/unsafe/define
ffi/unsafe/port)

(define libhello (ffi-lib "/home/pmatos/dev/tmp/ffi-hello-world/hello.so"))

(define-ffi-definer define-libc (ffi-lib #f))
(define-ffi-definer define-hello libhello)

(define-libc dup2 (_fun _int _int -> _int))
(define-hello hello (_fun -> _void))

(define-values (in out) (make-pipe))

;; FAILS because (unsafe-port-> ...) return #f
(dup2 (unsafe-port->file-descriptor out) 1)

;;...
;; connect the other end of the port to current-output-port
;; and use a thread to copy-port
```

Surely there must be a better way than this and I would be surprised if
there isn't already something out there in a library but I haven't found
anything yet. Any help here would be great.

Thanks,

--
Paulo Matos

Sage Gerard

unread,
May 12, 2021, 10:54:15 AM5/12/21
to racket...@googlegroups.com
I ran into this issue with rsound. I'm not sure how standard output can
be directly captured from a lower-level language in a Racket context
when that language can freely ignore the Racket printer and write
directly to STDOUT within the same operating system process.

I'd hate to just add a "me too", but the only way quick way I could
think to handle that was to add another subprocess. If I were wanting to
solve the problem in one process, I'd probably extend Racket at the C
level and integrate my output with ports.

https://docs.racket-lang.org/inside/Ports_and_the_Filesystem.html?q=c%20api
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/87r1icxuhi.fsf%40linki.tools.

--
~slg


Jon Zeppieri

unread,
May 12, 2021, 8:39:14 PM5/12/21
to Paulo Matos, Racket Users
Can you use BinaryenModuleWriteText instead? It looks like it was
added to address your use case. -J

Paulo Matos

unread,
May 14, 2021, 6:39:07 AM5/14/21
to Jon Zeppieri, Racket Users

Jon Zeppieri writes:

> Can you use BinaryenModuleWriteText instead? It looks like it was
> added to address your use case. -J

Good point and nice catch.

Although it doesn't solve the issue with racket it does provide a way
out for me to create a safe binding for this function that works within
Racket.

Thanks,

Paulo Matos
--
Paulo Matos

Paulo Matos

unread,
May 14, 2021, 7:31:16 AM5/14/21
to Sage Gerard, racket...@googlegroups.com

Sage Gerard writes:

> I ran into this issue with rsound. I'm not sure how standard output can
> be directly captured from a lower-level language in a Racket context
> when that language can freely ignore the Racket printer and write
> directly to STDOUT within the same operating system process.
>
> I'd hate to just add a "me too", but the only way quick way I could
> think to handle that was to add another subprocess. If I were wanting to
> solve the problem in one process, I'd probably extend Racket at the C
> level and integrate my output with ports.
>
> https://docs.racket-lang.org/inside/Ports_and_the_Filesystem.html?q=c%20api

I was thinking about this until I saw Jon's suggestion - specific to
binaryen bindings.

However, why would you use the C API (which seems to be mostly focused
on BC)? My approach would be to wrap the function in C, redirect stdout
using dup2 to a file and bind that wrapper in racket, read from the file
and send to `current-output-port`.

Kind regards,

Paulo Matos
--
Paulo Matos

Sage Gerard

unread,
May 14, 2021, 12:46:14 PM5/14/21
to Paulo Matos, racket...@googlegroups.com
Ah, you're right. I'm still acclimated to BC here. Disregard!
--
~slg


Reply all
Reply to author
Forward
0 new messages