この仕組みはいたって単純です。関数の 位置を示すポインタを用意し , それを配列 にして定義します。種類を示す値に従って 配列と実際の関数を結び付けておきます。 コマンドを実行したいところには , cmdlis [ 種類の値 ] ( 引数 ) ; という形で書いておくと , 不思議なことに 値に対応した関数が呼び出されます。 単純なだけに不正な値を与えるとすぐ暴 走してしまいます。必ず値の範囲をチェッ クするようにしてください。 してみました (List 3 ) 。 値から対応する関数を直接呼び出す方法に ドを追加したりして , いろいろ試してみて testadv. c となります。ソースコードにコー ジャンプ」により , コマンドの種類を示す そこで , 「関数ポインタによるテープル 列するのはちょっと耐えられません。 るほうがいいはずです。 if 文や case 文が羅 もしれませんし , 手軽にメンテナンスでき ものが考えられます。将来的に拡張するか シーンデータのコマンドにはいろいろな ・コマンド 字が 1 文字ずつ表示されます。 これを文字が尽きるまで繰り返せば , 文 にします。 元に戻し , 新しい行で文字を表示するよう いつばいになったら , 次に y を増やして x を で横幅を気にせず 1 加えていきます。 x が横 ください。 ・まとめ これらのソースコードをまとめたのが , よく WonderWitch でひっかかりやすいとこ ろが , SS!=DS 問題です。 WonderWitch の構 造上 , スタックセグメント (SS) で示される スタックの内容を置く場所と , データセグメ ント (DS) で示されるデータの内容を置く場 所が違うため , 「これらが同じ領域を指す」と いう前提のコンパイラやライブラリでは , メ モリ内容のコピーなどで問題が起こります。 私たちが行うプログラミングにどんな影響 を与えるかというし ・文字列や配列を正しく関数へ渡せない ・参照渡しが利用できない ・正しいデータをコピーできない などの事態が発生します。これを理解するた めには , WonderWitch で提供されている開発 環境がどのように変数を配置するかを知って おく必要があります。 C 言語の変数には大きく分けて , static 変数 と auto 変数の 2 つがあります。 static 変数はプ ログラムが実行されている限広永続的に内 容が確保されます。一方 auto 変数は , 関数中 で定義したのなら , その関数の外に処理が移 ると変数の内容は保証されません。逆に , 関 数の中に処理がある限りその変数は使えると いうことになります。 関数外で変数を宣言したり , 変数宣言の型 名の前に static と入れることで static 変数を官 言することができます。 static 変数のデータ を確保する場所は , DS で示されるデータ領 コラム 1 SS!=DSB 題 域となります。また , 関数内で単純に変数を 言すると , 自動的に auto 変数になります。 auto 変数は SS で示されるスタック領域上に 確保されます。また関数の引数なども通常は スタック領域上に置かれます。 ここからがポイントです。通常 SS と DS は 同じ領域を使うスタイルになっています。 S S==DS です。これを前提としている開発環境 を使っていると , SS と DS が違う領域になっ ている場合に , テータが置かれている正しい 場所にアクセスできない現象が起きてしまい ます。それが WonderWitch の構造上の問題と なって現れます。 実例を見てみましよう。たとえば , char bufl [ 256 ] ; void f00 (void) char buf2 [ 256 ] ; memcpy (buf2, bufl , sizeof(bufl ) ) ; というコードがあるとします。この場合 , bu fl は static 変数となるため , DS の領域に格納 されます。 buf2 は auto 変数となるので , スタ ック上 , つまり SS 領域に格納されます。一方 memcpy ではいずれも DS 領域に入っているも のと仮定して処理を行ってしまうため , でメモリの破壊が起きてしまいます。 このようなソースコードの場合は , static char buf2 [ ] というように頭に static を付け , 隧びあレ 完成されたゲームを目指す 検証版のプログラムをさらに発展させて 本格的なゲームにしてみましよう。 ■データファイルの作成 データが読み込めないと何事も始まりま せん。次は「データを作るもの」から考えて みます。ここではテキストファイルに記述 された内容からシーンデータや画像データ をまとったパッケージファイルを作ること にします。 変換の方法は申しわけないくらい何も考 えていません。テキストデータを 1 行取り 出し , それを strstr ( ) という指定した文字 列が対象となる文字列に含まれているかど うかを探す関数を使ってシーンに含みたい 明示的に DS 領域に変数の内容を置くように しなければなりません。また , int i ; のように 整数型やほかの型の変数を関数内で宣言し物 それを , memcpy(&i, buf, sizeof(int)); のよう に変数のアドレスを渡すときも , static inti; と宣言しなければなりません。 値を受ける側では , これらの問題に対処す るため , 値を受け取る引数では必ず far ポイン タで定義します。 まとめると「内容を保持するもの , アドレ スを渡す変数 , よく見られるものとしては配 列や構造体 , 文字列には static を付ける」 , 「ポインタなどを受け渡す関数では , 引数に far ポインタを使う」ということがセオリーに なります。 Turbo C でこうした対策を行わな いソースコードをコンパイルするしよくリ ンク時に「 SCOPY @がない」と叱られます。 これを 1 つの目安にするのもいいのかもしれ ません。とはいえ , 例外ももちろんあります。 ファイルサイズの取得などに使う stat ( ) とい う B ℃ S 関数へは , auto 変数として確保した 構造体を渡さないといけません。また , この 問題を明示的に static などを付けず , そのま ま解決できるコンパイラも Turbo C + + などを はじめ , いくつか存在するようです。 いずれにしろ , 変数や配列などがメモリの どこに配置されるのか , 常に意識しながらプ ログラミングを行っていくのがいちばんのよ うです。 遊びのレシピ fo 「 WonderWitch 1 3 /
のようになり , どこが交点なのかはっきり までを走査し , 線分の重なり具合を のは関数 fillPolygon で行っている処理です。 表す情報の累計を求める。同時に画 このなかで , 線分情報バッフアの作成 , 走 しない場合があります。 これらに対応するために , 線分情報バッ 像への描画も行う 査による描画という処理を行っています。 フアに線分の有無を表すフラグと , 線分の ( 4 ) 上の ( 3 ) で求めた累計値が奇数であれ 線分情報バッフアを作成したあと , 自己 ば領域内外の切り替えを行う 重なり具合を表す情報とを合わせて記録し 交差を考慮した形で走査を行う処理は関数 ます。線分の有無を表すフラグは単純に線 ⑤次に線分のある点までを走査する。 fillregion のなかで行っています。この関数 このとき , 状態が「領域内」であれば の中身は前述の処理そのままです。 分のある部分に 1 をセットし , 線分の重な 画像への描画を行う り具合を表す情報は , Fig. 10 のように y 座 計算により交点を求める 標が変化する部分についてのみ + 1 するとい ( 6 ) 線分が見つかると ( 3 ) の処理から繰り 返し , 走査領域の右端まで処理を行う 次に , 走査線との交点を計算によって求 う条件で記録していきます。走査の際に線 以上のアルゴリズムに従って実装したプ 分にぶつかったら線分がつながっている部 める方法を考えます。線分の式が , 分での線分の重なり具合を表す情報の値を ログラムが付録 CD - ROM に収録した polygo y=ax + b 合計します。その結果 , 累計値が奇数であ nfl. c です。塗りつぶし処理のメインとなる と与えられていれば , 座標 y の走査線との れば領域内部・外部の切り替えを行い , 偶 Fig. 11 線分の重なり状態の判別 数であれば切り替えを行いません ( Fig. 11 ) 。 下のようになります。 バッファ値の累計 1 1 2 2 ( 1 ) 「領域外」の状態から始める ( 2 ) 走査線上のバッフアを調べ , 線分が ある部分を探す Fig. 10 線分の重なりを記録したバッファ 累計値が偶数ならば 切り替えを行わない 切り替えない : 1 0 0 0 バッファ値の累計 1 1 1 切り替える 1 累計値が奇数ならば 切り替えを行う 0 は 0 2 バッファ値の累計 2 2 3 3 切り替える Fig. 12 処理対象線分の絞り込み (a) 偽の交点 ( b ) 交点を求めてはいけない線分 ※ y 座標の異なる部分で 1 回だけ記録する 本来は交点では ないはずの点 線分 走査線 走査線 0 0 線分 線分 ※ほかの線分が重なってきた場合も同様 ( 前の線分によってセットされた値が ある場合は 1 を加算 ) 走査線に対して , 完全に上 , または下 にある線分は処理対象にしない 106 C MAGAZINE 2001 2
る命令の処理を行わなければなりません。 それ以外ならば , その行は表示用のテキス トということなので , 読み込んだ行をその まま画面に表示します。 実際に作成したプログラムが , List 3 , 4 です。この 2 つのソースファイルで 1 つのプ ログラムなので , コンパイル時には注意し てください。一般的なコンパイラであれば , > ( コンパイラ ) list3. c list4. c と , ソースファイルを指定するところに並 べて書けばコンパイルできます。プログラ ムを開発する場合には , このように複数の ソースファイルに分けるのが普通なのです。 スクリプトデータとして、 novel. txt" という ファイルを読み込むので , List2 のファイル 名を変更しておいてください ( Fig. 4 ) 。 今回のスクリプトで用意した命令は , sel ect 関数 , goto 関数 , if 関数 , 変数への代入 式の 4 種類です。 select 関数では , 以降の 3 行を , 質問 , 選択肢 1 , 選択肢 2 と解釈して 表示を行い , 番号の入力を待ちます。番号 が入力されたら , 選択された番号に応じた ラベルにジャンプします。 select 関数 , got 。関数 , if 関数に使われて いるラベルへのジャンプでは , ファイルを 先頭から 1 行 1 行調べて , 該当するラベルま で移動しています。あまり効率のよい方法 ではありませんが , プログラム自体の構造 は見やすくなっているでしよう。 演習 : ラベルジャンプを効率よく 行うためには , どのような工夫を すればよいだろう ? 数式処理 if 関数における条件式の処理や , 変数へ の代入式の処理は , List 4 の calculate 関数で 行われています。これは , どのようなアル ゴリズムで処理されているのでしようか ? 普段私たちの書く数式は , コンピュータ にとって処理しやすい書式ではありません。 なぜなら , カッコや演算子の優先順位とい った概念があるため , 実際に計算するため これらを探し出し , 数式の中間地点 ノベルゲーム実行画面 し」ントフロこ′フト - ′ : 「 ks 〉ⅱ st3 には , から計算していく必要があるからです。算 数が苦手な子供がカッコ付きの計算を間違 ブラインドタッチかてきれば作業効率も上がり , 肩こりも解消されるなどいいこ とすくめなのて , ブラインドタッチをマスターするヘくタイビンクソフトを作って みましよう。 実行すると . 最初に準備確認のメッセージが出ます。心の準備が出来たらリター ンキーを押すと , 入力すべき文字列か表示されます。同し文字列をキーポードから 打ち込み , すかさすリターンキーを押します。 入、力する文字列を間違ったり . 打ち込むのか遅かったりすると減点 , 素早く打ち込 りたら加点します。これを繰り返し , 持ち点か 0 点になったらケームオーバーてす。 プームオーバーまてに何問クリアてさるかを競います。 病習 : 猷引 C の範囲内て 1 囲行程度て記述てさる , 簡単なケームを作成せよ。 にようし、やってみるか。 2 : いや、もう 1 回最初から読んてみよう。 コラム 1 前回の演習問題の答え まず , 「ビン詰め問題のすべての組み合 わせを調べる方法を改良する」についてで す。これは , たとえば次のように考えます。 ビン詰め問題では , ビンに詰めるときの 要素の組み合わせ方を考えます。このとき , 大きな要素の中には , どうやってもほかの 要素と組み合わせるとビンに入らないもの があるかもしれません。こうした要素は , その要素 1 個でビンを 1 つ使わざるをえない ので , 組み合わせを調べる対象から外すこ とができます。こうして , 組み合わせを調 べる要素数を減らしてやれば , そのぶん処 理が速くなります。 具体的には , 全要素をソートして , 大き いものから順にもっとも小さい要素と組み 合わせてビンに収まるかを調べ , 収まらな い要素を要素群から取り除いていきます。 収まるものが見つかった時点で , それより 小さい要素はすべて収まりますから , この 調査を終了させることができます。あとは , 残った要素群ですべての組み合わせを調べ ればよいことになります。このプログラム の作成例 (ansl . c) を付録 CD - ROM に収録し ました。実行時に最後に表示される sho cut_items が , 取り除くことができた要素の 数になります。この値が大きいときほど , アルゴリズムが有効に作用しているという ことです。全体的にデータが大きい場合は 有効に作用するでしよう。 次に , 「ランダムサーチの要素を使った 巡回セールスマン問題の解法」についてで すが , たとえば次のように考えます。 すでにある巡回経路に対して , 部分経路 の一部を修正して , もっとよい答えになら ないかを調べることにします。与えられた 巡回経路からランダムに 4 都市の連結を選 び , その真ん中の 2 都市の順番を入れ替え るとします。するし 4 都市間の 3 本の移動 経路が変わるので , 移動コストも変化しま す。変化したコストが , 最初のものより小 さければ , その経路のほうがよい答えだと いうことになります。この操作を問題の規 模に応じた回数だけ行ってやれば , よりよ い答えが得られるでしよう。 これもプログラムの作成例 ( ans2. c ) を付 録 CD ー ROM に収録しています。前回の List 5 を元に , さらに答えをよくするプログラム になっています。 ランダムな要素を持ったアルゴリズムを 考える場合は , どのようにランダムな要素 を持たせるかで答えのよさが変わってきま す。いろいろ考えてみるとよいでしよう。 / 2 C MAGAZINE 2001 2
スプライン一族 第 5 回 久保裕一郎 / 宇治社中 (http://www.cc.「 im. o 「 . jpFdeviIman/) 今回から基礎となる概念を使って新しい話題に取り組んでみます。 ます基礎となる部分から解説を始め , それが現在知られている技術 にどのように結び付いていくのかを紐解いてみましよう。今回はス プライン曲線について取り上げます。 はじめに は少々問題があるのです。たとえばが 0 の わりと理解しやすい数式ですが , これに だとすると Fig. 1- (a) のようになります。 元上での円の数式をあげてみます。半径 1 curves) の話をしましよう。例として , 2 次 まず , パラメトリック曲線 (parametric バラメトリック曲線 題です。 すが , 理解すると確実に芸の幅が広がる話 す。少し敬遠されがちなスプライン曲線で く過程を追うのが今回のメインテーマで 便利に , そしてそのせいで複雑になってい ついての話です。単純なものが , 汎用的に その始めとなる今回はスプライン曲線に して , 新しい概念に取り組んでみましよう。 須になってきます。今までの概念を部品と りなのですが , その理解には基礎概念が必 話ではなく , 十分に応用範囲の広い話ばか り上げていきます。枝葉といっても特異な 回からは木でいうところの枝葉の部分を取 グに必要な基礎概念を述べてきました。今 これまで駆け足ながら , 3D プログラミン C MAGAZINE 2001 2 とき , は 1 または -1 と答えが複数出てきて 108 しまいます。逆に ! を用いても x が一意に決 まりません。数学上ではともかく , 3D プロ グラミングには向いていない形なのですが どうすれば x とを一意に決定できるでしょ うか。 答えはこうです。新たに変数〃を用いて Fig. 1- ( b ) のように書き換えます。こうする と , 〃によって円周上の点が一意に決まり ます・・・・・・と , なんだか騙されたような , 当 たり前のような気がしますね。ところがこ れが大いなるパラダイムシフトなんです。 この〃の導入によって , 円周上の点群がた った 1 変数による関数として表現できます。 これを「パラメトリック表現」といいます。 別の例を見てみましよう。 Fig. 1- ( c ) は 2 次曲線の非パラメトリック表現ですが れに変数“を導入して Fig. 1- ( d ) とすると , パラメトリック表現ができます。あれ , 単 に変数を増やしただけのような気がしてき ました。まだ便利さがピンとこないですね。 こはとりあえずパラメトリック表現され る曲線をパラメトリック曲線と呼ぶことだ け覚えて , あとは気にせずに進んでみまし よう。 階数 2 次曲線という言葉が出てきたので , 注 意しなければならないところを解説してお きます。先ほどの曲線の式ではリの 2 乗と Fig. 1 バラメトリック曲線 (a) オリジナルの数式 2 十 ) ・ 2 = 1 (b) 変数を用いて ( a ) を書き換えた数式 XZCOS y=sin ( c ) 2 次曲線の非バラメトリック表現 ( d ) 変数″を用いて ( c ) を書き換えた例 工 = レ 厂リ 2 + 2 レー 1 いう項があったので , 2 次の曲線 , または 2 次関数と呼びました。これからの説明の中 では似たような 3 階の関数という呼び方を することがあります。単純に ( 次数 + 1 ) = 階 数ということになるのですが , 汎化する際 にこの次数もしくは階数自体を変数として 扱う必要が出てくるので実にややこしいの です。 今回は 3 次の曲線に焦点を当てます。 3 乗 の項のある関数が主に利用され , 4 階の曲 線と呼ぶこともできるものです。混乱した ときは再度確認をお願いします。 工ルミート曲線 具体的に点を滑らかにつなぐことを考え てみましよう。 2 点を直線で結ぶ方法は 1 つ
P 冊墅 m 衂 - 第 14 回 シェル関数を使ったレジストリの操作 今回のテーマはレジストリの操作です。このテーマは第 3 回 ( 2000 年 2 月号 ) にも取り上げた ことがあります。このときはレジストリ関数の使い方を解説しましたが , 今回はシェル関数を 使ったレジストリの操作について解説します。 サポートされていないので , 移植性も現状 今回のサンプルに限らず , レジストリを扱 / もう 1 つのレジストリ関数 では厳しいものがあります。 う関数を利用する場合 , 細部にわたって注 しかし , そのぶん構成が整理されており , 意が必要です。 レジストリを操作する API というと , 関 非常に見通しのよいコーディングができる サンプルプログラムの動作 数名の先頭に、 Reg " の付くものを頭に思い ようになっています。今回のサンプルでも 浮かべると思います。ですが , そのほか 扱っていますが , レジストリキーの列挙な に先頭に "SH" が付く , いわゆるシェル関 どは 1 つの関数で済んでしまいます。 最初に , 今回のサンプルプログラムの動 数と呼ばれるもののなかにもレジストリを それぞれ一長一短があるので , 状況に応 作を説明しておきましよう。起動すると , 操作するものがあります。 じて使い分けるのがいいでしよう。 まず Fig. 1 のようなダイアログボックスが Windows の初期のころからある Reg 系の 表示されます。実はこのとき , レジストリ レジストリの操作について 関数に比べ , シェル関数は最近実装された 「 HKEY_CURRENT_USERHSoftware 」サプ ものです。よって , 利用環境にも差があり Wmdows という OS にとって , レジストリ キーの下に「 cmagazine 」キーを作成してい ます (TabIe 1 ) 。基本的に最近の OS ならば はその生命線のようなものです。操作いか ます。このレジストリサプキーは Window シェル関数でも問題はありませんが , たと んによっては Windows そのものを壊す可能 s に登録されているアプリケーションが設 えば lnternet ExpIorer 4.0 ( 以下 IE4 ) がイン 性があることをまず理解しておきましよう。 定情報を保存する時に利用する場所です。 ストールされていないと動作しなかったり , レジストリは , アプリケーションの単純 そのほかのレジストリキーの名称とその利 デフォルトでは Windows 95 でも動作しな な設定情報の保存だけにとどまらず , シス 用方法については , 以前 ( 2000 年 2 月号 ) の テムで利用する値も多数格納しています。 かったりします。また , Windows CE でも 己事で説明しているため , 今回は省略しま Table 1 日 eg 系関数とシェル関数の環境の違い す。 日 eg 系関数 シェル関数 こで , 工ディットボックスに任意の文 Windows NT 3.1 以降 Windows 2000 または旧 4 以降を導入した Windows NT 4 ℃ Windows NT 字列を入力して [ レ亟ユ翦ーボタン Windows 95 以降 Windows 98 または旧 4 以降を導入した Windows 95 Windows を押すと , シェル関数を使って「 cmagaz ⅲ Windows CE 未サポート Windows CE 1 .0 以降 e 」キーの下に入力した名前のキーを登録し ヘッダファイル winreg. h shlwapi. h インボートライブラリ advapi32 」 ib shlwapi 」 ib ます。レジストリエデイタなどで , 登録さ Fi g. 3 [ レジズリッ 2 を三の郭丞タ 2 の動作 しジストリの操作 SH& 指定キー取得ー レジストリ値の列挙 Mic 「 80 廿 Macromedia Netscape Policies tatS uya(tatsuya@exnetcom.com) 一三ロ Fi g. 1 [ レジズリを三作成丞タ 2 の動作 しジストリの作 SH& レジストリサブキーの列挙 レジストリ値の列ー 指定キ Fig. 2 作成されたレジストリキー - - MAGAZIUE - MA•:aAZlUE ごレジストリエ〒イク レジスト丿旧 ) 編集 ( 印表示 ()Øお知こ入り ( E ) ヘルプ凹 △前 釦 w e ーコ瀬 Y 田口 田一」 A ″を 印、」 0 s 阜朝 c 庁翫 「ヨⅢ朝滝 Fdee 烏 イユ / ビュー外 HKEY - リ RRENT - 碼 E 釦れ鬱 e 紀 m TEST 定 ) 08e 00 1 20 C MAGAZINE 2001 2
キストモード ) と , 改行が変換されてしま います。 Windows のテキストファイルは C R + LF の 2 バイトが改行を表しますが , PerI が読み込むときに 1 バイト ( LF のみ ) に変換 してしまうのです。ご注意ください。 File::Find こまででファイルのダイジェスト値を 得ることができました。次に , 指定したデ Fig. 5 List 4 の実行結果例 ( 抜粋 ) File::Find モジュール $name: $ ! 蕚 n 新・ /doc/hyuki/hyuki.com/jb/doc/17while.txt /doc/hyuki/hyuki ・ com/jb/doc/16for.セ x セ /doc/hyuki/hyuki.com/jb/doc/15swit.txt /doc/hyuki/hyuki.com/jb/doc/14if 北 x ヒ /doc/hyuki/hyuki.com/jb/doc/13var.txt /doc/hyuki/hyuki.com/jb/doc/12calc.txt /doc/hyuki/hyuki.com/jb/doc/llhe Ⅱ 0. txt /doc/hyuki/hyuki.com/jb/doc/10mihar.txt /doc/hyuki/hyuki.com/jb/doc/01mokuji.txt ( 以下略 ) Enjoy Perl Programming モジュールを活用はう 現在注目しているファイル名 ( ディレクトリなし ) 現在のディレクトリ 現在のディレクトリとファイル名を合わせたもの シンボリックリンク解決時のファイル名 内容 $File::Find::fuIIname $File::Find::name $File::Find::dir 変数 Table 3 F ⅱ e : : Find モジュールの変数一覧 ィレクトリ以下のすべてのファイルのダイ ジェスト値を取得してみましよう。自分で 叩 endir や readdir を使って再帰的なプログ ラムを書いてもいいのですが こでは Fi Ie::Find というモジュールを使ってみます。 List ディレクトリ / doc の下のファイルの一覧を表示する (t 「 ee. pl) print $FiIe: :Find: :name, ”基 n sub print—f e { ex 辻 ( 0 finddepth(%&print—file, use Fil e : : Find; '/doc' / doc の下の . txt ファイルのダイジェスト値を取得する (digest_t 「 ee. pl) List :$name%n" ・ close(FILE); print $dObj ->hexdigest, $dobj->addfiIe(*FILE); binmode ( F I LE open(FILE, $name) or warn "warning: return unless ($name = ~ / . tx / return unless ()f $name); my $name = $File: :Find: :name; sub digest—fil e { ex 辻 ( 0 finddepth(%&digest—file, '/doc' $dObj = Digest->MD5; use Digest ー use Fil e : : Find ー まず , File : : Find の簡単な使い方を示し ます。 List 4(tree. (l) はディレクトリ /doc の下の , ファイルおよびディレクトリの一 覧を表示するプログラムです。実行結果は Fig. 5 に示します。 こで使われている関数は , finddepth というものです。指定したディレクトリ以 下の個々のディレクトリエントリごとに指 定した関数をコールバックしてくれます。 具体的には , finddepth(%&print_file, '/doc' ) ; という関数を呼び出すと , / doc ディレクト リ以下のディレクトリエントリごとに関 数 &print-file を呼び出してくれるのです。 すべてのディレクトリエントリの呼び出し が終了すると , この finddepth 自体が終了 して制御が戻ってきます。 finddepth の代 わりに以下のように find を使うと , サプデ ィレクトリ以下を探ることはせず , 指定し たディレクトリ内のみをスキャンします。 find(%&print_file, '/doc' ) ; コールノヾックされた関数 p ⅱ nt ー file では , ディレクトリを含まないファイル名が特殊 変数 $ ーに代入されてやってきます。ディ レクトリを含んだファイル名は , List 4 の とおり , $File::Find::name という変数で得ることができます。 Fi1e::F ⅲ d モジュールの変数一覧を Table 3 に示し ます。 ダイジェスト値を得ながら ディレクトリを移動 こまで準備ができれば , ダイジェスト 値を取得しながらディレクトリを移動する Enjoy perl Programming モジュールを活用しよう 85
= 3 最新の たら , Win32 API の GetProcAddress で , 関 数名から DLL 内の関数のアドレスを取得し ます (Fig. 8 ) 。これで DLL の関数が使えるよ うになりました (Fig. 9 ) 。 LoadLibrary でロードした DLL は使用が終 わったら Win32 API の FreeLibrary で必ず解 放します。 FreeLibrary(g_hdmdlI) ; g_hdmdII=NULL; 先ほどのインポートライプラリを使用す る方法と比較するといろいろとめんどうな び出されますが , adLibra Ⅳを使う場合は 処理が多くなりますが , 慣れてしまうとけ 変数を宣言していきます。 インポートライプラリを使用する方法で Fig. 7 のようにプログラム内で明示的に呼 っこう便利です。 び出しを行います。 DLL のロードが終わっ なにしろ開発環境の違いや DLL のバージ はプログラムの起動時に自動的に DLL が呼 FadeOut 関数 IsPIaying 関数 extern ″ C ” int —stdcall FadeOut(int no) extern ” C ″ int —stdcall IgPlaying(int no) HRESULT hr; HRESULT hr; 加セ cu = 〃カウント用 DWORD セ countl = 0 / タイムカウント用 1 DWORD tcount2 = 0 / タイムカウント用 2 long mvo ト 0 / フェードアウトで使用するマスターポリューム操作用 if(false==gf—coini) {return 11 羶 / / colnitia は ze 呼び出し済みかどうかの確認 if(no<0){return 1 / / 引数のチェック 土 f ( no > 2 ) { て e セ n 1 〃引数のチェック if(NULL==g—pIPerformance) {return 52;)//performance のチェック if(NULL==g—PISeg[no] ) {return 71;)//segment のチェック hr=g—pIperformance—>IsPlaying(g—pISeg[nol , 製 U if(SUCCEEDED(hr) ) { if(S—OK==hr) { ⅶ幻 e ( 6 u ) ( tcount1=GetTickCount ( ); if( (tcount1-tcount2)>400) { mvo ト = 50 hr=g—pIPerformance—> SetGlobalParam(GUID—PerfMasterVolume, (void*)&mvol, (DWORD)sizeof(mvol) 土 f ( FA 跖団 ( 社 ) ) {return 55 / / メソッド set 引 0 ゆ a て am 失敗 tcount2=tcount1 ー cu 十十一 Fig. 5 使用する関数のテータ型を定義する typedef 土 n し ( —stdca Ⅱ *PDLLFUNCI ) ( int * , int * , int * , int * //OpenDm light typedef int (—stdca Ⅱ *PDLLFUNC2 ) (HWND , BOOL) ;//CreateLoaderPerformance typedef int („stdca Ⅱ *PDLLFUNC3 ) (LPSTR); //SetSearchDir Fig. 6 使用する変数を宣言する PDLLFUNCI g—pOpenDm Iight=NULL; //OpenDm は ght へのポインタ PDLLFUNC 2 g—pCreateLoaderPerf ormance=NULL / CreateLoaderperformance へのポインタ PDLLFUNC3 g—pSetSearchDir=NULL;//SetSearchDir へのポインタ List List if(false==gf—coini) {return ll;)//colnitialize 呼び出し済みかどうかの確認 if()o く 0){return 1 / / 引数のチェック if(no>2){return 1 / / 引数のチェック if(NULL==g—pIPerformance) {return 52 羶 / / p fo て mance のチェック if(NULL==g—PISeg[no] ) 仕 e に n 71;)//segment のチェック hr=g—pIPerformance—>IsPlaying(g—pISeg[no], NULL); if(SUCCEEDED(hr)){ if(s—OK==hr) {return 0 return ー 1 ー )else{return 54 羶 / / メソッド IsPlaying 失敗 List Maste 「 G 「 ooveLevel 関数 0 extern ” C ″ int —stdcall MasterGrooveLevel(BmL fset, 土 n し *val ) HRESULT hr; c て e ち / / マスタ oove v 引の取得 / 設定用 if(false==gf—coini) {return 11靆〃 colnitialize 呼び出し済みかどうかの確認 土 f ( も L = = v 引 ) {return 2 / / 引数のチェック if(NULL==g—pIPerformance) {return 52 / / pe て f0 て mance のチェック if(TRUE==fset) { if(*vaI<-100){return 2 羶 / / 引数のチェック 土 f ( * va 100 ) { て et n 2 〃引数のチェック glev=(char) (*val hr=g—pIPerformance->SetGIobaIParam(GUID—PerfMasterGrooveLeveI, ( vo 土 d * ) & e ち (DWORD)sizeof(glev) if(FAILED(hr) ) 仕 e 加て n 55 / / 虻 e て G て oove も ev 引の設定に失敗した return 0 ー )else{ hr=g—pIperformance->GetGlobaIParam(GUID—perfMasterGrooveLeveI, (void*)&glev, (INORD)sizeof(glev) if(FAIT,m(hr) ) {return 56 羶〃 erGroove v 引の取得に失敗した *val=(int)glev; return 0 ー }//while end Stop(TRUE, 0 0 トの〃デフォルトのマスターポリュームをセット hr=g—pIperformance->SetGI Oba. lParam ( GUID—PerfMasterVO lume , (void*)&mvol, (DWORD)sizeof(mvoI) 土 f ( FA 跖 ( ) ) {return 55 羶〃メソッド Set 引 0 a て am 失敗 return 0 ー } 〃迂 S-OK end )//if SUCCEEDED end return -1 ー List 1 2 StopMusic 関数 extern ” C ″土 n し—stdcall StopMusicÄ)L f 虻Ⅱ , int no) if(false==gf—coini) {return 11 / / coln 址土 a は ze 呼び出し済みかどうかの確認 return Stop(fstpall , no 63 特集 3 最新の DirectMusic を使う
特集 1 XMLX 門 は , 変数 itemld に保存する (List 9 の 7 行目 ) 。 を読み込み終えてから処理を行うように設 べる。 chiIdNodes コレクションで item プロ パティを参照すれば , その子要素の保持す HTML 文書中で定義したデータアイラン 定する。 る値が取り出せる。 item にも 0 から始まるイ ドに変数血 ame の保持する XML 文書を読み parseError は DOM のエラーを保持するオ ンデックスを与える。要素「品番」は最初の 込ませるため , parse 関数を呼び出す。 XM プジェクトで , parseError プロバテイから 要素なので , item ( 0 ) とする。その保持す 参照できる。もし文書の読み込みに失敗す LDOMDocument オプジェクトへの参照が る値は text プロバティで取得できる (List 9 れば , parseError. errorCode が 0 以外の値と 返り値となるため , それを変数 document に の 45 行目 ) 。 受け取っている (List 9 の 12 行目 ) 。 parse 関 なる。このとき parseError. reason でエラー キー項目 (itemld) と「品番」の値が一致し メッセージが参照できる (List 9 の 25 , 26 行 数の処理は次項で説明する。 目 ) 。 たら makeList 関数を呼び出し , ループ内で XMLDOMDocument オプジェクトの doc 現在処理中の要素 ( child が示すオプジェク umentEIement プロバティには , 読み込んだ 最後に , 文書を読み込んだ XMLDOMDo cument オプジェクトへの参照を返す (List 9 ト ) からその子要素の値を文字列として生 XML 文書のルート要素が保持されている。 これを変数 t 叩 vel に受け取る (List 9 の 13 の 28 行目 ) 。 成する ( List9 の 48 行目 ) 。 ループを最後まで繰り返しても該当する 行目 ) 。 searchltem 項目が見つからなかったときは , 処理結果 最後に , 先頭要素 t 叩往 vel , 出力先。 u ゆ を保存する変数 text が初期化されたままと ut, 検索キー itemld の 3 つを引数にして , 実 XML 文書の先頭要素 ( ノード ) , 出力先 , なっている。その場合は , 工ラーメッセー 際の検索処理を行う関数 searchltem を呼び 検索のキー項目を引数に , 項目「品番」の値 ジを出力する ( List9 の 52 ~ 54 行目 ) 。 出す ( List9 の 13 行目 ) 。 がキー項目に合致する要素を探し , その子 要素をすべて出力先に表示する。 parse 引数 n 。 de は XML 文書に記述された構造 の先頭要素にの例では「商品一覧」 ) を指し 引数として要素 ( 商品 ) を受け取り , その finditem から呼び出され , データアイラ 子要素の値を文字列として整形して返す。 ている。そこで , 処理の対象を 1 っ下の要 ンドに XML 文書のリストを読み込む。引数 素 ( 商品 ) に移動して , 要素「商品」の数だけ 品番 , 品名 , 単価の 3 つの要素に対して , として XML ファイル名と XMLID を受け取 List9 の 63 ~ 65 行目のように項目名を示す 処理を繰り返す。子要素は childNodes オプ る。 文字列と要素の保持する値とを連結し , 1 ジェクトに保持されており , その集合 ( コ XML ID で示されるデータアイランドに レクション ) である childNodes を通じて参照 つの文字列にするだけである。 XML 文書を読み込ませるには , 以下のよう できる。子要素の数は childNodes の length もし要素名を示す文字列が不要なら , 以 にする。 プロバテイから取得できる ( List 9 の 40 行 下のような for ループで処理することもでき ・ async プロバティを f se ( 偽 ) に設定す 目 ) 。 る。 る for ループの中では , 要素「商品」のさらに ・ XML 文書を指定して load メソッドを実 子要素である「品番」の値を調べなければな 行する らない。そこで , 変数 child に子要素への参 ソースコードでは List9 の 22 , 23 行目のよ 照を受け取る。ここで child に受け取るのは うになる。 ルートのすぐ下の要素ーー「商品」である。 async プロバテイも load メソッドも IE5 固 chiIdNodes コレクションでは , インデック 有のものである。したがって , この処理は XML データアイランド スによって「何番目の子要素」かを指定でき IE5 以降のプラウザでしか正常に機能しな る。 インデックスは 0 から始まる。カウンタ ここでは , XML データアイランド (Data async はデフォルトでとなっており , lsland : 以下「データアイランド」 ) と呼ばれ 変数 i をインデックスに用いることで , for 文書の読み込みとデータ処理が非同期で実 ループ内ですべての要素を操作させている 行される。すると , XML 文書がすべて読み る機能を利用した。 データアイランドは , HTML 文書中に X (List 9 の 43 行目 ) 。 込まれないうちにデータ処理が実行されて ML 文書を埋め込んで操作する場合に用い 変数 child のさらに子要素を参照して , 品 しまう場合がある。これでは都合の悪い場 番の値と itemld の値が等しいかどうかを調 合が多いため , false としてすべてのデータ られる機能だ。 特集 1 XML 入門プログラミング基礎の基礎 2 / makeList var for (i = 0 ・ i く 3 ; i + + ) textList + = child. chiIdNodes.item (i) . text
ョンアップを気にすることなく DLL をプロ ので , スタイルべースのセグメントによる DirectX 8 のオーディオコンポーネントは グラム部品として利用できるのですから , サンプル曲 ( 4 小節ほどの短いものです ) を V1sualBasic からストレスなく呼び出せるの その利点をうまく活用すればアプリケーシ 用意しておきました。グループレベルの変 で , わざわざ dmlight. d Ⅱを使用する利点は ョン側のプロジェクト管理も楽になり , 比 更による曲の変化はスタイルのパターンの 少ないかもしれませんが , いくつかの処理 較的自由に開発を進めることができるよう 区切りごとに起こります。 についてはⅥ sual Basic で直接プログラミン になります ( ただし , 関数の引数を変更し グすると多くのコードが必要になる部分も [ 注 6 ] とりあえず , インプライズ開発環境用の ものし VisuaI C + + 6.0 用 ( 5.0 でも使用可 ) のも たときなどは要注意 ! また , DLL のハン あります。そのような場合には VisualBasic のを付録 CD-ROM の VcmagaVtoku3%dmlight%Iib ドルを解放してしまったあとにポインタで から dm ⅱ ght. d Ⅱを使用するメリットがある %borland と VcmagaVtoku3%dmIightYIibVvc に収 録してあります , それ以外の開発環境でご使用 関数を呼び出すことがないようにすること かもしれません。 になる方はその開発環境で dm ⅱ ght 剛を構築し も大切です ) 。 たときに生成されるインポートライブラリを使 Visual Basic で dmlight. dll の関数を使用す 用するか , もしくは開発環境に付属しているイ dmlight. dll を LoadLibrary 形式で使用する るには Wm32API を使用するときと同様に ンポートライブラリ生成用のツール ( たとえば , インプライズの開発環境に付属してい剳 MPL 旧 サンプルプログラムを付録 CD - ROM に収録 Declare ステートメントによる DLL のエキス ユーティリティなど ) で作成したものをご使用 しました (YcmagaYtoku3YdmlightYtestYsour ポート関数への参照の宣言が必要になりま ください。 ceYcppYtest. cpp) 。コンパイル済みの実行フ す。 ァイル (test. exe) とサンプル曲のデータは付 たとえば , OpenDmIight を使用するため VisuaIBasic 6.0 から DLL を使う 録 CD-ROM の YcmagaYtoku3Ydmlight}testY の宣言は Fig. 10 のようになります。これで V source \ bin に収録しています。グループレ 次に Visual Basic 6.0 ( 以下 , Visual Bas isual Basic が使用可能な変数の型 / 戻り値の ベルを変更する機能が使えるようになった ic ) から利用する方法について紹介します。 型で関数を呼び出すことができます。 Download 関数と Unload 関数 CloseDmIight 関数と CleanUp 関数 extern ″ C ” int ーー 8 セ dca Ⅱわ 0 ⅱ oad ( 土 n セ no, BOOL freload) HRESULT hr; List List 14 extern ″ C 新 int —stdcall CloseDmIight( ) c 厄 ( return 0 ー if(false==gf—coini) {return ll;)//colnitialize 呼び出し済みかを確認 土 f ( no く 0 ) ( て et n 1 〃引数のチェック 土 f ( no > 9 ) { て e セ n 1 〃引数のチェック if(NULL==g—pIPerformance) {return 52;}//Performance のチェック if(NULL==g—pISeg[n01 ) {return 71;)//Segment のチェック / / freload が TRUE なら gf ー do [ no ] が t て ue でも / / / / 再度ダウンロードを実行する 土 f ( 駅 = = f て oad ) { if(true==gf-downld[nol ) { g—pISeg[no]->UnIoad(g—pIPerformance); gf—downld[no]=false; hr=g—pISegtno] → Download(g-pIPerformance) / ダウンロード実行 if(FAILED(hr)){return 73 / / ダウンロード失敗 gf—downld[nol=true; return 0 ー }else{ if(true==gf—downld[no] ) {return ー 1 羶 hr=g—pISegtno ト >Download(g—pIPerformance) / ダウンロードして 〃いない場合は実行 if(FAILED(hr)){return 73 / / ダウンロード失敗 gf do [ no ] = t て u return 0 ー void CleanUp( ) int c 〃カウント用 if ( true==gf—coini ) { Stop(TRUE, 0 //segment のクリーンアップ / / for(cu=0; cu く 1 cu 十十 ) { if(NULL!=g—pISeg[cu] ) { g—pISeg[cu ト >Release( g—pISeg[cu]=NULL; gf—downld[cu]=false;// アンロードは C 日 eDo にまかせる }//if end } / / fo て end / / セカンダリセグメント用のオーディオパスクリーンアップ / / if(NULL!=g—p13DAudioPath) { g—p13DAudioPath->ReIease( g—p13DAudioPath=NULL ・ //performance オブジェクトの終了処理、クリーンアップ / / if(NULL!=g—pIPerformance) { g—pIperformance—>CloseDown( g—pIPerformance—>Release( ); g—pIPerformance=NULL ・ / 九 oade てオブジェクトのクリーンアップ / / if(NULL!=g-pILoader) { g—pILoader->ReI ease ( g—pILoader=NULL ・ extern 新 C ” int ーー 8 セ dca Ⅱ Unload(int no) if(false==gf—coini) 仕 e セ n 1 )//colnitialize 呼び出し済みかを確認 迂 ( no く 0 ) { て e セ n 1 羶 / / 引数のチェック if(no>9){return 1 / / 引数のチェック if(NULL==g—pIPerformance) {return 52;)//performance のチェック if(NULL==g—pISeg[no] ) { て e セて n 71 / / Segme 北のチェック if(false==gf—downld[nol ) {return ー 1 g—pISeg[no ト >UnIoad(g—pIPerformance); gf—downld[no]=false; return 0 ー //CoUninitiaIize の呼び出し / / CoUninitiaIize( gf—coini=fa lse ー 6 イ C MAGAZINE 2001 2
Fig. 6 ベジェ曲線 Fig. 5 Fig. 3 ー ( e ) を行列で表現してみた例 2 ー 2 1 1 ー 3 3 ー 2 1 0 0 1 0 1 0 0 0 0 1 ・ 0 1 ・ S(u)= いドリ 2 リ 1 トルでもいいのです。 2D でも 3D でも , 工 ート関数やその値は同じなのです。 2D とまったく同じ手順なのに , 3D 空間上 の点を滑らかにつなぐ曲線を生成してくれ るのは , かなり便利ですよね。 マトリクス表現 便利ついでに , いつもお世話になってい る行列の式で書いてみましよう。 Fig. 5 の ように行列を用いて表現する方法もあり , そのときには真ん中の行列がエルミート行 Fig. 7 ベジェ曲線の数式 列と呼ばれています。“も外に出されて単 ( a ) 2 階の関係式 なる定数による行列になっているのですが , 曲線の形や性質を決定しているのが , この が 1 = ( 1 ー唖 1 + 驂 部分なのです。スプラインというと難しく ( b ) 3 階の点を求める 聞こえますが , ようはうまくつながるよう が 0 = ( 1 ーのが。 + = ( 1 ーの 2 〃 + 2 可 1 ーの + 22 にこの部分を決めているだけのことです。 なぜこんな行列になるのか知ってしまうと , (Bezier Curve) のできあがりです。あまり てエルミート曲線と同じように ずいぶんと楽になった気がしてきません が 0 のと にあっけなくて不思議な感じがします。 きを 0 , 1 のとき〃 , になるとします。すると , あるリのときの点は , 線分〃 1 を心 (l-u) と 定義を導く いう比率で分割していることがわかりま す。この内分点をここでは〃 10 と名付けまし た。右肩の 1 が次数を表していて , これだ 先ほどのが 0 の数式を見ると , どうやら工 先ほどは , 方向を決めておくことで曲線 けだと線形補間を行っていることになりま ート曲線のときと似た形になっている ノレミ す。がとが 1 は比例の関係で , Fig. 7- ( a ) の ことがわかります。用意した定義値を“の を定義したわけですが , 今度は始点と終点 関数で足し込んでいる形です。もう 1 点増 以外に新たな点を導入することで定義して ように書くことができます。 やして同じ手順をもう一段階行うと Fig. 7- みましよう。ますは点を 1 つだけ追加して 同じことを今度はがとがの間で行うと すると , Fig. 7- ( b ) となり , 聴の 2 次関数に ( c ) のように 4 階のベジェ曲線を求めること みます。ただ , そのままでは 3 階止まりに なるので , ルールを理解したあとで 4 階に なります。このが 0 , Fig. 6 から見てもわか ができます。工ルミートのときと違うのは , りますが , “が 0 から 1 に変化すると , それ 方向べクトルが追加の 2 点になったことと , 拡張します。 混ぜ合わせるための関数 (blending function) につれてを 0 からを 1 に滑らかに変化していき Fig. 6 の図を見てください。与えた点は ます。実はこれで , 3 階のベジェ曲線 〃 0 , を 1 , を 2 の 3 点です。まず , 〃 0 と〃 1 を考え の形が違うことだけです。このベジェ用の 〃 2 ( c ) 4 階のベジェ曲線 BO (u)=(l ーの 3 B ( の = 3 可 1 ーの 3 召 ( の = 3 1 ーの 2 , 3 町 3 ( の剛 3 s ( の = 県 0 加。 + 町 30 加 + B2 , 30 加 2 + B3 , 3 ( 3 へジェ曲線 1 1 0 c MAGAZINE 2001 2