C++ は使わずに、C言語とCRT(Cランタイムライブラリ)とWin32APIだけを用いて、コンソール/Windows/スタティックライブラリ/DLL を、コマンドラインからコンパイルして作成する方法のまとめ。UNICODE対応は使わない。
対象:
> bcc32 Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland (...) > ilink32 Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland (...)
分割コンパイルを行わない、一番簡単なパターンをまとめる。
特にオプションを指定しない場合は、Win32コンソールアプリケーションとしてコンパイルされ、"main()"関数がエントリポイントになる。
HelloWorld_cui.c:
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
コンパイル&実行:
> bcc32 HelloWorld_cui.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland helloworld_cui.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > dir /b HelloWorld_cui.* HelloWorld_cui.c helloworld_cui.exe helloworld_cui.obj helloworld_cui.tds > HelloWorld_cui.exe Hello, World!
TDUMPで確認すると、Subsystemが確かにコンソールアプリケーションになっている:
> tdump -e HelloWorld_cui.exe (...) Subsystem 0003 [ Windows character ] (...)
コンソールアプリであることを明示したい場合は、-WCオプションをつけてコンパイルする。
> bcc32 -WC HelloWorld_cui.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland helloworld_cui.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > helloworld_cui.exe Hello, World!
-Wオプションを付けるとWindowsアプリケーションとしてコンパイルされ、"WinMain()"関数がエントリポイントになる。
HelloWorld_gui.c:
#include <windows.h> int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MessageBoxA( NULL, "Hello, Win32 GUI Applications!", "Hello, World!", MB_OK); return 0; }
コンパイル&実行:
> bcc32 -W HelloWorld_gui.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland helloworld_gui.c: 警告 W8057 helloworld_gui.c 16: パラメータ 'hInstance' は一度も使用されない(関数 WinMain ) 警告 W8057 helloworld_gui.c 16: パラメータ 'hPrevInstance' は一度も使用されない(関数 WinMain ) 警告 W8057 helloworld_gui.c 16: パラメータ 'lpCmdLine' は一度も使用されない(関数 WinMain ) 警告 W8057 helloworld_gui.c 16: パラメータ 'nCmdShow' は一度も使用されない(関数 WinMain ) Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > dir /b HelloWorld_gui.* HelloWorld_gui.c helloworld_gui.exe helloworld_gui.obj helloworld_gui.tds > HelloWorld_gui.exe (メッセージボックス表示)
TDUMPで確認すると、Subsystemが確かにWindowsアプリケーションになっている:
> tdump -e HelloWorld_gui.exe (...) Subsystem 0002 [ Windows GUI ] (...)
※スタティックライブラリを生成するにはTLIBコマンドを使う為、「複数ファイル分割」で紹介。
-WDオプションを指定するとDLLをターゲットにしてコンパイルする。
※BCC32はデフォルトでC呼び出し規約(cdecl)でコンパイルする。stdcall呼び出し規約でエキスポートする関数には明示的に "__stdcall" を指定する必要がある。(それか"-ps"でデフォルトをstdcallにする。)
libdllfoobarbaz.c:
int __stdcall __declspec(dllexport) foo(int a, int b) { return a + b; } int __stdcall __declspec(dllexport) bar(int a, int b) { return a * b; } int __stdcall baz(int a, int b) { return a - b; }
コンパイル:
> bcc32 -WD libdllfoobarbaz.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland libdllfoobarbaz.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > dir /b libdllfoobarbaz.* libdllfoobarbaz.c libdllfoobarbaz.dll libdllfoobarbaz.obj libdllfoobarbaz.tds
インポートライブラリまでは作成されない。IMPDEFとIMPLIBコマンドを使ったインポートライブラリの生成については、「複数ファイル分割」で紹介する。
TDUMPコマンドでエクスポートを調べると、foo(), bar()関数がエクスポートされているのを確認出来た:
> tdump -ee libdllfoobarbaz.dll Turbo Dump Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation Display of File LIBDLLFOOBARBAZ.DLL EXPORT ord:0003='___CPPdebugHook' EXPORT ord:0001='bar' EXPORT ord:0002='foo'
※「複数ファイル分割」で紹介。
usedll_explicit.c:
#include <stdio.h> #include <windows.h> typedef int (CALLBACK* lp_foo)(int, int); typedef int (CALLBACK* lp_bar)(int, int); int main() { HINSTANCE hDll; lp_foo foo; lp_bar bar; hDll = LoadLibrary("libdllfoobar"); if (NULL == hDll) { printf("dll load error.\n"); return 1; } foo = (lp_foo)GetProcAddress(hDll, "foo"); if (!foo) { printf("GetProcAddress error(foo).\n"); FreeLibrary(hDll); return 1; } bar = (lp_foo)GetProcAddress(hDll, "bar"); if (!bar) { printf("GetProcAddress error(bar).\n"); FreeLibrary(hDll); return 1; } printf("foo(2, 3) = %d\n", foo(2, 3)); printf("bar(2, 3) = %d\n", bar(2, 3)); return 0; }
コンパイル&実行:
> bcc32 usedll_explicit.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland usedll_explicit.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > usedll_explicit.exe foo(2, 3) = 5 bar(2, 3) = 6
複数ファイルに分割するパターンをまとめる。
ILINK32.EXEで手動でリンクする場合、スタートアップモジュールやライブラリファイルを手動で指定する必要がある。ILINK32.EXEのコマンドラインオプションは以下の形式になっている。
ILINK32 [@respfile][options] startup myobjs, [exe], [mapfile], [libraries], [deffile], [resfile]
"@respfile", "mapfile", "deffile", "resfile"の解説は省略する。
"options"では、/c (シンボルで大文字と小文字を区別)オプションを忘れずに指定すること。
またターゲット指定のオプションを以下に簡単にまとめる。
/aa | 32 ビット Windows アプリケーションを構築する |
/ad | 32 ビット Windows デバイスドライバを構築する |
/ap | 32 ビット Windows コンソールアプリケーションを構築する |
/Tpd | Windows の .DLL ファイルをターゲットにする |
/Tpe | Windows の .EXE ファイルをターゲットにする |
"startup", "myobjs"はOBJファイルを列挙する。startupについてはBCCの方でアプリの種類に応じたOBJファイルを用意しているので、それを指定する。主なものを以下に示す。"w"が付くとワイド文字版になる。
c0x32(w).obj | 32bitコンソールアプリ用EXEスタートアップモジュール |
c0w32(w).obj | 32bitWindowsGUIアプリ用EXEスタートアップモジュール |
c0d32(w).obj | 32bitDLL用スタートアップモジュール |
詳細は本記事末尾に載せた参考URL参照。
なお、ファイル名の拡張子部分 ".obj" は省略出来る。
> ilink32 ... c0x32.obj main.obj foo.obj bar.obj ...
と、
> ilink32 ... c0x32 main foo bar ...
は同じ意味になる。
また、カンマ(,)は省略できない点に注意すること。例えばEXEファイル名を指定するには次のようにカンマで区切る。
> ilink32 ... c0x32.obj main.obj foo.obj, myexe.exe, ...
カンマを省略してしまうと、OBJファイルと見なされてしまう。
> ilink32 ... c0x32.obj main.obj foo.obj myexe.exe, ... → Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland Fatal: ファイル MYEXE.EXE が開けません
これは myexe.exe がリンクすべきOBJファイルとして扱われた事が原因。
同様に、マップファイルを省略したい場合もカンマは省略しない。
> ilink32 ... c0x32.obj main.obj foo.obj, myexe.exe, , foo.lib bar.lib, ...
なおターゲットを指定するコマンドラインオプションに応じて、拡張子(".EXE"/".DLL")は省略可能。
最後に、ILINK32で手動リンクする場合はランタイムライブラリを手動で指定する必要がある。本記事で扱う単純なプログラムでは、次の二つを指定すればよい。
CW32.LIB : シングルスレッドのRTL(ランタイムライブラリ) IMPORT32.LIB : APIのインポートライブラリ
OBJファイルと同様、拡張子の".lib"は省略可能。
> ilink32 ... , cw32.lib import32.lib = > ilink32 ... , cw32 import32
マルチスレッドやDirectX, OLE, WININET, WinSock2などを扱う場合は、それぞれ専用のRTLやインポートライブラリが提供されているのでそれを指定する。詳細は本記事末尾に載せた参考URL参照。
DLLまでは、次の foo.c, bar.c を使い回す。
foo.c:
int foo(int a, int b) { return a + b; }
bar.c:
int bar(int a, int b) { return a * b; }
BCC32で-cオプションを指定すると、リンクはせずにOBJファイルのコンパイルまでを行う。
> bcc32 -c foo.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland foo.c: > bcc32 -c bar.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland bar.c:
OBJファイル名を指定したい場合は、 "-oファイル名" オプションを指定する。
> bcc32 -c -obaz.obj bar.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland bar.c: > dir /b *.obj bar.obj baz.obj foo.obj
/TpeでWindowsのEXEをターゲットとし、"/ap"でWin32コンソールアプリケーションを指定する。
スタートアップモジュールは32bitコンソールのEXE用ということで、c0x32.objを使う。
foobar_cui.c:
#include <stdio.h> extern int foo(int a, int b); extern int bar(int a, int b); int main() { printf("foo(2, 3) = %d\n", foo(2, 3)); printf("bar(2, 3) = %d\n", bar(2, 3)); return 0; }
コンパイル:
> bcc32 -c foobar_cui.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland foobar_cui.c:
リンク&実行:
> ilink32 /c /ap /Tpe c0x32.obj foobar_cui.obj foo.obj bar.obj, foobar_cui.exe, , cw32 import32 Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > foobar_cui.exe foo(2, 3) = 5 bar(2, 3) = 6
TDUMPで確認すると、Subsystemが確かにコンソールアプリケーションになっている:
> tdump -e foobar_cui.exe (...) Subsystem 0003 [ Windows character ] (...)
参考としてBCC32でリンク&実行させる場合を以下に示す。
> bcc32 -efoobar_cui2.exe -WC foobar_cui.obj foo.obj bar.obj Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > foobar_cui2.exe foo(2, 3) = 5 bar(2, 3) = 6
/TpeでWindowsのEXEをターゲットとし、"/aa"でWin32Windowsアプリケーションを指定する。
スタートアップモジュールは32bitGUIのEXE用ということで、c0w32.objを使う。
foobar_gui.c:
#include <windows.h> #include <stdio.h> extern int foo(int a, int b); extern int bar(int a, int b); int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char buf[200]; sprintf(buf, "foo(2, 3) = %d\n", foo(2, 3)); MessageBoxA(NULL, buf, "foobar", MB_OK); sprintf(buf, "bar(2, 3) = %d\n", bar(2, 3)); MessageBoxA(NULL, buf, "foobar", MB_OK); return 0; }
コンパイル:
> bcc32 -c foobar_gui.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland foobar_gui.c: 警告 W8057 foobar_gui.c 22: パラメータ 'hInstance' は一度も使用されない(関数 WinMain ) 警告 W8057 foobar_gui.c 22: パラメータ 'hPrevInstance' は一度も使用されない(関数 WinMain ) 警告 W8057 foobar_gui.c 22: パラメータ 'lpCmdLine' は一度も使用されない(関数 WinMain ) 警告 W8057 foobar_gui.c 22: パラメータ 'nCmdShow' は一度も使用されない(関数 WinMain )
リンク&実行:
> ilink32 /c /aa /Tpe c0w32 foobar_gui foo bar, foobar_gui, , cw32 import32 Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > foobar_gui.exe ("foo(2, 3) = 5", "bar(2, 3) = 6" というメッセージボックスが順に表示される)
TDUMPで確認すると、Subsystemが確かにWindowsアプリケーションになっている:
> tdump -e foobar_gui.exe (...) Subsystem 0002 [ Windows GUI ] (...)
参考としてBCC32でリンク&実行させる場合を以下に示す。
> bcc32 -efoobar_gui2.exe -W foobar_gui.obj foo.obj bar.obj Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > foobar_gui2 ("foo(2, 3) = 5", "bar(2, 3) = 6" というメッセージボックスが順に表示される)
スタティックライブラリを管理するにはTLIB.EXEを使う。
コマンドラインオプションは次のような形になっている。
tlib [@レスポンスファイル] [オプション] ライブラリ名 [演算子] [,リストファイル]
"@レスポンスファイル"については省略。主なオプションは以下の通り。
/a : ファイルをライブラリに追加する。 /d : ファイルをライブラリから削除する。 /u : ライブラリ中のファイルを更新する。 /e : ライブラリからファイルを抽出する。
これらのオプションの代わりに、対象ファイル名の頭に演算子を付けることで操作を指定することもできる。
+foo : foo.objをライブラリに追加する。 -bar : bar.objをライブラリから削除する。 *baz : baz.objをライブラリから抽出する。 -*foobar or *-foobar : foobar.objをライブラリから抽出&ライブラリから削除する。 -+barbaz : barbaz.objを更新する。
".obj" および ".lib" の拡張子は省略出来る。
LIBファイルの内容(リスト)を調べたい時は、", リストファイル"を使う。カンマは省略出来ない。
tlib mylib.lib , mylib_list → mylib_list.LST にmylib.libの内容が出力される。
foo.obj, bar.objをスタティックライブラリ"foobar.lib"にまとめ、リストファイルに出力して内容を確認してみる:
> tlib foobar /a foo bar TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > tlib foobar , foobar_list TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > type foobar_list.LST Publics by module bar size = 11 _bar foo size = 11 _foo
"+"演算子でfoo.obj, bar.objの順で追加していくサンプル:
> tlib foobar.lib +foo.obj TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > tlib foobar.lib , foobar_list.LST TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > type foobar_list.LST Publics by module foo size = 11 _foo #続いてbar.objを追加してみる: > tlib foobar.lib +bar.obj TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > tlib foobar.lib , foobar_list.LST TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > type foobar_list.LST Publics by module bar size = 11 _bar foo size = 11 _foo
foobar_cui.cをリネームしてfoobar_usestatic.c とし、これをコンパイル&foobar.libとリンクしてみる。
コンパイル:
> bcc32 -c foobar_usestatic.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland foobar_usestatic.c:
リンク&実行:ライブラリファイルにfoobar.libを追加指定。
> ilink32 /c /ap /Tpe c0x32.obj foobar_usestatic.obj, foobar_usestatic.exe, , cw32 import32 foobar Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > foobar_usestatic.exe foo(2, 3) = 5 bar(2, 3) = 6
参考としてBCC32でリンク&実行させる場合を以下に示す。
> bcc32 -efoobar_usestatic2.exe -WC foobar_usestatic.obj foobar.lib Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland > foobar_usestatic2.exe foo(2, 3) = 5 bar(2, 3) = 6
ファイル単体で使ったlibdllfoobarbaz.cを再利用する。BCC32.EXEで一気にDLLを作成するのではなく、ILINK32.EXEに分けてみる。
ただし"__stdcall"は削除し、代わりにコンパイル時に"-ps"オプションを指定しデフォルトをstdcall呼び出し規約にする。
libdllfoobarbaz.c:
int __declspec(dllexport) foo(int a, int b) { return a + b; } int __declspec(dllexport) bar(int a, int b) { return a * b; } int __stdcall baz(int a, int b) { return a - b; }
コンパイル:
> bcc32 -c -ps libdllfoobarbaz.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland libdllfoobarbaz.c:
続いて手動でILINK32.EXEを実行してDLLを作成する。
/TpdでWindowsのDLLをターゲットとし、"/aa"でWin32Windowsアプリケーションを指定する。
スタートアップモジュールは32bitのDLL用ということで、c0d32.objを使う。
> ilink32 /c /aa /Tpd c0d32.obj libdllfoobarbaz.obj, libdllfoobarbaz.dll, , cw32 import32 Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
インポートライブラリまでは作成されない。
TDUMPコマンドでエクスポートを調べると、foo(), bar()関数がエクスポートされているのを確認出来た:
> tdump -ee libdllfoobarbaz.dll Turbo Dump Version 5.0.16.12 Copyright (c) 1988, 2000 Inprise Corporation Display of File LIBDLLFOOBARBAZ.DLL EXPORT ord:0003='___CPPdebugHook' EXPORT ord:0001='bar' EXPORT ord:0002='foo'
暗黙的なリンクを行うにはインポートライブラリが必要である。BCC32.EXE, ILINK32.EXEでDLLをビルドしてもインポートライブラリは作成されない。IMPLIB.EXEにより手動生成する必要がある。
インポートライブラリというのは、明示的リンク(explicit link)で使ったLoadLibrary()やGetProcAddress()をラップし、DLLが公開している関数名だけでアクセス出来るようにしてくれるライブラリである。
非常に大雑把に描くと以下のようなイメージになる。
クライアント(=アプリケーション) foo(2, 3) ↓ インポートライブラリ(*.lib) h = LoadLibrary("xxxxyyyy"); f = GetProcAddress(h, "foo"); →→ xxxxyyyy.dll : "foo"関数 return f(2, 3);
IMPLIB.EXEでは、DLLまたはモジュール定義ファイルを入力として、LIBファイルを出力する。モジュール定義ファイルは手動でインポートライブラリに取り込みたい関数を指定したい場合や、C++の静的メソッドを別名にして取り込みたい場合に使用する。
IMPDEF.EXEを使うと雛形となるモジュール定義ファイルを自動的に生成出来る。
インポートライブラリにとって知りたい情報としてはDLLのファイル名とエキスポートされた関数があればよいため、定義ファイルがあればDLL本体は不要となる。
今回は、上で作成したlibdllfoobarbaz.dllを入力としてみる。
> implib libdllfoobarbaz.lib libdllfoobarbaz.dll Borland Implib Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation > tlib libdllfoobarbaz.lib , dll.LST TLIB 4.5 Copyright (c) 1987, 1999 Inprise Corporation > type dll.LST Publics by module ___CPPdebugHook size = 0 ___CPPdebugHook bar size = 0 bar foo size = 0 foo
ここまでで材料は揃ったので、暗黙的なリンクを使ってWin32コンソール/Windowsアプリケーションをビルドしてみる。
まずはWin32コンソールアプリケーションから:foobar_usedll_cui.c
#include <stdio.h> /* "__stdcall" を付けないと、インポートライブラリのシンボルを 参照出来ず外部シンボル未解決となる */ extern int __stdcall foo(int a, int b); extern int __stdcall bar(int a, int b); int main() { printf("foo(2, 3) = %d\n", foo(2, 3)); printf("bar(2, 3) = %d\n", bar(2, 3)); return 0; }
BCC32でコンパイル&実行:(途中メッセージは省略)
> bcc32 -c foobar_usedll_cui.c > bcc32 -WC foobar_usedll_cui.obj libdllfoobarbaz.lib > foobar_usedll_cui.exe foo(2, 3) = 5 bar(2, 3) = 6
ILINK32でリンク&実行::(途中メッセージは省略)
> ilink32 /c /ap /Tpe c0x32.obj foobar_usedll_cui.obj, \ foobar_usedll_cui2.exe, , cw32 import32 libdllfoobarbaz > foobar_usedll_cui2.exe foo(2, 3) = 5 bar(2, 3) = 6
続いてWin32Windowsアプリケーション:foobar_usedll_gui.c
#include <windows.h> #include <stdio.h> extern int __stdcall foo(int a, int b); extern int __stdcall bar(int a, int b); int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char buf[200]; sprintf(buf, "foo(2, 3) = %d\n", foo(2, 3)); MessageBoxA(NULL, buf, "foobar", MB_OK); sprintf(buf, "bar(2, 3) = %d\n", bar(2, 3)); MessageBoxA(NULL, buf, "foobar", MB_OK); return 0; }
BCC32でコンパイル&実行:(途中メッセージは省略)
> bcc32 -c foobar_usedll_gui.c > bcc32 -W foobar_usedll_gui.obj libdllfoobarbaz.lib > foobar_usedll_gui.exe ("foo(2, 3) = 5", "bar(2, 3) = 6" というメッセージボックスが順に表示される)
ILINK32でリンク&実行::(途中メッセージは省略)
> ilink32 /c /aa /Tpe c0w32 foobar_usedll_gui, foobar_usedll_gui2, ,cw32 import32 libdllfoobarbaz > foobar_usedll_gui2.exe ("foo(2, 3) = 5", "bar(2, 3) = 6" というメッセージボックスが順に表示される)