HaskellでのSDLの使い方

SDLHaskellバインディングはいくつかありますが、この記事ではHackageのSDLを使って説明しています。Windows環境だとCabalではうまく入らなかったりするので、直接ソースからビルドする必要があるかもしれません。

SDLを使うにはFFIが必要

SDLを使ったHaskellコードを単純に

$ ghc -package SDL --make Main.hs

のようにコンパイルしても、SDL_mainが見つからないなどのエラーが出てコンパイルできません。SDLを使ったプログラムでは本来、ユーザが作成したmain関数はSDL.hのマクロによってSDL_mainにリネームされ、SDLライブラリの内部にある本当のmain関数がSDL_mainを呼び出すことになってるのですが、ghcはその面倒まで見てくれないのでSDL_mainが作られず、エラーになってしまいます。

これを回避するためには、C言語で書いたmainの中からFFIHaskellを呼び出した上で、元のmainをSDL.hでSDL_mainに書き換えさせる必要があります。

Haskellの関数をエクスポートする

まずは画面を表示するだけのHaskellコードを書いて、Cのmainから呼び出すためのhs_mainをエクスポートします。

{-# LANGUAGE ForeignFunctionInterface #-}
module HsMain where

import qualified Graphics.UI.SDL as SDL

foreign export ccall "hs_main" hs_main :: IO ()

hs_main :: IO ()
hs_main = do
    SDL.init [SDL.InitEverything]
    SDL.setCaption "SDL test" ""
    SDL.setVideoMode 300 300 32 []

    mainLoop
    SDL.quit

mainLoop :: IO ()
mainLoop = do
    e <- SDL.waitEvent
    case e of
        SDL.Quit -> return ()
        _ -> mainLoop

ここで、このコードをそのまま

$ ghc --make HsMain.hs

のようにコンパイルしようとすると、HsMain_stub.cとHsMain_stub.hというファイルが生成されます。これがHaskellコードをCから呼び出すためのインターフェースになります。

Cから呼び出す

次に、このインターフェースを通してHaskellコードを呼び出すCコードを用意します。

#include <SDL.h>

#include "HsFFI.h"
#ifdef __GLASGOW_HASKELL__
#include "HsMain_stub.h"
extern void __stginit_HsMain(void);
#endif

int main(int argc, char* argv[]) {
	hs_init(&argc, &argv);

#ifdef __GLASGOW_HASKELL__
	hs_add_root(__stginit_HsMain);
#endif

	hs_main();
	hs_exit();
	return 0;
}

最後に、これらを合わせて

$ ghc -package SDL --make -no-hs-main HsMain.hs c_main.c

のようにコンパイルすると、実行ファイルを生成することが出来ます。

実行結果


GHCFFIに関する詳しいドキュメントはこの辺にあります。