libffiをVisual C++でビルドする

[libffi](https://sourceware.org/libffi/)をビルドするためには、[公式ドキュメントにも記載されているとおり](https://github.com/atgreen/libffi/blob/v3.2.1/README#L147-165)、`./configure`が動く環境が必要です。今回は、VC2010(32bit)とCygwin(32bit)を組み合わせて使うことにしました。

# ライブラリのビルド

まずは、VC2010のコマンドプロンプトを立ち上げます。そこから `C:\cygwin\bin\mintty -` を実行することで、`cl.exe`にパスが通った状態でCygwinを立ち上げます。

現時点のlibffiの最新版はv3.2.1ですが、そのままではVC2010ではコンパイルが通らないため、Ruby 2.2のソースに含まれる[パッチ](https://github.com/ruby/ruby/blob/ruby_2_2/ext/fiddle/win32/libffi-3.2.1-mswin.patch)を元にした[パッチ](https://gist.github.com/k-takata/c7a78f0c5fbbcb8365d0)を当てます。（v3.1であればパッチを当てずともビルドできます。）

```sh
$ git clone git://github.com/atgreen/libffi.git
$ cd libffi
$ git checkout -b v3.2.1-msvc v3.2.1
$ wget https://gist.githubusercontent.com/k-takata/c7a78f0c5fbbcb8365d0/raw/master/0001-Support-MSVC.patch
$ git am 0001-Support-MSVC.patch
$ ./autogen.sh
$ ./configure CC=$PWD/msvcc.sh CXX=$PWD/msvcc.sh CPP="cl -nologo -EP"
```

※64bitのCygwinを使う場合は、`./configure`に`--build=i686-pc-cygwin`オプションを指定する必要があります。

```sh
$ ./configure CC=$PWD/msvcc.sh CXX=$PWD/msvcc.sh CPP="cl -nologo -EP" --build=i686-pc-cygwin
```

`ffitarget.h`がCygwin形式のシンボリックリンクとして作成されますが、当然`cl.exe`からは読めないので、修正してからmakeします。

```sh
$ rm i686-pc-cygwin/include/ffitarget.h
$ echo '#include "../../src/x86/ffitarget.h"' > i686-pc-cygwin/include/ffitarget.h
$ make
```

これで `i686-pc-cygwin/.libs/libffi-6.dll` が出来ますが、シンボルがエクスポートされていないため使用できません。
スタティックリンク用のライブラリは、`i686-pc-cygwin/.libs/libffi_convenience.lib` が利用できます。

## スタティックリンク版の動作確認

`man/ffi_call.3` にあるサンプルコードを少し修正し、以下のコードを用意します。
（VC2010ではC99スタイルの変数宣言が使えないため、その修正をしてあります。）

```c:foo.c
#include <ffi.h>
#include <stdio.h>

unsigned char
foo(unsigned int, float);

int
main(int argc, const char **argv)
{
    ffi_cif cif;
    ffi_type *arg_types[2];
    void *arg_values[2];
    ffi_status status;
    unsigned int arg1;
    float arg2;

    // Because the return value from foo() is smaller than sizeof(long), it
    // must be passed as ffi_arg or ffi_sarg.
    ffi_arg result;

    // Specify the data type of each argument. Available types are defined
    // in <ffi/ffi.h>.
    arg_types[0] = &ffi_type_uint;
    arg_types[1] = &ffi_type_float;

    // Prepare the ffi_cif structure.
    if ((status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI,
        2, &ffi_type_uint8, arg_types)) != FFI_OK)
    {
        // Handle the ffi_status error.
    }

    // Specify the values of each argument.
    arg1 = 42;
    arg2 = 5.1;

    arg_values[0] = &arg1;
    arg_values[1] = &arg2;

    // Invoke the function.
    ffi_call(&cif, FFI_FN(foo), &result, arg_values);

    // The ffi_arg 'result' now contains the unsigned char returned from foo(),
    // which can be accessed by a typecast.
    printf("result is %hhu", (unsigned char)result);

    return 0;
}

// The target function.
unsigned char
foo(unsigned int x, float y)
{
    unsigned char result = x - y;
    return result;
}
```

以下のコマンドで、ビルド＆実行します。スタティックリンクする場合は、`-DFFI_BUILDING`が必要です。

```sh
$ cd i686-pc-cygwin
$ cl foo.c -Iinclude -MD -DFFI_BUILDING .libs/libffi_convenience.lib
$ ./foo.exe
result is 36
```

`result is 36`と表示されたので、どうやら動いているようです。

## ダイナミックリンク版

MSYS2 (MinGW-w64)でDLLをビルドし、出来たDLLから.defファイルを作成し、それを読み込ませることでシンボルをエクスポートします。.defファイルを読み込ませるためには、Makefileを修正する必要があります。

```diff
--- a/i686-pc-cygwin/Makefile
+++ b/i686-pc-cygwin/Makefile
@@ -1149,7 +1149,7 @@
 	src/vax/$(DEPDIR)/$(am__dirstamp)
 
 libffi.la: $(libffi_la_OBJECTS) $(libffi_la_DEPENDENCIES) $(EXTRA_libffi_la_DEPENDENCIES) 
-	$(AM_V_CCLD)$(libffi_la_LINK) -rpath $(toolexeclibdir) $(libffi_la_OBJECTS) $(libffi_la_LIBADD) $(LIBS)
+	$(AM_V_CCLD)$(libffi_la_LINK) -rpath $(toolexeclibdir) $(libffi_la_OBJECTS) $(libffi_la_LIBADD) $(LIBS) '-Wl,-link -def:libffi.def'
 
 libffi_convenience.la: $(libffi_convenience_la_OBJECTS) $(libffi_convenience_la_DEPENDENCIES) $(EXTRA_libffi_convenience_la_DEPENDENCIES) 
 	$(AM_V_CCLD)$(LINK)  $(libffi_convenience_la_OBJECTS) $(libffi_convenience_la_LIBADD) $(LIBS)
```

MSYS2でビルドします。

```sh:MSYS2
$ ./configure --build=i686-pc-mingw32
$ cd i686-pc-mingw32
$ make
```

出来たDLLからシンボルを抽出し、.defファイルを作成します。VC2010の場合は、

```sh:Cygwin
$ dumpbin -exports i686-pc-mingw32/.libs/libffi-6.dll | awk 'BEGIN {print "EXPORTS"} $3 ~ /^[0-9A-F]+$/{if($4 ~ /longdouble|complex/){$4=";" $4} print "\t", $4}' > i686-pc-cygwin/libffi.def
```

VC2013以降の場合は、

```sh:Cygwin
$ dumpbin -exports i686-pc-mingw32/.libs/libffi-6.dll | awk 'BEGIN {print "EXPORTS"} $3 ~ /^[0-9A-F]+$/{if($4 ~ /longdouble/){$4=";" $4} print "\t", $4}' > i686-pc-cygwin/libffi.def
```

VCでビルドします。

```sh:Cygwin
$ cd i686-pc-cygwin
$ rm libffi.la   # リンクを再実行させるため
$ make
```

# 64bitの場合

まずは、VC2010(64bit)のコマンドプロンプトを立ち上げます。そこから `C:\cygwin64\bin\mintty -` を実行し、`cl.exe`にパスが通った状態でCygwinを立ち上げます。

```sh
$ git clone git://github.com/atgreen/libffi.git
$ cd libffi
$ git checkout -b v3.2.1-msvc v3.2.1
$ wget https://gist.githubusercontent.com/k-takata/c7a78f0c5fbbcb8365d0/raw/master/0001-Support-MSVC.patch
$ git am 0001-Support-MSVC.patch
$ ./autogen.sh
$ ./configure CC="$PWD/msvcc.sh -m64" CXX="$PWD/msvcc.sh -m64" CPP="cl -nologo -EP"
$ rm x86_64-unknown-cygwin/include/ffitarget.h
$ echo '#include "../../src/x86/ffitarget.h"' > x86_64-unknown-cygwin/include/ffitarget.h
$ make
```