Haskell how to work with extern FILE* from c?

203 views Asked by At

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.

2

There are 2 answers

1
K. A. Buhr On BEST ANSWER

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.

0
uuhan On

With c2hs of version 0.28.2, the following code works:

-- lib.chs
{#pointer *FILE as File newtype#}
foreign import ccall "stdio.h &__stderrp" c_stderr :: Ptr (Ptr File) -- can not just use "stdio.h &stderr", this may cause a reference error

-- main.hs
stderr <- File <$> peek c_stderr
func stderr