Hs2lib Tutorial
Hs2lib Tutorial
Tamar Christina
[email protected] http://hackage.haskell.org/package/Hs2lib
September 4, 2011
This is a shortened version of the manual showing only the introduction and examples. Full manual to follow.
Copyright 2011 Tamar Christina http://code.zhox.com/haskell/hs2lib Licensed under the BSD License, Version 3.0 (the License); you may not use this le except in compliance with the License. You may obtain a copy of the License at http://www.opensource.org/licenses/bsd-license. php. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an as is basis, without warranties or conditions of any kind, either express or implied. See the License for the speci c language governing permissions and limitations under the License. First printing, September 2011
Contents
1 Introduction 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . Examples 2.1 Simple Arithmetic 2.2 User data types . 2.3 Callbacks . . . . . 2.4 Sessions . . . . . 7 7
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
9 . 9 . 23 . 24 . 25
CONTENTS
Release Notes
Version 0.5.5 - (Started) implementing a better test mechanism Version 0.5.4 - fixed a major marshalling bug having to do with lists of pointer types. Version 0.5.3 - Fixed an error with parsing pragmas - Renamed pragma hs2c# to hs2cs - Fixed an alignment issue with stdcall - Fixed an issue with lists and type synonyms Version 0.5.2 - No longer frees Strings, in order to prevent heap corruptions on calls from C#. Version 0.5.1 - Library bug fix which cauzed an error in marshalling Version 0.5.0 - Added support for memory leak tracing with the --debug flag % Added support for qualified importing. It is now possible to export values and types with the same name. (unfinished) % - Added the ability to export polymorphic types. (unfinished) % - Added support for infix constructors (unfinished) - Generates free functions StablePtr Version 0.4.8 - Major restructuring of code to allow users to extend/override the default type conversions of the tool. - Support for custom type translations Version 0.4.5 - Brand new Haskell code generators - Support for lists inside an IO wrapper (e.g. IO [Int]) - Brand new implementation in NativeMapping - Avoids unsafePerformIO as much as possible Version 0.4.4 - Fixed Include paths - NO support for datastructures with multiple constructors where one constructor has the same name as the datatype (codegen naming limitation) - Fixed codegen issue with callbacks - Added better IO Error handling Version 0.4.3 (Initial Release)
hs2lib manual
NO support for NO support for NO support for Does not allow NO support for
infix constructors lists inside Applied types (e.g. Maybe [String]) lists inside tuples (e.g. (Int, [String])) for custom translation of types. quantified types. (e.g. M.Map)
Introduction
Creating a static library from a Haskell program is no easy task. It requires you to create marshalling code1 to translate all your Haskell structures. Create foreign export declarations, write some initialization code and more. Hs2Lib aims to make this much easier by providing very simple annotations that can be used to mark functions that needs to be exported. Calling C functions from Haskell is very popular and has a lot of tooling support. The tool c2hs generates the appropriate Haskell data types and bindings from a C header le. Hs2Lib however does the inverse and much more. Since this tool is part of the back end of Visual Haskell 2010, it is essential that we be able to generate C# code as well. The tool itself is platform independent, but has mostly been tested only on windows.
De ning instances of the Storable instance for your datatypes. More on this in Chapter ??
1.1
Motivation
2 3
Throughout the years it has become increasingly easier to create shared libraries from Haskell programs when using GHC2 . The task however remains quite involved. The canonical example is the Adder program3 :
Syntax highlighting for Haskell provided by Alessandro Vermeulens lhs2tex-hl. Check it out at http://alessandrovermeulen.me/projects/lhs2texhl/
adder ::Int Int IO Int adder x y return px y q foreign export stdcall adder ::Int
Int IO Int
StartEnd.c contains RTS Initialization and termination code. More about this in Chapter ??
hs2lib manual
This is somewhat disingenuous, the Haskell FFI report5 speci es Char,Int,Double,Float and Bool as basic foreign types exported by the prelude. The Adder example is thus limited to using types that are prede ned to be a Foreign type. No custom marshallers are de ned 6 since we do not export any user types. The steps that in general need to be taken to compile an arbitrary Haskell le are in a nutshell:
RTS FFI Storable GHC DLL
marshalling (similar to serialization) is the process of transforming the memory representation of an object to a data format suitable for storage or transmission. It is typically used when data must be moved between di erent parts of a computer program or from one program to another.
DEF
LIB
Figure 1.1: Compilation steps to produce a static lib RTS De nes the runtime system initialization and termination function. Without this le we would have no way of starting and stopping the Haskell runtime. FFI Declare the appropriate foreign export de nitions. Also create wrappers over the to be exported functions, which then exposes them using types that are supported by the Foreign Function Interface. Storable De ne marshalling code for user data types. This is done by providing a proper Storable instance for every data type that needs to be exported. DEF (optional) By default, everything that is a function is exported by GHC in the Exports table by GHC. This includes closures etc. To hide all the noise we can de ne our own DEF le, which then states which functions are to be put in the static libs export table. LIB (optional) If we want to use MSVC++ and compile C++ code which uses the Haskell DLL, then we also need a .LIB le for the linker, which is created in this step GHC The nal step is to use a set of GHC commands to link and compile everything together into a neat little package. Doing these steps takes a long time. They are also quite repetitive and easy to automate. Which is exactly what Hs2Lib does. 7
NOTE: This tool does not use, nor keep the stubs generated from GHC. It produces its own includes.
2
2.1
Examples
This chapter shows 4 examples of small case studies to illustrate how to use Hs2lib. The rst one will be rather elaborate, showing how to call this tool from all three code generators. The rest will all be a bit more compact.
Simple Arithmetic
module Arith where -- @@ Export summerize::r Int s r Int s Int summerize x y sum px y q -- @@ Export single::Int r Int s single x r 1 . . x s
Figure 2.1: Simple Haskell Arithmetic The code is quite simple. summerize takes two list of integers and calculates their collective sum, whereas single takes an integer x and returns a list which contains the enumeration from 1 to x. The functions to be annotated have already been marked using the export annotation. In order to compile this example the only command needed is hs2lib Arith. PS C:\Examples> hs2lib Arith 9
10
hs2lib manual
Linking main.exe ... The directory should now contain the following les Directory: C:\Examples Mode ----a---a---a---a---a---a---a--LastWriteTime ------------5/30/2011 1:19 AM 5/30/2011 1:17 AM 5/30/2011 1:19 AM 5/30/2011 1:20 AM 5/30/2011 1:20 AM 5/30/2011 1:20 AM 5/30/2011 1:20 AM Length -----534 159 1973 6874624 711 1250 5630 Name ---Arith.hi Arith.hs Arith.o Hs2lib.dll Hs2lib.h Hs2lib_FFI.h HSHs2lib.a
1 2
The default namespace used by the tool is "Hs2lib"1 . The library produced has also already been stripped2 . By default, manual RTS initialization needs to be done. If we were to look at the exports table for Hs2lib.dll3 we would see the following output Microsoft (R) COFF/PE Dumper Version 10.00.40219.01 Copyright (C) Microsoft Corporation. All rights reserved.
The only extra shrinkage that can be gained now is by packing the le using something like upx. The smaller size comes with some slight overhead in start-up time. UPX will not be executed by Hs2lib 3 dumpbin /EXPORTS Hs2lib.dll
Dump of file Hs2lib.dll File Type: DLL Section contains the following exports for Hs2lib.dll 00000000 4DE2D4AC 0.00 1 8 8 characteristics time date stamp Mon May 30 01:20:12 2011 version ordinal base number of functions number of names name HsEnd@0 HsStart@0 _HsEnd@0 _HsStart@0 _single@8 _summerize@16 single@8 summerize@16
ordinal hint RVA 1 2 3 4 5 6 7 8 ... 0 1 2 3 4 5 6 7 00002299 00002258 00002299 00002258 0000204C 00001F54 0000204C 00001F54
examples
11
We can see that thanks to the .DEF4 le only the functions we wanted have been exported. Along with the RTS initialization functions HsStart and HsEnd.
Figure 2.2: New project dialog of Netbeans 6.8 In the le main.c add the following code: #include <stdio.h> #include <stdlib.h> #include "Hs2lib_FFI.h" int main(int argc, char** argv) { HsStart(); int* foo foo[0] = foo[1] = foo[2] = = (int*)malloc(sizeof(int)*3); 2; 5; 10;
printf("Sum result: %d\n", summerize(3, foo, 3, foo)); free(foo); int count; int* bar = single(8, &count);
12
hs2lib manual
printf("Single result: (%d)\n", count); int i; for(i = 0; i < count; i++) printf("\t%d\n", bar[i]); free(bar); HsEnd(); return (EXIT_SUCCESS); }
This test tests the functions summerize and single. It also tests the ability to pass lists back and forth. If we try to run this project building will fail. This is because theres still some settings we need to change. So lets open up the project settings dialogue.
The rst thing we need to change is the linker settings. In particular the Additional Library Directories and Libraries values need to be changed to the appropriate values. 5 In addition to the Linker, the Include Directories of the C Compiler settings also have to be changed.
examples
13
Figure 2.4: C Compiler Include Path dialog of Netbeans 6.8 As mentioned before, Hs2lib comes with a set a prede ned includes that support the generated code. These les are located in %AppData%\cabal\Hs2lib-version \Includes in windows6 . Next to the Hs2lib include directory, we also need to include the folder in which the generated header les were placed for the current project7 . The last thing that we need to change is the Run Directory. This sets the CWD of the executable. This needs to be set to the same folder which has our generated .DLL le, so that it is now in the path of the executable.
Note that most compilers dont expand this path. So Its recommended to expand them before hand and then add them to the Include Directories. You can do this by just opening up the folder in explorer, which will show you the full path. 7 C:\Examples in this case
Figure 2.5: Run CWD Path dialog of Netbeans 6.8 The project can now be compiled and run, if all goes well, the following output is displayed:
14
hs2lib manual
examples
15
Figure 2.7: New C++ Project Dialog Visual Studio 2010 We want a Win32 Console project, and also want an empty project.
Figure 2.8: Empty C++ Project Wizard Visual Studio 2010 The C++ Source le contains the following code: #include <stdio.h> #include <stdlib.h> #include "Hs2lib_FFI.h" int main() {
16
hs2lib manual
printf("Sum result: %d\n", summerize(3, foo, 3, foo)); delete foo; int count; int* bar = single(8, &count); printf("Single result: (%d)\n", count); int i; for(i = 0; i < count; i++) printf("\t%d\n", bar[i]); free(bar); HsEnd(); return 0; } Just like before we test the exact same functions and expect the exact same results to be returned. Before we can run this however we need to change a few settings again. First of which, is the Linker dependencies.
examples
17
We add Hs2lib.lib to the Additional Dependencies value of the Linker. This allows it to nd our exported functions. The next step is modify the Include- and Library Directories, we add the same values we added in Figure 2.4 and Figure 2.3.
And lastly we need to modify the working directory using the same values as in Figure 2.5.
If we were to compile and run this project, We would unfortunately receive the following error message:
18
hs2lib manual
This is because of a mismatch between memory allocators. The call to free is corrupting the heap because the free function being used has a di erent allocation style then the allocato(malloc) that reserved the memory. The di erence is in the version of msvcrt.dll being used. GHC which uses GCC to link, links against the msvcrt.dll 8 which is the standard C runtime on windows. Unfortunately, every version of Microsoft Visual C++ brings with it a new version of the runtime. The version of visual studio being used here9 uses the runtime msvcrt100.dll10 . These di erent versions of the runtime use di erent incompatible allocators. The standard solution/convention is to always allocate and deallocate memory on the callers side 11 . I prefer not to use this approach, It would require much more work to allocate memory for the Haskell data types. Instead, I provide in Hs2lib speci cally for this case a DLL12 which provides us with the same free function being used by msvcrt.dll. It is infact a simple redirect which just re-exports the free function. This Support le exports a function freeNative which should be used to free memory allocated in Haskell Land. 13 Using this alternative free function, our project nally runs correctly.
Visual Studio 2010 is the 10th version of visual studio. 10 http://msdn.microsoft.com/ en-us/library/abx4dbyh(v=VS.100) .aspx 11 It is probably for this reason that Win32 APIs require you to give a preallocated structure which will be lled in by the call instead of just returning a structure that it allocated. 12 WinDllSupport.dll
13
Include the le WinDllSupport.h and link to WinDllSupport.lib. Keep in mind that de calling convention for this DLL is Cdecl.
examples
19
Visual C#
The last part of this case study is using the C# code generator. Despise what intuition would suggest, this is actually quite straight forward. Hs2lib can generate the C# binding automatically for us. To do this, we need to pass it the c# ag.
This generates a le Hs2lib.cs containing everything we need. We can now create the new empty C# project.
After we have our empty project, we can now add a reference to the FFI.dll managed dll. This contains a plethora of helper functions to use the DLL from C#14 . Everything both generated and in this helper library will be in the WinDll namespace. The code that has just been generated can be found in WinDll.Generated and the types in WinDll.Generated.Types.
14
Highlights include a free function, methods to read Maybe values and much more.
20
hs2lib manual
15
15
After adding the references we can add the two les we need for this project.
We need to add both the DLL and the Hs2lib.cs le that was generated. By adding the DLL we can instruct MSBuild to copy the DLL to the output folder. This enables us to not have to set the Working directory of the project. First click on Hs2lib.DLL in the solution explorer and set its build action to Copy if newer.
examples
21
Figure 2.17: C# Hs2lib.DLL property Dialog In Program.cs add the following lines using using using using System; WinDLL.Generated; WinDLL.Utils; System.Runtime.InteropServices;
namespace CsArith { class Program { static void Main(string[] args) { Hs2lib.HsStart(); unsafe { IntPtr fooPtr = Marshal.AllocHGlobal(sizeof(int) * 3); int* foo = (int*)fooPtr.ToPointer(); foo[0] = 2; foo[1] = 5; foo[2] = 10; Console.WriteLine("Sum result: {0}", Hs2lib.summerize(3, foo, 3, foo)); Marshal.FreeHGlobal(fooPtr); int count; int* bar = Hs2lib.single(8, &count); Console.WriteLine("Single result: ({0})", count);
22
hs2lib manual
int i; for (i = 0; i < count; i++) Console.WriteLine("\t{0}", bar[i]); FFI.free(bar); } Hs2lib.HsEnd(); return; } } }
Before we can compile this code we need to enable unsafe code compilation in the compiler. We can do this by going into the projects property window.
After this we can nally run the program, and the result should be the same as before.
examples
23
2.2
This case studies deals with how to export and read user de ned data types using Hs2lib. As with all preceding examples, well only cover the C examples. The example used here is the evaluation of simple expressions. module Expr where data Expr
Add Expr Expr | Sub Expr Expr | Mul Expr Expr | Div Expr Expr | Parens Expr | Var Var | Val Int | Let String Expr Expr
type Var String type Env rpVar,Int qs -- @@ Export = eval -- | Evaluate an expression, throwing an exception if the variable is not found. foldExpr ::Expr Int foldExpr foldE r s where foldE ::Env Expr Int foldE env pAdd e1 e2 q pfoldE env e1 q pfoldE env e2 q foldE env pSub e1 e2 q pfoldE env e1 q pfoldE env e2 q foldE env pMul e1 e2 q pfoldE env e1 q pfoldE env e2 q foldE env pDiv e1 e2 q pfoldE env e1 q div pfoldE env e2 q foldE env pParens e q foldE env e foldE env pVar nm q case lookup nm env of Nothing error p"Variable " nm " could not be found."q Just val val foldE env pVal val q val
24
hs2lib manual
in foldE env 1 e This time while compiling were going to instruct Hs2lib to rename the project. This can be done by setting the default namespace16 PS C:\Examples> hs2lib .\Expr.hs -n Eval Linking main.exe ... Done. This creates a le Eval.DLL among other things. The exports of this DLL includes a function named eval. By specifying a name after the Export statement we can on an individual basis rename functions. Ill just assume that the user is familiar with C and no explain how to call the function. By looking at the prototype of the function its clear that its pretty straight forward. // eval :: Expr -> Int extern CALLTYPE(int) eval (Expr_t* arg1); struct Expr { enum ListExpr tag; union ExprUnion* elt; } Expr_t ;
16
This can be set with -n and will instead of using the name Hs2lib for output les, use the name supplied for all les.
2.3
Callbacks
Int
Figure 2.20: Example exportable Higher-order function The generated header le would contain the following de nition // myadd :: CBF1 -> Int -> Int extern CALLTYPE(int) myadd (CBF1_t arg1, int arg2); // Callback functions typedefs // type CBF1 = Int -> Int typedef __stdcall int (*CBF1_t)(int); This is again rather straight forward, the higher-ordered arguments just become function pointers. One important thing is, if you use any lists or IO action in the higher-ordered argument. (e.g. pInt r Int sq Int Int) then the argument must be in IO (e.g. pInt IO r Int sq Int IO Int).
examples
25
The reason is that creating and reading arrays to lists is an IO operation. If you omit this the tool will still compile, but be forced to use unsafePerformIO. Calling this function is very easy, the following code illustrated how. int __stdcall iadd(int i){ return i+i; } ... int ret = myAdd(iadd,8); printf("results 1: %ld\n", ret )
2.4
Sessions
One particular neat thing is that you can pin certain pieces of Haskell data in memory. This information is not accessible outside of Haskell and thus doesnt require any marshalling information to be generated. This is handy for when you need to implement session or caching in your haskell code. The following code example should explain it: type Context StablePtr pIORef q type Env Context r Int s -- @ Export initEnv::IO Env initEnv newStablePtr newIORef -- @ Export freeEnv::Env IO pq freeEnv freeStablePtr -- @ Export add::Env Int IO pq add env val do env_value deRefStablePtr env modifyIORef env_value ppval : q $! q -- @ Export = sumPrime sum 1 ::Env IO Int sum 1 env do env_value deRefStablePtr env current readIORef env_value return $ sum current main::IO Int main do env initEnv add env 1 add env 1
rs
26
hs2lib manual
add env 1 add env 1 val sum 1 env freeEnv env return val When called, initEnv will return a void pq which you cant do anything with but pass as arguments to the Haskell functions. This is used extensively in Visual Haskell itself.