Search code examples
haskellc2hs

Haskell how to work with extern FILE* from c?


I want to foreign import a function from some c header, but how to deal with the stderr of type FILE* which defined as:

extern FILE* __stderrp;
#define stderr __stderrp

Maybe not precisely. I use c2hs for my ffi work, and already have:

{#pointer *FILE as File foreign finalizer fclose newtype#}

but I can not import stderr like this:

foreign import ccall "stdio.h stderr" stderr :: File

My c function has the signature:

void func(FILE*);

I can import func with c2hs:

{#fun func as ^ {`File'} -> `()'#}

I need to use stderr to run func:

func(stderr);

I am noob with the foreign import mechanism. It seems I can not import stderr in this way.

ps. Maybe I would wrap my func in a new function

void func2(void){func(stderr);}

This is a workaround, but seems not clean.


Solution

  • It's not unusual to require some kind of "shim" when writing FFI code for Haskell, and I'd encourage you to just write a helper function:

    FILE* get_stderr() { return stderr; }
    

    and use that (see example at the bottom of this answer).

    However, I was able to get the following minimal example to work by using the vanilla FFI's support for static pointers -- it doesn't import stderr directly, but imports a pointer to the stderr pointer. This kind of import is not directly supported by c2hs, so the interface code is ugly, and I don't think there's any way to avoid having to fetch the stderr pointer value in the IO monad, independent of whether or not you use c2hs.

    // file.h
    #include <stdio.h>
    void func(FILE*);
    
    // file.c
    #include "file.h"
    void func(FILE *f) {
        fputs("Output to stderr!\n", f);
    }
    
    // File.chs
    
    {-# LANGUAGE ForeignFunctionInterface #-}
    
    module Main where
    
    import Foreign   
    
    #include "file.h"    
    {#pointer *FILE as File newtype#}
    {#fun func as ^ { `File' } -> `()'#}
    
    foreign import ccall "&stderr" stderr_ptr :: Ptr (Ptr File)    
    
    main :: IO ()
    main = do stderr <- File <$> peek stderr_ptr
              func stderr
    

    For comparison, this minimal example with the helper function looks much cleaner at the Haskell level:

    // file.h
    #include <stdio.h>
    void func(FILE*);
    FILE* get_stderr(void);
    
    // file.c
    #include "file.h"
    void func(FILE *f) {
        fputs("Output to stderr!\n", f);
    }
    FILE* get_stderr(void) {return stderr; }
    
    // File.chs
    {-# LANGUAGE ForeignFunctionInterface #-}
    
    module Main where
    
    #include "file.h"    
    {#pointer *FILE as File newtype#}
    {#fun func as ^ { `File' } -> `()'#}
    {#fun pure get_stderr as ^ {} -> `File'#}
    
    main :: IO ()
    main = func getStderr
    

    Note that in both these examples, I removed your fclose finalizer. You probably don't want Haskell arbitrarily deciding it's a good time to close stderr on you.