先日、覚え書きとして「ライブラリを作成、使用する」を書きましたが、同じ事を Microsoft Windows と Microsoft Visual Studio で行うためのメモです。
環境は Microsoft Windows 7 Professional 日本語版 32bit / Microsoft Visual Studio 2010 Professional です。
Linux では共有ライブラリと静的ライブラリでしたが、Windows の世界ではダイナミックライブラリ(.dll)と 共通オブジェクトファイル COFF(.lib)があるようです。後者は、Microsoft の文書中で「COFF形式の標準ライブラリ」とも表記されていますが、C 標準ライブラリとは無関係で、Linux で言う静的ライブラリの事を指すようです。
| Linux | Windows 標準コンパイラ | gcc | cl.exe オブジェクトファイル | .o | .obj 静的ライブラリ | .a | .lib 共有ライブラリ | .so | .dll インポートライブラリ | | .lib エクスポートライブラリ | | .exp エクスポート定義 | GCC version script | .def ライブラリ検索パス | 可変 | 強制 バージョン管理 | 環境変数、リンク | Side by Side
DLL だけでも、レギュラー DLL、MFC に静的リンクしたレギュラー DLL、MFC に動的にリンクしたレギュラーDLL、MFC 拡張 DLL、COM をサポートした DLL(OLE/ActiveX/COM+)、Microsoft .NET Framework 向けのマネージド DLL など、様々な種類や実装方法があるようですが、最も純粋なレギュラーDLLをベースに書きたいと思います。
作業に取り掛かる前に、開発環境に必要な環境変数を定義する必要があります。Visual Studio をインストールすると、環境変数「VS100COMNTOOLS」が定義されます。これは名前のとおり、Visual Studio 2010 の共通ツールへのパスが定義されています。標準では「C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools」というパスになっているはずです。Visual Studio 2008 や 2005 でも VS**COMNTOOLS という環境変数が定義されるはずですので、読み替えればOKだと思います。
コマンドプロンプトを立ち上げ、次のようにタイプします。
> "%VS100COMNTOOLS%\vsvars32.bat" Setting environment for using Microsoft Visual Studio 2010 x86 tools.
これで開発に必要なツールに PATH が通り、開発ができるようになります。(なお、%VS100COMNTOOLS% には標準で空白文字が含まれているので、上記のようにダブルクォートで囲ってあげないと、変数展開後にパスを見失なってしまうので注意が必要です。vsvars32.bat で定義された環境変数の寿命は、コマンドプロンプトのセッション内ですので、コマンドプロンプトを一度閉じてしまったら、再度、上記のコマンドを実行する必要があります。)
前回と同様、サンプルとして次の func.c を準備します。
[cpp]
int add(int a, int b)
{
return a+b;
}
[/cpp]
二つの整数値を足して、その値を返すだけの簡単な関数 add() を定義しています。
オブジェクトファイルの静的リンク
前回と同様、Microsoft の C コンパイラ cl.exe も -c オプション(コンパイルのみ)をサポートしています。 Microsoft 流儀で言うと /c オプションとなりますが、このオプションはハイフン(-)でもスラッシュ(/)でも通るようです。実際に func.c をビルドすると、静的ライブラリ func.obj が作成できます。gcc では func.o ですが、拡張子が DOS 風に3文字となっています。
> cl -c func.c Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. func.c
Microsoft の著作権情報は /nologo オプションで非表示にすることができます。コマンドラインが長くなりますが… :(
呼び出し元から静的リンクするには、次のように一緒にビルドするだけです。ここはほとんど gcc と変わりませんね。
> cl /out:app.exe app.c func.obj Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. app.c Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. /out:app.exe app.obj func.obj > .\app.exe a+b=8
gcc の -o オプションは cl だと /out: になります。省略すると、最初に指定されたファイル名に拡張子 exe をつけたものを吐いてくれますが、ビルドするファイルが多い場合や処理を自動化した場合、予期せぬファイル名を自動的につけて出力する危険性があるので、/out: オプションはなるべく省略しない方がいいと思います。
ちなみに、コマンドプロンプトから実行ファイルの指定を確実にするには、
> .\app.exe
と相対パスで実行する方が無難でしょう。
ダイナミックリンクライブラリを動的リンク
共有ライブラリのようなものとして、ダイナミックリンクライブラリ(DLL)という実装があります。これは、前回の Linux と gcc をやった時のようにコンパイルオプション一つ変更するだけで完成する訳ではなく、専用の記述やファイルを準備しなければなりません。
func.c を次のように修正します。
[cpp]
__declspec(dllexport)
int add(int a, int b)
{
return a+b;
}
[/cpp]
__declspec(dllexport) が関数宣言の前につきました。これは、ライブラリが公開する関数を制御する為に用いられるものらしいです。前回に比べればちょっと面倒くさいですが、そういうものらしいです。
これをビルドします。
>cl /LD func.c Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. func.c Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. /out:func.dll /dll /implib:func.lib func.obj ライブラリ func.lib とオブジェクト func.exp を作成中
オプションは /LD です。大文字・小文字を判別しますので注意してください。上手くビルドされると、4つのファイルが生成されます。
func.dll ... DLL ファイル。 func.exp ... エクスポートライブラリ。循環参照の解決に用いられるらしい。 func.lib ... インポートライブラリ。外部から DLL を参照する際に用いられる。 func.obj ... オブジェクトファイル。
これで DLL ファイルが完成しました。
次は、DEF ファイルを用いて作成してみます。DEF ファイルを用いると、オリジナルのソースコードをライブラリ化の為に変更しなくていいので、func.c を最初のサンプルのように戻します。
[cpp]
int add(int a, int b)
{
return a+b;
}
[/cpp]
そして func.def という新しいファイルを作成し、下記のように入力します。
LIBRARY func EXPORTS add @1
add 関数を序数1で「エクスポート(つまり公開)」する、という意味らしいです。
DEF ファイルを用いてビルドするには次のコマンドを実行します。
>cl /LD /DEF:func.def func.c Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. func.c Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. /out:func.dll /dll /implib:func.lib func.obj
DEF ファイルが、gcc でいうバージョンスクリプト的なものなら、なんとなく理解できる仕様です。ソースコードと DEF ファイルの管理が煩雑にならないか注意する必要がありますね。
さて、生成された DLL ファイルを利用するには、app.c も改変します。
[cpp]
include <stdio.h>
__declspec(dllimport) int add(int, int);
int main()
{
printf("a+b=%d\n", add(3, 5));
return 0;
}
[/cpp]
dllimport がついた関数宣言を追加しました。ただし、前回と同様に必須ではなく、MSDNでの解説曰く、
__declspec(dllimport) を使用しなくてもコードは正しくコンパイルされますが、使用した方がコードがより洗練されます。__declspec(dllimport) を使うと、ある関数が DLL 内にあるかないかをコンパイラが判断できるようになり、その結果、コードでは通常 DLL の境界を越える関数呼び出し内に存在する間接操作のレベルをスキップできます。ただし、DLL 内に変数をインポートするには、__declspec(dllimport) を必ず使用する必要があります。
との事です。
さらにビルド時には、インポートライブラリを渡してやらなければなりません。func.lib を指定していますが、この場合は共通オブジェクトファイルとは全く別のものなので注意が必要です。
> cl app.c func.lib Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. app.c Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. /out:app.exe app.obj func.lib E:\tmp>app.exe a+b=8
これで実行バイナリ app.exe が生成されます。
ちなみに、インポートライブラリを使わずに DEF ファイルを用いて「インポート」する方法もあるらしいです。