SDL Source Tour Vol.1

前書き

他にも書かないかんかなーと思うとこはたくさんあるのですが、 気分的にこっち書いとこうということになりました。よろしく。

(2001/05/25追記: 途中でやめててもうしわけないっす。 書き足しました)

SDLができるまで

SDLはmulti-platformなライブラリです。 また、本家の冒頭で 「cross-platform」と書いている通り、 WindowsのライブラリをLinux上で作成する、といったことも可能です。

SDLの配布ファイル(ソースコードのarchive file)は、Windows用、Linux用、 といったように分かれておらず、すべて共通(例: SDL-1.2.0.tar.gz)です。 配布ファイルの中にはサポートしているすべての環境に対するコードが入っています。

それらのコードの多くの部分は、あなたが今使っている 環境(マシン、OS、インストールされているパッケージなど)と 違う環境向けに書かれたコードですので、コンパイルに失敗するかもしれませんし、 コンパイルが通ってもリンクでエラーが起きるかもしれません。 いずれにしても不要なコード、あるいは、別の環境では必要になるんだけど、 ここでは含まれてはいけないコードが 含まれています。

そのため、buildの前段階で、この環境(コンパイルする環境、 ターゲットとなる環境)で、どれが必要で、どれが不要かということを 調べる作業をする必要があります。

他の多くのソフトと同様に、 SDLは、この作業のためにautomake/autoconf/libtoolといったツールを 利用しています。それって何?という方は、 shimakiさんの ページや、 この本などを 読んでみてください。

SDLは、configureというスクリプトで、buildする環境に 関する情報、たとえば、どのようなOSなのか、 どんなパッケージがインストールされているか、などを調べます。 このconfigureスクリプトを手軽に生成するためのツールがautoconfです。

configureはその内容にしたがってMakefile.in からMakefileを生成します。 このMakefile.inを手軽に生成するためのツールがautomakeです。

libtoolはとりあえず略...

SDLソースツリー

SDLソースのディレクトリ構造の図です。
ソース構造1 ソース構造2
手抜きでスマンクス(^^; これを見ますと、audio、cdrom、endian、events... といった、SDL introで 見慣れた機能が並んでいて、その下に環境ごとにディレクトリが掘られている 様子が分かります。

今回はaudioを例にとって、

といったことを調べてみたいと思います。

audioの構造

zinnia@iguchi:~/build/SDL/SDL-1.2.0/src/audio[10]% \ls -ARF
Makefile.am             SDL_sysaudio.h          macrom/
Makefile.in             SDL_wave.c              nas/
SDL_audio.c             SDL_wave.h              nto/
SDL_audio_c.h           alsa/                   paudio/
SDL_audiocvt.c          arts/                   sun/
SDL_audiodev.c          baudio/                 ums/
SDL_audiodev_c.h        dma/                    windib/
SDL_audiomem.c          dmedia/                 windx5/
SDL_audiomem.h          dsp/
SDL_mixer.c             esd/

./alsa:
Makefile.am             SDL_alsa_audio.c
Makefile.in             SDL_alsa_audio.h

./arts:
Makefile.am             SDL_artsaudio.c
Makefile.in             SDL_artsaudio.h

./baudio:
Makefile.am     Makefile.in     SDL_beaudio.cc  SDL_beaudio.h

./dma:
Makefile.am     Makefile.in     SDL_dmaaudio.c  SDL_dmaaudio.h

./dmedia:
Makefile.am             SDL_irixaudio.c
Makefile.in             SDL_irixaudio.h

./dsp:
Makefile.am     Makefile.in     SDL_dspaudio.c  SDL_dspaudio.h

./esd:
Makefile.am     Makefile.in     SDL_esdaudio.c  SDL_esdaudio.h

./macrom:
Makefile.am     Makefile.in     SDL_romaudio.c  SDL_romaudio.h

./nas:
Makefile.am     Makefile.in     SDL_nasaudio.c  SDL_nasaudio.h

./nto:
Makefile.am             SDL_nto_audio.c
Makefile.in             SDL_nto_audio.h

./paudio:
Makefile.am     Makefile.in     SDL_paudio.c    SDL_paudio.h

./sun:
Makefile.am     Makefile.in     SDL_sunaudio.c  SDL_sunaudio.h

./ums:
Makefile.am     Makefile.in     SDL_umsaudio.c  SDL_umsaudio.h

./windib:
Makefile.am     Makefile.in     SDL_dibaudio.c  SDL_dibaudio.h

./windx5:
Makefile.am     Makefile.in     SDL_dx5audio.c  SDL_dx5audio.h

src/audio以下のディレクトリは こんな感じになっています。 これ見て分かるのは

といったあたりでしょうか。対応させたい「環境」と書いたのは OS名だったりサーバ名だったりマシン名だったりというのが理由です。 が、ソース内の流儀(下のほうでわかります)にしたがってdeviceと 表記することにしたいと思います。configure内ではfeatureにだいたい 相当します。

configureの動き

configureをしますと、各featureごとにその存在チェックを行います。 --disable-feature(または--enable-feature=no)することでこの チェックをスキップすることができます。

nas(Network Audio System)を例にとってconfigureの該当部分を眺めてみましょ う。

CheckNAS()
{
    # Check whether --enable-nas or --disable-nas was given.
if test "${enable_nas+set}" = set; then
  enableval="$enable_nas"
  :
else
  enable_nas=yes
fi

    if test x$enable_audio = xyes -a x$enable_nas = xyes; then
	echo $ac_n "checking for NAS audio support""... $ac_c" 1>&6
echo "configure:3788: checking for NAS audio support" >&5
	have_nas=no
	if test -r /usr/X11R6/include/audio/audiolib.h ; then
		have_nas=yes
	fi
	echo "$ac_t""$have_nas" 1>&6
	if test x$have_nas = xyes; then
	    CFLAGS="$CFLAGS -DNAS_SUPPORT"
	    SYSTEM_LIBS="$SYSTEM_LIBS -laudio -lXt"
	    AUDIO_SUBDIRS="$AUDIO_SUBDIRS nas"
	    AUDIO_DRIVERS="$AUDIO_DRIVERS nas/libaudio_nas.la"
	fi
    fi
}
おおざっぱに眺めてゆきますと と、こんな感じでしょうか。featureによってはもっとチェック方法が 複雑だったり(実際にテストプログラムをbuildしたり)しますが、基本は だいたいこんな感じです。

CFLAGSの方は、まあ、条件コンパイルのフラグっぽいということで よいのですが、AUDIO_{SUBDIRS,DRIVERS}は何者でしょうか? とりあえずこれを調べてみることにしましょう。

Makefileの動き

とりあえずgrepかけてみますか...

(zsh user only)
zinnia@iguchi:~/build/SDL/SDL-1.2.0[44]% grep -l AUDIO_SUBDIRS **/*
Makefile.in
configure
configure.in
docs/Makefile.in
docs/html/Makefile.in
docs/man3/Makefile.in
include/Makefile.in
src/Makefile.in
src/audio/Makefile.am
src/audio/Makefile.in
src/audio/alsa/Makefile.in
src/audio/arts/Makefile.in
src/audio/baudio/Makefile.in
src/audio/dma/Makefile.in
src/audio/dmedia/Makefile.in
src/audio/dsp/Makefile.in
src/audio/esd/Makefile.in
src/audio/macrom/Makefile.in
src/audio/nas/Makefile.in
(中略)
src/video/wincommon/Makefile.in
src/video/windib/Makefile.in
src/video/windx5/Makefile.in
src/video/x11/Makefile.in
うーん、かなりの量のファイルがヒットしました。が、ほぼすべてが Makefile関係のようですね。 Makefile.inはMakefile.amの生成物ですから...
zinnia@iguchi:~/build/SDL/SDL-1.2.0[46]% grep -l AUDIO_SUBDIRS **/* | grep -v Makefile.in$
configure
configure.in
src/audio/Makefile.am
ふむふむ。 src/audio/Makefile.amを見てみることにしましょう。

## Makefile.am for the SDL audio library

noinst_LTLIBRARIES = libaudio.la

# Define which subdirectories need to be built
SUBDIRS = @AUDIO_SUBDIRS@
DIST_SUBDIRS = alsa arts baudio dma dmedia dsp esd macrom nas nto \
               paudio sun ums windib windx5

DRIVERS = @AUDIO_DRIVERS@

# Include the architecture-independent sources
COMMON_SRCS =			\
	SDL_audio.c		\
	SDL_audio_c.h		\
	SDL_audiocvt.c		\
	SDL_audiodev.c		\
	SDL_audiodev_c.h	\
	SDL_audiomem.c		\
	SDL_audiomem.h		\
	SDL_mixer.c		\
	SDL_sysaudio.h		\
	SDL_wave.c		\
	SDL_wave.h

libaudio_la_SOURCES = $(COMMON_SRCS)
libaudio_la_LIBADD = $(DRIVERS)
libaudio_la_DEPENDENCIES = $(DRIVERS)
automakeの細かいことはおいといて、意図を掴むように努力してみましょうか。 流れとしてはこんな感じです。configureでAUDIO_{SUBDIRS,DRIVERS}が、 存在チェックに通ったものだけ、どんどん追加されていって、 最終的にMakefileに反映されます。どう反映されるのか? というのは、configureが終わったあとのMakefileを見てみれば 分かります。私の環境ではsrc/audio/Makefileの該当部分はこんな感じに なりました:
AUDIO_DRIVERS =  dsp/libaudio_dsp.la dma/libaudio_dma.la arts/libaudio_arts.la esd/libaudio_esd.la nas/libaudio_nas.la
AUDIO_SUBDIRS =  dsp dma arts esd nas
Makefileの動きとしては、SUBDIRSのそれぞれについてmakeをするというような 理解でよろしいかと思います。そんなこんなで、 AUDIO_{SUBDIRS,DRIVERS}に出てこないサブディレクトリは そもそもコンパイル/リンクの対象にならなさそうだということが 想像できましたでしょうか?

コンパイルの動き

makeまでの手順はだいたい想像がつきました。 では実際にコードがどんなふうになっているのか、いよいよ見てみることに しましょう。configureのときに出てきた-DNAS_SUPPORTを手がかりにします。

zinnia@iguchi:~/build/SDL/SDL-1.2.0/src/audio[66]% grep NAS_SUPPORT **/*
SDL_audio.c:#ifdef NAS_SUPPORT
SDL_sysaudio.h:#ifdef NAS_SUPPORT
おや、思ったより少ない。では該当部分を眺めてみます。
(SDL_audio.c)
#ifdef NAS_SUPPORT
        &NAS_bootstrap,
#endif

(SDL_sysaudio.h)
#ifdef NAS_SUPPORT
extern AudioBootStrap NAS_bootstrap;
#endif
該当部分はこれだけでした。 どうもNAS_bootstrapというGlobal変数(AudioBootStrap構造体)が キーのようです。

コードの動き

AudioBootStrap構造体の定義は、 SDL_sysaudio.hの、 さきほど見た場所のすぐ上にあります。

typedef struct AudioBootStrap {
        const char *name;
        const char *desc;
        int (*available)(void);
        SDL_AudioDevice *(*create)(int devindex);
} AudioBootStrap;
2つのconst charへのポインタ、2つの関数へのポインタを持つ構造体の ようです。見た感じ、名前、説明、チェック用関数、???といった 気がします。 最後の関数createは、 SDL_AudioDeviceへのポインタを返す関数(引数はint1つ)へのポインタ ということになりそうです。 SDL_AudioDeviceの定義は、AudioBootStrapのすぐ上にあります(うーん楽な展開) ちょっとでかいので引用はひかえます。中身を見てみると、 OpenAudio、InitAudio、PlayAudioといった名前の(関数への)ポインタが あったり、enabled、paused、openedといったint変数があったりで、 どうもdeviceの内部状態へのアクセス手段を提供するもののように 見えます。

ここまでの話をまとめます。

さてさて。では実際にNAS_bootstrapというものは どういう中身を持つのか調べてみましょう。

zinnia@iguchi:~/build/SDL/SDL-1.2.0/src/audio[67]% grep -l NAS_bootstrap **/*
SDL_audio.c
SDL_sysaudio.h
nas/SDL_nasaudio.c
上の2つは先程見たものですからいいですね。というわけで nas/SDL_nasaudio.cを見ることにします。
AudioBootStrap NAS_bootstrap = {
        NAS_DRIVER_NAME, "Network Audio System",
        Audio_Available, Audio_CreateDevice
};
先程のAudioBootStrapの定義とてらしあわせてみますと、 といった感じですね。 では2つの関数の中身を眺めることにしましょう。 まずはAudio_Availableです。
static int Audio_Available(void)
{
	AuServer *aud = AuOpenServer("", 0, NULL, 0, NULL, NULL);
	if (!aud) return 0;

	AuCloseServer(aud);
	return 1;
}
AuOpenServer/AuCloseServerというのはnasのAPIという理解でよいでしょう。 実際に起動できるかどうかのチェックをして、失敗したら0、 成功したら1を返す。というわけで、名前の通り利用可能かどうかの チェックをする関数のようです。

続いてAudio_CreateDeviceです。 中身を見てみると、おおざっぱに言ってSDL_AudioDeviceのセッティングを行い、 それを返す関数と言ってよいようですね。

        /* Set the function pointers */
        this->OpenAudio = NAS_OpenAudio;
        this->WaitAudio = NAS_WaitAudio;
        this->PlayAudio = NAS_PlayAudio;
        this->GetAudioBuf = NAS_GetAudioBuf;
        this->CloseAudio = NAS_CloseAudio;

        this->free = Audio_DeleteDevice;

        return this;
最後の方だけ引用してみました。thisは(現在セットアップ中の) SDL_AudioDevice構造体へのポインタです。 しかしthisを変数名として使うのはいかがなものか...

こんなふうにして、SDL_AudioDeviceに、nas(src/audio/nas)向けのルーチンを 登録して上流(src/audio)に返してやるというのが、 個々のdeviceの仕事のすべてです。

ここで上でリンクした関数がいずれもstaticで宣言されていることに とりあえず注目しておきましょう。 これらの関数は外から直接呼ばれるのではなく、 SDL_AudioDevice構造体経由で呼ばれます。 構造体にアドレスを登録できればそれでいいわけで、外部に公開する 必要はないわけです。staticにすることで、 SDL_nasaudio_Audio_Availableみたいに 名前が衝突しないようにする気遣いも不要になり、コードの使いまわしが 楽になりそうーという利点もあります。

まとめ

いろいろと眺めてみましたが、ここまでの結果をまとめておきましょう。 なお、ここでは「推測」や「想像」などがまじっていることを おことわりしておきます(ここまで読んできた方には先刻承知でしょうが)。 疑わしいと思ったらコードを確認するようにしてみてください。

これが各deviceに視点を向けて、コードを追いかけたときの様子です。 SDL本体としては、個々のdeviceに対して直接働きかけるのではなく、 かならずSDL_AudioDeviceのポインタ経由で触りにゆくのだ、ということも おわかりいただけたと思います。そのため、新しいdeviceのサポートを したいときは、SDL_AudioDeviceで要求されているmethodを実装してやれば、 全体としての構造をいじる必要はないということになります(SDL_audio.cに 定義を追加する必要はありますが)

coming soon

では、そうやって作られたAudioBootStrap、SDL_AudioDeviceは 実際にどのように使われてゆくのでしょうか? というのが 次回のネタになる予定です。

感想

いきなり息切れ中。感想とかいただけるととても喜びます。 変なところや説明が不十分なとこなども是非ご指摘ください。 わかりづらいとか無駄なことはやめろとかでも涙を飲みつつ 粛々と読ませていただきます。ではまた。


戻る

Zinnia (zinnia@risky-safety.org)
このWebコンテンツ(ここから辿れるもの)に対する コメントのメールは許可なく公開することがあります。 (最近多い無礼なメール対策であって穏当なメールをいきなり公開したり することはありません)