How to test foreign call in the ghci with a c object file

Viewed 51

I have two c files and a c header as follows:

/* C header file: test.h */
#ifndef __TEST_H_
#define __TEST_H_

#define add(X, Y) ((X)+(Y))

#define PI 3.1415926

int sub(int x, int y);

typedef struct person {
    char *name;
    int age;
} Person;

Person new_person(char *name, int age);

void show_person(const Person *person);

#endif
/* C file 1: sub.c */
#include "test.h"

int sub(int x, int y)
{
    return x - y;
}
/* C file 2: person.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test.h"

Person new_person(char *name, int age)
{
    Person person = { 0 };
    person.age = age;
    person.name = calloc(strlen(name) + 1, 1);
    strcpy(person.name, name);
    return person;
}

void show_person(const Person *person)
{
    printf("%s is %d years old.\n", person -> name, person -> age);
}

Then I compile the above c files and merge the compiled object files into a library libtest.a.

Next I try to bind to the above c code in Haskell by using the hsc2hs tool. Well, I am just learning the FFI in Haskell. The .hsc file are as follow:

-- .hsc file: Test.hsc
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE CApiFFI #-}

module Test where

import Foreign
import Foreign.C
import System.IO.Unsafe

#include "test.h"

foreign import capi "test.h add"
    c_add :: Int -> Int -> Int

foreign import capi "test.h sub"
    c_sub :: Int -> Int -> Int

foreign import capi "test.h value PI"
    c_pi :: Double

data {-# CTYPE "test.h" "Person" #-} Person = 
    Person { name :: String, age :: Int} deriving Show

instance Storable Person where
    sizeOf _ = #{size Person}
    alignment _ = #{alignment Person}
    peek ptr = Person <$> (#{peek Person, name} ptr >>= peekCString)
                      <*> #{peek Person, age} ptr
    poke ptr (Person name age) = do
        withCString name $ #{poke Person, name} ptr
        #{poke Person, age} ptr age

foreign import capi "test.h show_person"
    c_show_person :: Ptr Person -> IO ()
showPerson :: Person -> IO ()
showPerson person = alloca $ \ptr -> do
    poke ptr person
    c_show_person ptr

{- Since FFI doesn't support passing a C strut by value directly, 
I create a wrapper function for the new_person C function
-}
#def void wrapper_new_person(Person *person, char *name, int age) \
{ *person = new_person(name, age); }

foreign import capi "Test_hsc.h wrapper_new_person"
    c_wrapper_new_person :: Ptr Person -> CString -> Int -> IO ()
newPerson :: String -> Int -> Person
newPerson name age = unsafePerformIO . alloca $ \ptr -> do
    name' <- newCString name
    c_wrapper_new_person ptr name' age
    peek ptr

After that, I run hsc2hs Test.hsc and get three files: Test.hs, Test_hsc.h, Test_hsc.c. Then I compile these code with the following command:

ghc -c Test.hs Test_hsc.c -ltest

The I want to test the foreign call in the ghci, so I run:

ghci Test.o

It tells that unknown symbol 'wrapper_new_person'.

Try again:

ghci Test.o Test_hsc.o -ltest

Now everything seems OK. However when I check the type of newPerson, or c_add, I get the error telling <interactive>:1:1: error: Variable not in scope: newPerson or <interactive>:1:1: error: Variable not in scope: c_add. It seems that the module is not loaded.

I also try to load the Test.hs directly:

ghci Test.hs -ltest -fobject-code

Everything seems OK, but when I run c_sub 3 4 or newPerson "TOM" 35 or showPerson (Person "Tom" 35) or c_add 3 4, ghci tells that unknown symbol 'wrapper_new_person', which is understandable since the Test_hsc.o is not linked.

Occasionly, a fatal error happens telling Access violation in generated code when reading 0x30, Attempting to reconstruct a stack trace...

1. The first question is what happens here?

2. Another question is how to test foreign call in the ghci without Test_hsc.o added into libtest.a?

This question is due to two facts that

  • if I add Test_hsc.o into libtest.a, then running ghci Test.hs -ltest -fobject-code makes everything OK.
  • if I write a Main module to test it as follows, then everything is OK too.
-- Main.hs
module Main where

import Test

p = newPerson "Tom" 35

main :: IO ()
main = do
    showPerson p
    putStrLn $ "4 + 5 = " ++ show (c_add 4 5)
    putStrLn $ "4 - 5 = " ++ show (c_sub 4 5)
    putStrLn $ "PI = " ++ show (c_pi)

Thanks for any information!

0 Answers
Related