Go で各 OS 向けライブラリをビルドして使う

# モチベーション
![STORM.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/14966/7048093c-95c0-dbf8-5538-b1b453b1c532.jpeg)

# メリット
- 一回書けば Win/Mac 両方で動作する（Write once, run anywhere）
  - Java と違い各プラットフォームネイティブのバイナリが出力される
- ネイティブのバイナリが出力されるので Java より高速
- 実装が統一される

# デメリット
主として Windows 側に集中しています。

- Windows でのビルド方法がやや特殊
  - ひと手間（非公式の手段）かける必要性があり若干の懸念あり
  - これは、Go が Windows 向けの共有ライブラリを生成できない（`buildmode=c-shared` を非サポート）のと、C# が Win32 形式での静的ライブラリを利用できないためです。
- Windows（と言うか C# というか CLR）では C# で書くよりパフォーマンスの懸念あり
  - アンマネージド DLL の呼出しなのでマーシャリングのコストが余計にかかるのではとの予想です。
  - マルチプラットフォーム対応でなく速度重視なら、おとなしく C#（というか CLR で動作する言語）で書いた方が高速だと思います。


# Windows

呼び出し側は C# をターゲットにしています。

## Go ライブラリのコード

```rest.go
package main

import (
	"C"
	"fmt"

	"gopkg.in/resty.v0"
)

//export GetUsers
func GetUsers(p *C.char) *C.char {
	fmt.Println(C.GoString(p))
	resp, err := resty.R().
		SetHeader("Accept", "application/json").
		Get("https://jsonplaceholder.typicode.com/users")
	if err != nil {
		return C.CString("")
	}

	return C.CString(resp.String())
}

func init() {}
func main() {}
```

適当なウェブサービスを呼び出して、その結果を JSON で受け取り文字列として返却する関数です。

- [関数のコメント重要](https://golang.org/cmd/cgo/#hdr-C_references_to_Go)
  - ここでエクスポートを宣言している（cgo）
- C 言語を意識する
  - データ型の変換に注意

## Go ライブラリのビルド

この手順は公式の方法です。

```
go build -buildmode=c-archive rest.go
```

## DLL のビルド

この手順は非公式な方法です。
以下のツールをインストールしてください。

- [TDM-GCC](http://tdm-gcc.tdragon.net/download)
  - 環境変数 `PATH` に `bin/` のパスを通してください。
  - 一時的に設定する場合は `set PATH=%PATH%;C:\TDM-GCC-64\bin` です。
- [Visual Studio Community](https://www.microsoft.com/ja-jp/dev/products/community.aspx)
  - インストール後に C++ のプロジェクトを作成して関連ツールをインストールしてください。

まずモジュール定義 (.def) ファイルを作成します。

```rest.def
LIBRARY   rest
EXPORTS
   GetUsers
```

関数は `EXPORTS` 以下に追加していきます。

DLL をビルドします。

```
gcc -m64 -shared -o rest.dll rest.def rest.a -Wl,--allow-multiple-definition -static -lstdc++ -lwinmm -lntdll -lWs2_32
```

[Golang で Windows の DLL を作る方法](http://mattn.kaoriya.net/software/lang/go/20160921010820.htm)

## C# から DLL を呼び出す

### Visual Studio での注意

- IDE の参照メニューからは DLL への参照を追加できないので（アンマネージドだから？）、実行ファイル（`.exe`）があるフォルダ（`Debug`）にビルドした DLL をコピーします（実行パスにある DLL はデフォルトで読み込まれる動作を利用）。
- プラットフォーム構成の種類が `Any CPU` だと実行時例外が発生するので、 `x64` に変更します（DLL が  `x64` 向けにビルドされているので）。


```Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SampleGoDLL
{
    static class Program
    {
        [DllImport("rest.dll")]
        public static extern void PrintHello(string pStr);
        [DllImport("rest.dll")]
        public static extern IntPtr GetUsers(string pStr);

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            IntPtr pRet = GetUsers("Call from C#");
            String json = Marshal.PtrToStringAnsi(pRet);
            Console.Write(json);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
```

アプリケーションの起動時に実行されるようにしました。
関数の外部参照宣言（`extern`）毎に `DLLImport` 属性が必要です。
C# に関する詳細はインターネット上の情報を参照してください。

## その他
### DLL の検証

`C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\x86_amd64` を環境変数 `PATH` に追加しておくと便利です。

- DLL で宣言されている関数の確認する
  - `dumpbin.exe /exports rest.dll`
- DLL のターゲットアーキテクチャを確認する
  - `dumpbin.exe /headers rest.dll | findstr machine`

# macOS

呼び出し側は Objective-C をターゲットにしています。
Go ライブラリのコードおよび Go ライブラリのビルドは Windows と共通です。
macOS の場合は共有ライブラリの出力も可能ですが、Windows に合わせて静的ライブラリを出力します。

## Objective-C から静的ライブラリを呼び出す
### Xcode のプロジェクト設定

Xcode の詳しい使い方はインターネット上の情報を参照してください。

- 静的ライブラリ（`rest.a`）を追加します。
- ヘッダーファイル（`rest.h`）を追加します。

```ViewController.m
#import "ViewController.h"
#import "rest.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.
    char *pStr = GetUsers("Call from Objective-C");
    NSLog(@"%s", pStr);
}
@end
```

ビューのロード時に実行されるようにしました。
ポイントはヘッダーファイルのインポートです。

# 最後に
Go は高速性とか並列性とかが注目されがちですが、こういう便利な機能ももっと注目されて良いと思います。