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:

    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:

    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.