Search code examples
gostdiocgo

CGO how to convert to a FILE* type


package main

/*
#include <stdlib.h>
#include <stdio.h>

void print_string( FILE *stream, char *text) {
    printf("Input pointer is %p\n", (void *) stream);
    printf("Expected stderr is %p\n", (void *) stderr);
    fprintf(stream, text);
}
*/
import "C"

import (
    "unsafe"
    "syscall"
)

func main() {
    text := C.CString("test123\n")
    defer C.free(unsafe.Pointer(text))

    // fileStream := (*C.FILE)(C.stderr) // this works
    fileStream := (*C.FILE)(unsafe.Pointer(uintptr(syscall.Stderr)))
    defer C.free(unsafe.Pointer(fileStream))

    C.print_string(fileStream, text)
}

Throws an access violation exception.

I'm wanting to do this because a C library I want to use fprintf's the errors so I want to be able to create a Go file to read the errors from that.


Solution

  • You can't do

    (*C.FILE)(unsafe.Pointer(uintptr(syscall.Stderr)))
    

    For two reasons:

    • syscall.Stderr is system-dependent: on UNIX-like systems (including macOS) it's merely an integer number (2), while on Windows it's a HANDLE (which can as well be NULL if the application is not attached to a console, but we digress).
    • A FILE* is a (pointer to a) type provided by the C standard library, and Go does not use it to work with files.

    So there's simply no way to type-convert (or type cast, if you like) syscall.Stderr to a FILE*. The fact you've defeated the Go's type system using the usual tricks so that the attempted conversion compiles means nothing: your code basically makes a pointer (an address of memory) with the value 2, and if this happens on x86, you merely get an expected fault when attempting to read memory at address 0x02. On different platforms it may have more funny results but I think you get the idea.

    But then the question is: why want that? Depending on what you're after, you have three options:

    • You might want to print to stderr from C code linked to your Go code, you can merely use the stderr symbol provided by the C standard library and directly call fprintf et al on it.

    • You might want to obtain a file descriptor out of os.Stderr by calling os.Stderr.Fd() and then doing fdopen(3) on the returned FD at the C side.

    • You might want to use os.Stderr (which is not the same thing as syscall.Stderr!) for output sort-of directly from the C side. To do that, you have to write and provide to your C side a set of wrapper Go functions.

    Note that the first two options above would not provide any synchronization with the Go side. I mean, if it's possible for your C code and your Go code to write to the process' stderr from different threads in parallel, you might get intermixed output.

    They also do not protect from Go code reopening os.Stderr to another file while not managing to have that new file reuse the standard stderr's file descriptor.