UNIXではgccがタダで使えるんだから、 WindowsでだってVC++になんか金を出せるか、と思っていた筆者だが、Borland C++ Compiler 5.5の無料配布をきっかけに、 こいつで窓プログラミングをしてみよう、と思い立った。 それに、Cygwinでだって窓プログラム書けるんでしょ? そうです書けます。
これは、そんな筆者によるメモです。 残念なことに、 このような情報は(特に日本語では)ほとんど存在しないようなので、 同じような境遇の人の助けになることと思います。
念のため付け加えておくと、この道はいばらの道です。 窓プログラミングを体験してみたいが金がないという場合にも、 投入する時間を考えるとVC++を買った方が安上がりかもしれません。 (とはいえ、筆者がかきわけた道を通るのは幾分楽なはずですが)
あ、UNIX触ったことがないとか、プログラム書いたこともないとか、 そういう方には(そうでなくても?)死ぬほど不親切なページだと思います。 ですが、自分用のメモ+αですので、この程度が限界です。 意見・質問等のメールは大歓迎ですが、 時間的・能力的に期待に沿えるとは限りませんのであしからず。
Borland C++ Compiler 5.5(以下bcc)というのは、 Borland社が開発した Windows用開発環境Borland C++ Builder 5.5のうち、 コマンドラインツール(の一部)を無料で配っているものです。 これだけでWindowsのアプリケーションを作ることができます。
Cygwinというのは、UNIXのシステムコールを実現するようなWindowsのDLLを提供し、 UNIX用のソースを変更なしにコンパイルすることで Windowsで動くバイナリを作れるようにしよう、 というプロジェクトです。 gccやgdbが動いており、必要最低限のWindows用開発ツールも提供されています。
最初に両者の欠点について述べておきます。 耐えられそうになければ、VC++でも買ってください。
bccが無料で配られているといっても、統合環境は相変わらず売り物です。 コマンドラインから使えるコンパイラ、リンカなど一式を配っているだけで、 リソースエディタや見栄えのするデバッガなどはついてきません。 そういう意味で、gcc環境と似たようなものです。 過度の期待をしてはいけません。
ユーザーが少ないせいなのですが、 bccもCygwinも資料がほとんどありません。 web上にある資料はVC++かdelphiのものばかりです。
SDKレベルのプログラミングしかできません。 MFCが使いたければVC++が必要です。 bccでも売り物を買わないとOWLやらVCLやらを使うのは無理のようです。
あまり使っていないですが、見てくれが悪すぎます。
"表示"とかです。 Windowsのプログラムだと文字列はSJISで書くしかないんですよね‥。 "表\示"とかやればうまくいきます。同じソースはbccでも通ります。 でもあまりやる気はしません。
VC++にはANSIにない関数がかなりあります。 VC++の人は当然のように使っています。 bccはかなり追随しているようですが、 Cygwinにはない関数が多いです。
こんな感じでしょうか。 何か筆者の誤解している点がありましたら、ご連絡いただけると助かります。
この記事ではCygwinのツールを使うことをかなり前提にしています。 bccと、Windows標準のツールだけでも同じことができるはずですが、 その際には適宜読み替えてください(rmをdelとするなど)。 また、makeを使ったプログラム開発の経験も前提にしています。
参考までに筆者の環境を書きます。 Celeron466MHz/128M/Windows2000のマシンで、 shellはbashを使い、 コンパイルはGNU makeで行っています。 今のところbccもgccも同程度使っています。
これ以外のものを使っても構いませんけどね(例えば、Linux上でクロスコンパイラで開発とか‥)。
OSはできるだけNT/2000を使うべきでしょう。 95/98はただでさえ落ちやすいので、 開発中にポインタ周りのバグがあったりするとひどい目に会うかもしれません。
コンパイラは、 Borland C++ Compiler 5.5に含まれるbccか、 Cygwinに含まれるgccを使います。
Platform SDKというのは、 Microsoftが配っている開発者用のツールやヘルプなどのパッケージです。 これに含まれるヘルプが開発には欠かせません。 このヘルプがないと思い通りのプログラムを書くのは困難と言っていいでしょう。
この文章の最後のリンク集を参考に、必要なものをダウンロードしましょう。 bccは8M程度、Cygwinはsnapshotを含めて25M程度です。 Platform SDKはフルパッケージで550M程あります。 必要な部分だけダウンロードしてくれるインストーラもありますが、 ヘルプだけでも130M程あります。 接続状況的にどうしても無理ならあきらめましょう。 コンパイラだけあればコンパイルはできます。
bccのreadme.txtに書いてあるのですが、 Borland C++Builder documentation site からヘルプをいくつか取ってきた方がいいかもしれません。
まずは落としてきたfreecompiler.exeを実行します。 インストールするディレクトリを聞いてくるので、 適当に答えるとインストールされます。 次に、readme.txtに書いてある通り、 binディレクトリにbcc32.cfgとilink32.cfgを作ります。 最後に、このディレクトリにパスを通すなり通さないなり適当にやります。 筆者は、GNUのmakeを使うためにCygwinの方がパスの上位にくるようにしています。
もう直っているかもしれませんが、筆者がダウンロードしたときには 日本語版はヘッダファイルが足りないように思いました。 そんなわけで筆者は英語版を使っていますが、 日本語版のメリットはコンパイルエラーメッセージとヘルプが日本語になっていることです (でも日本語版ヘルプだとうまく見えないページがある‥)。
CygwinでUNIXのツールをコンパイルできる状態になっていれば、 Windowsのプログラムもコンパイルできる状態になっているはずです。
インストールしていなければ、詳しいページを参照した方がいいでしょう。 藤枝さんの『Cygwin情報』からあちこちに飛んでみるといいかもしれません。
2000/04/27にCygwin 1.1いれてみたところ、どうもgdbが動かない‥。なんでだろ。 それ以外はあれこれ改善されててウハウハなんですけど。 と思ったらw2kでだけ動かなかったみたいですね。 2000/08/03の時点では(それなりに)動いてます。
Cygwin1.1に関しては日本語の資料がまだ少ない状況ですね。 早田さんの『Cygwin 1.1 環境の構築』と yzoneさんの『Cygwin 1.1 Information』くらいでしょうか。 筆者も不親切な記事を書いています( 『Cygwin 1.xでUNIX気分の窓生活』)。
必要なものを勝手にftpで取ってきてくれるexeをダウンロードした場合は、 それを実行すれば必要なものだけインストールしてくれます。
Microsoftのftpサーバから、 最新のPlatform SDKのディレクトリ(例えば April2000/) の下を全てダウンロードしてきた場合や、 MSDNのPlatform SDKを借りてきた場合(ライセンス的に問題はないはず)、 msiファイルをダブルクリックすれば ローカルのファイルをインストールしてくれるはずです。
初めてWindowsプログラミングをする方は、 本やウェブページなどを参考に頑張ってください。 MFCでへろへろ書いているものは参考になりません。 SDKとかAPIとかいうキーワードをたよりに探してください。 もしくは、Platform SDKのヘルプだけでも何とかなるかもしれません。
実際にプログラムを書いてみて困るのは、 ウインドウのデザインをどうやるかということです。 こればっかりはGUIの方がいいに決まってますが、 そんなものはbccにもCygwinにもついてきません。 一応、Platform SDKのツールとしてダイアログエディタがついてきますが、 これはWindows3.1時代の遺物で、動きはするものの使用に堪えません (置けるコントロールも限られていますし‥)。 現在はあきらめてテキストエディタでリソースファイルを書いてます。
適当なソースを拾ってくるなり、自分で書くなりして、コンパイルしてみましょう。 適当なものが見当たらなければこれでもどうぞ。
コンパイラはbcc32、リソースコンパイラはbrc32かbrcc32、リンカはilink32という名前です。
bcc32では、gccでの-o(オブジェクトファイルや実行ファイルの名前を指定) にあたるものが-o,-eと2つある上に、 それぞれの後ろに空白を入れずにファイル名を書かなくてはならないことに注意が必要です。 ライブラリは他のobjファイル同様に列挙すればリンクできます。 リンカのオプションも癖がありすぎて悩みました (^^;
下のように、ふつーにコンパイルするだけです。 -eと-oは変わってますけど、 普通は省略するので気にならないと思います。
$ bcc32 -c -otest.obj test.c $ bcc32 -etest.exe test.obj
$ bcc32 -etest.exe test.c
-WオプションをつけてコンパイルすればWindowsアプリができます。 リソースコンパイラでresファイルも作れます。
$ bcc32 -W wtest.c $ brcc32 -r wtest.rc
ただ、リソースをexeにくっつける方法がわかりません。 brc32でできるはずだと思うんですけど‥。 現在はリンカを苦労して使っています。
$ ilink32 c0w32.obj wtest.obj,wtest.exe,,cw32.lib import32.lib,,wtest.res
-WMをつけると、mt-safeなライブラリをリンクしてくれます。 VC++同様、_beginthread()も使えます。 Windowsアプリケーションの場合には
$ bcc32 -W -WM mttest-w.c
のように、-Wと両方つければOKです。
次のように-WDオプションをつけてコンパイルすればDLLができます。
$ bcc32 -WD dlltest.c
DLLファイル名の指定は実行ファイル同様-eです。
インポートライブラリは、implibコマンドで作ります。 defファイルから作るか、またはdllからいきなり作ります。
$ implib dlltest.lib dlltest.dll
defファイルが必要なときには、 impdefコマンドでdllからdefファイルを作ることができますが、 普通はdefファイルを作る必要はないでしょう。
シンボル名の変換ルールが違うので、 Cygwinで作ったdefファイルが同じディレクトリにあったりすると 「Attempt to export non-public symbol '***'」 などと文句をいわれます(が、インポートライブラリは正しくできています)。
そのインポートライブラリをリンクすればDLL内の関数が使えます。
$ bcc32 usedll.c dlltest.lib
以下はGNU make用のMakefileです。bccについてくるmakeではおそらく動きません。
### Makefile.bcc SRCS = hoegrara.c OBJS = hogerara.obj PROG = hogerara RES = $(PROG).res EXE = $(PROG).exe CC = /apps/Borland/bcc55/Bin/bcc32 CFLAGS = LD = /apps/Borland/bcc55/Bin/ilink32 LDFLAGS = RC = /apps/Borland/bcc55/Bin/brc32 IPATH = /apps/Borland/bcc55/include .SUFFIXES : .obj .c .SUFFIXES : .res .rc ALL: $(EXE) clean: rm -f $(RES) $(OBJS) $(EXE) .c.obj: $(CC) $(CFLAGS) -o$@ -c $< .rc.res: $(RC) -r -i$(IPATH) -fo$@ $< $(EXE): $(OBJS) $(RES) $(LD) c0w32.obj $(OBJS),$(EXE),,cw32.lib import32.lib,,$(RES)
Cygwinのコンパイラはgcc(g++とかg77を使う方がいてもいいですが)、 リソースコンパイラはwindresという名前です。 リンカはldですけど、 gccが勝手に呼び出してくれますので、明示的に使うことはないでしょう。
ふつーのgccです。
$ gcc -o test.o -c test.c $ gcc -o test.exe test.o
-mwindowsをつけてコンパイルするだけです。
Cygwinで何も考えずに作ったバイナリはcygwin1.dllがない環境では動きません。 それじゃ折角バイナリを作っても配りづらいなあ、と思われるでしょうが、 最近のCygwinはmingw32というパッケージを含んでおり、 -mwindowsに加えて-mno-cygwinをつけることで、 cygwin1.dllなしで動くバイナリを作ることができます。 少し動作が変わったりしますけど、普通は気にならないはずです。
リソースコンパイラwindresのsyntaxは、 MSのものと少々互換性がないので手を焼きます (ディレクティブによってはquoteするとエラーになるなど)。 また、Cygwinではリソースをres形式にしてもリンカが解釈できませんので、 coff形式にしてリンクしなくてはなりません。
$ gcc -c wtest.c $ windres -i wtest.rc -o wtest.coff $ gcc -mwindows -o wtest.exe wtest.o wtest.coff
printfデバッグをするなど(笑)の理由で、 Windowsアプリでコンソールが欲しくなることもあるでしょう。 -mwindowsに加えて-Wl,--subsystem,consoleオプションをつければコンソールができます。 もちろん、さらに-mno-cygwinをつけることもできます。
全然わかってません。 最近のCygwinのライブラリはmt-safeになったとかいう話を友人に聞いたので、 pthreadのプログラムはさくっと動くのかもしれません。 が、VC++の人達がよく使う_beginthread()は動かなさそうです。
DLLは、dllwrapで作ります。 これは名前の通りwrapperですが、 gccとdlltoolを何度も呼んでいて理解しがたいです。 まずは.defファイルを作ります。
$ gcc -c a.c b.c c.c dllinit.c $ dlltool --export-all --output-def app.def a.o b.o c.o dllinit.o
作った.defファイルを使って、dllを作ります。
$ dllwrap --driver-name gcc --def app.def -o foo.dll a.o b.o c.o dllinit.o
インポートライブラリも.defファイルから作ります。
$ dlltool --dllname foo.dll --def app.def --output-lib libapp.a
以上を一気にやるコマンドが
$ dllwrap --export-all --output-def app.def --implib libapp.a --driver-name gcc -o foo.dll a.o b.o c.o dllinit.o
ということみたいです(今ひとつわかってません)。 mingw32なDLLは、dllwrapに'--target i386-mingw32'をつけて、 '--driver-name=gcc -mno-cygwin'とすればできます。
できたインポートライブラリを
$ gcc usedll.c -L. -lapp
というようにリンクすればDLL内の関数が使えます。
ちなみに、参考にした資料はMumit Khanさんの DLL HelpersのREADMEです。 この人はどうやらdllwrapの作者みたいです。 mingw32の作者であるColin Petersさんによるチュートリアル 『Programming Win32 with GNU C and C++』のDLLの章にもいろいろ書いてありますけど、 よくわからないので (^^;、dllwrapを使った方がいいと思います。
### Makefile.gcc SRCS = hogarara.c OBJS = hogerara.o PROG = hogerara RES = $(PROG).coff EXE = $(PROG).exe CC = gcc CFLAGS = LD = gcc LDFLAGS = -mwindows -mno-cygwin # win app: # -mwindows # win app with console: # -mwindows -Wl,--subsystem,console # win app (which can be executed without cygwin1.dll): # -mwindows -mno-cygwin # win app with console (which can be executed without cygwin1.dll): # -mwindows -mno-cygwin -Wl,--subsystem,console RC = windres .SUFFIXES : .o .c .SUFFIXES : .coff .rc ALL: $(EXE) clean: rm -f $(RES) $(OBJS) $(EXE) .c.o: $(CC) $(CFLAGS) -c $< .rc.coff: $(RC) -i $< -o $@ $(EXE): $(OBJS) $(RES) $(LD) $(LDFLAGS) -o $(EXE) $(OBJS) $(RES)
Platform SDKに含まれるspyを使うと、飛んでいるメッセージを見ることができます。 自分のプログラムが思い通りのメッセージを出しているかを調べたり、 他のアプリケーションがどんなメッセージをやりとりしているのかを学べたりと、 なかなか有用なツールです。 しかし、VC++付属のspy++に比べると非常に使いにくく、 もう少しなんとかならんかったのか、という気がします。
Turbo Debuggerが一応使えるみたいです。 確かにGUIではあるんですが、 NTコンソールベースとでも言うのでしょうか、 とにかくイマイチです。 Platform SDKに含まれるデバッガも使えるのかもしれませんが、 よくわかりません。
gdbが使えます。SEGVが飛んできたよ、 などと言ってくれてUNIX気分を堪能できます。 最近はGUIもできていい感じです。 マルチスレッドプログラムのデバッグはできないのだろうか‥?
コンパイラの性能はどうなのか、調べてみました。 以下は、やる気のないプログラムをコンパイルし、 timeコマンドで実行時間を測定した結果です。 測定はCeleron466MHz/128M/Windows2000なマシンで行いました。
gcc: 4m33.463s gcc -O2: 2m28.964s bcc -Od: 3m35.329s bcc: 2m34.252s bcc -O2: 2m21.093s bcc -O2 -4: 2m21.854s bcc -O2 -5: 2m36.225s bcc -O2 -6: 2m23.366s
bccは、デフォルトで色々な最適化がされてしまうので、 最適化をさせないためには-Odオプションをつけなくてはなりません。 bccの-4,-5,-6はそれぞれ486,pentium,pentium pro用のバイナリを吐くのですが、 386用のバイナリよりかえって遅くなってしまっています。 -O2同士でならbccの方が速いバイナリを作れるようです。
次に、実際のアプリケーションについて見てみます。 mp3エンコーダーのlame 3.67betaを両者でコンパイルして、 手元にあった6分程度のwavファイルをエンコードしてみました。 どちらもコンパイルオプションは-O2のみとしました。
LAME version 3.67 (www.sulaco.org/mp3) GPSYCHO: GPL psycho-acoustic and noise shaping model version 0.77. Using polyphase lowpass filter, transition band: 15115 Hz - 15648 Hz Encoding ./hide/track_01.wav to ./hide/track_01.wav.mp3 Encoding as 44.1 kHz 128 kbps stereo MPEG1 LayerIII (11.0x) qval=5 Frame | CPU/estimated | time/estimated | play/CPU | ETA 13705/ 13705(100%)| 0:03:33/ 0:03:33| 0:03:34/ 0:03:34| 1.6801| 0:00:00
LAME version 3.67 (www.sulaco.org/mp3) GPSYCHO: GPL psycho-acoustic and noise shaping model version 0.77. Using polyphase lowpass filter, transition band: 15115 Hz - 15648 Hz Encoding ./hide/track_01.wav to ./hide/track_01.wav.mp3 Encoding as 44.1 kHz 128 kbps stereo MPEG1 LayerIII (11.0x) qval=5 Frame | CPU/estimated | time/estimated | play/CPU | ETA 13705/ 13705(100%)| 0:02:08/ 0:02:08| 0:02:09/ 0:02:09| 2.7866| 0:00:00
lameにはbcc用のアセンブリコードが含まれていませんので、 上の結果はその分bccに不利です。 gccでも、quantize-pvt.cで同じコードを使わせてみたところ、
LAME version 3.67 (www.sulaco.org/mp3) GPSYCHO: GPL psycho-acoustic and noise shaping model version 0.77. Using polyphase lowpass filter, transition band: 15115 Hz - 15648 Hz Encoding ../../../hide/track_01.wav to ../../../hide/track_01.wav.mp3 Encoding as 44.1 kHz 128 kbps stereo MPEG1 LayerIII (11.0x) qval=5 Frame | CPU/estimated | time/estimated | play/CPU | ETA 13705/ 13705(100%)| 0:02:51/ 0:02:51| 0:03:00/ 0:03:00| 2.0980| 0:00:00
となって、やはりbccの方が遅いという結果になりました (ソースを把握しているわけではないので、 他にもgccの方が有利な個所があるのかもしれません)。 コンパイルオプションをいろいろ試してみましたが、 あまり変わりませんでした。 他のアプリケーションでも試してみたいところです。
gccでエディットコントロールを作るとゴミが出るような気が。 どうやらwindresのバグ‥というかsyntaxが違うようだ。 brc32でresファイルを作って、 windresでresからcoffに変換するとうまくいった。なんじゃそりゃ。
### .bashrc export TZ=JST-9 export MAKE_MODE=UNIX export TMP=/tmp export TEMP=/tmp export PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/X11R6/bin:/usr/X11R6/bin:/apps/Borland/bcc55/Bin:$PATH export MANPATH=/usr/local/man:/usr/man:/usr/local/X11R6/man:/usr/X11R6/man export XAPPLRESDIR=$HOME/lib/X11/app-defaults if [ "$TERM" = "emacs" ] ; then alias ls="ls -N" alias dir='ls -laN' PS1="\h:\w> " else alias ls="ls -N --color" alias dir='ls -laN --color' if [ -f $HOME/.dir_colors ] ; then eval `dircolors -b $HOME/.dir_colors` fi PS1="\[\e[7m\]\h\[\e[m\]:\[\e[7m\]\w\[\e[m\] " fi if [ "$TERM" = "xterm" ] || [ "$TERM" = "kterm" ] ; then xmodmap $HOME/.xmodmaprc.astec-x fi case `uname` in CYGWIN_95* ) alias cddesktop='cd /windows/デスクトップ' alias start='convexec -n/b,/d,/i,/min,/max,/separate,/shared,/low,/normal,/high,/realtime,/abovenomal,/belownormal,/wait,/\? start' ;; CYGWIN_NT* ) alias cddesktop='cd /Documents\ and\ Settings/'$USERNAME'/デスクトップ' alias start='convexec -v -n4 -i -n/b,/d,/i,/min,/max,/separate,/shared,/low,/normal,/high,/realtime,/abovenomal,/belownormal,/wait,/\? cmd /c start ""' ;; * ) alias cddesktop='cd /windows/デスクトップ' ;; esac alias rehash='hash -r' alias bcc=bcc32 alias brc=brc32 alias brcc=brcc32 alias ilink=ilink32 #alias bcc='PATH=/apps/Borland/bcc55/Bin:$PATH bcc32' #alias bcc32='PATH=/apps/Borland/bcc55/Bin:$PATH bcc32' #alias brc='PATH=/apps/Borland/bcc55/Bin:$PATH INCLUDE="c:\apps\Borland\bcc55\include" brc32' #alias brc32='PATH=/apps/Borland/bcc55/Bin:$PATH INCLUDE="c:\apps\Borland\bcc55\include" brc32' #alias brcc='PATH=/apps/Borland/bcc55/Bin:$PATH INCLUDE="c:\apps\Borland\bcc55\include" brcc32' #alias brcc32='PATH=/apps/Borland/bcc55/Bin:$PATH INCLUDE="c:\apps\Borland\bcc55\include" brcc32' #alias ilink='PATH=/apps/Borland/bcc55/Bin:$PATH ilink32' #alias ilink32='PATH=/apps/Borland/bcc55/Bin:$PATH ilink32'
readme.txtに書いてある通りですけど。
-I"c:\apps\Borland\bcc55\include" -L"c:\apps\Borland\bcc55\lib"
-L"c:\apps\Borland\bcc55\lib"
$Date: 2003-12-10 03:12:22 $