TSR 型デヾイスドライバの記述法 CON 日 G. SYS によるテパイスドライバの常 駐は , 追加登録時に手数かかかります。そこ で TSR プログラミング手法を用いて , ADD D 日 V/DELDRV コマンドの機能を持たせた TS 日型テパイスドライバの作成に挑戦します。 くても , 疑似変数などの小細工がきく機能 6 ) を先頭に持ち , Fig. 7 のような構造をして を使用することによって , C 言語のみて、記述 います。一方 , Turbo C/C 十十のタイニィ TS R 型テパイスドライバ て、きます。そこて、 , 本バートて、は TurboC/ モデルのセグメント配置は Fig. 8 のような構 C 十十を中心に説明していきます。 MS-C て、 造をしています。また , MS-C のタイニィモ TSR プログラムの応用として TSR 型デバ は TurboC / C 十十の疑似変数に相当する機 デルのセグメント配置は Fig. 9 のような構造 能がないため , インラインアセンプリ言語 イスドライバを作成します。 TSR 型デバイ をしています。 て、記述します。 スドライバとは , デバイスドライバを TSR C 言語て、デバイスへッダを定義すると , プログラムとして記述したものて , CONFIG. DATA セグメントに取られるため C 言語て、デ SYS< はなくコマンドラインからデバイス バイスドライバを記述する際にデバイスへ テパイスドライバの構造 ドライバを登録するもの ( 簡単にいえば , ッダをファイルの先頭に持ってくることが ADDDRV/DELDRV コマンドの機能を取り ひとつのネックになります。デバイスへッ 込んだデバイスドライバのこと ) て、す。実は デバイスドライバはデバイスへッダ ( Table ダを TEXT セグメントの先頭に定義て、きれ 参考文献 [ 1 ] て、アセンプラレベルて、の手法 Fig. 7 デバイスドライバの構造 ( COM 型 ) を紹介しているのて、すが , 本バートては , これを C 言語てアプローチしてみます。べー 下位アドレス スとなる部分はアセンプラレベルて、の手法 と基本的にはさほど変わりませんが , C 言語 て、はアセンプラほどの小細工がて、きない部 分があるのて , 細かい点てはかなり異なっ ています。 筆者はこれまてさまざまなコンパイラを 使用しましたが , アセンプラ並みの小回り がきくという点て , 常駐プログラムには Turbo C/C 十十がもっとも適しているという印象を 得ています。ほかの処理系てはスタック操 どうしてもアセンプラの助けが 作などに , 必要となります。しかし , Turbo C/C 十十 てはインラインアセンプリ言語を使用しな 46 C MAGAZINE 1 1 7 テパイスストラテジェントリーポイント テパイス割り込み工ントリーポイント テパイスへッダ ストラテシルーチン 割り込みルーチン コマンド番号 1 以上の処理 コマンド番号 0 : 初期設定 通常は初期化後に切り離す ↓ 上位アドレス
C プログラマのための Fig. 4 初期化部分を切り離すコンノヾイル方法 CTurbo C 2.0 ] link exppathc 十 exppathr 十 %Tu 「 bO C%*L 旧十 exppathi, exppath, /noi, %Tu 「 bO C%*L 旧 , tcc -c -w -ms -zPDGROUP exppathi. c -zTurbo CODE exppath 「 . c tcc -c -B -w -ms -k -zACODE -zBCODE -zCRESI TEXT -zDRESI BSS -zPDGROUP -zRRESl DATA -zTu 「 bO CO DE exppathc. c tcc -c -w -ms -k- -zACODE -zBCODE -zCRESI TEXT -zDRESI BSS -zPDGROUP -zRRESl DATA げ u 「 bo C 十十 1 .0 ] tlink exppathc 十 exppathr 十 exppathi 十 %Turb0 C%*L 旧 *cOs, exppath, /c, %Turb0 C%*L 旧 tcc -c -w -ms -zPDGROUP exppathi. c -zTurbo CODE exppathr. c tcc -c -B -w -ms -k -zACODE -zBCODE -zCRESI TEXT -zDRESI BSS -zPDGROUP -zRRESl DATA -zTurbo CODE exppathc. c tcc -c -w -ms -k- -zACODE -zBCODE -zCRESI TEXT -zDRESl BSS -zPDGROUP -zRRESl DATA DOS-Extender を挿入し , EXP ファイ ルをフルバス名に置き換え というような手順を踏んて、います。 chain intr( ) 関数 (Table 9 ) は割り込み のチェーンを行うためのものて、 , この関数 を呼ぶとスタックと各レジスタの内容が最 初に割り込みが起こったときの状態に戻さ れて , 指定された割り込みハンドラにジャ ンプします。したがって , スタックが深く なることはありません。 Turbo C/C 十十に は , このような関数がないのて、 , インライ ンアセンプリ言語て、記述しています。 ②初期化部 ( main ( ) 関数以降 ) 初期化部は以下のようなことを行ってい ます。 ・ DOS-Extender の取得 DOS-Extender を取得するために環境変 数 RUN386 が定義されているかどうか調べ る。未定義の場合は , run386. exe, exe386. exe のどちらかが存在するかどうかを PATH のリストをたどって調べる。 ・環境変数 EXPPATH または PATH の取得 ・常駐の確認 ( chk 21 ) トラップする割り込みべクタ 21h を取得し て , べクタが指し示す内容を調べて , 常 駐しているかどうかを確認する。 常駐していなければ , 割り込みべクタ 21 h を新たに設定して , tsr exit ( ) 関数て、常 駐する。すて、に常駐していれば , EXP フ アイルの検索パスと DOS-Extender を再設 定する。 ・常駐部の解放 ( ts 「 release) スイッチー R が指定されると , 割り込みべ クタ 21h を元に戻して , 常駐部を解放す る。ただし , 割り込みべクタ 21h が常駐部 を指し示していないときは常駐部は解放 tcc —B ¯w ¯k exppath. c ・ Turbo C/C 十十 / st : 0X2800 / cp : 0X500 cl /J /Gs /W3 /zp exppath. c /link ・ MS-C 6 . 0 以下のようにコンパイルします。 コンバイル しない。 などて表示されるテンプレートが変わって RUN386 / EXE386 を挿入するため , 囮キー EXP ファイルを起動すると , 先頭に マシンて、あれば動作するはずて、す。 部分はないため , MS ー DOS 3. x が動作する 3.1 て、動作を確認しています。機種依存する ー DOS 3.3B と FM ー TOWNS の MS ー DOS 本ユーティリティは PC ー 9801 シリーズの MS 定してください TurboC / C 十十て、はスイッチー k を必ず指 しまうのて、 , 注意してください なお , 今月号の特集て、紹介したように TurboC / C 十十て、あれば , 初期化部を切り 離して常駐するように変更することも可能 て、す。初期化部分を切り離す常駐型 . EXP 起 動ユーティリティを , 常駐部 copyright : exppathc. c 常馬主部 : exppathr. c 初期化部・ exppathi. c に示します ( すべて付録ディスク収録 ) 。コ ンパイルは Fig. 4 のようにしてください Turbo C 十十て、はリンカに MS-LINK を 使用しています。 tlink て、は正常に動作する プログラムが生成て、きないため , 注意して ください ( 理由は特集を参照 ) 。 おわりに 筆者は GNU の C コンパイラ GCC をもと に , DOS-Extender 上て動作する 68K クロス コンパイラの移植に挑戦しましたが , その とき , EXP ファイルの起動が不便なのを感 じて , いくつかユーティリティを作成しま した。今回はその中から , ふたつほど紹介 しました。常駐型のユーティリティはプロ トタイプとして作成したものて、 , まだ , 実 用の域には達していませんが , これに DOS シェルの機能を付加すれば ( 本当は逆なのて、 すが ) おもしろいユーティリティになるて、し よう 0MS-Windows 3.0 がどこまて普及す るかは興味のあるところて、すが , DOS-Exten der にもまだしばらくは存在価値があると田 います。 DOS-Extender に関しては日本ては FM ー TOWNS が最初に導入し , 386 版 GCC の移植 に大きく貢献した形になっています。 FM- TOWNS は今後「 MS-Windows マルチメデ ィア拡張」仕様を搭載するようて , おもしろ い存在になってくるかもしれません。 参考文献 . TXT ( 付録ディスク収録 ) に参 考文献を示しておきました。あわせてご参 照ください 7 新 MS-DOS プログラミング入門 81
④実際に N ⅶポインタによる書き込みを行 なお , この方法には問題点がひとつあり この制限は場合によってはかなりきつい っているのではないため , 「 NuIIpointer ます。それは , 制限になる可能性があります。しかし , 筆 assignment 」が表示されても気にしない 「 exit 関数で終了できない」 者の発想て、はこの程度が限界て、した ( この発 ほかにもいろいろ考えられるてしようが , ということて、す。 main 関数の本体 ( far main) 想て、も , ない知恵をかなり絞ったのてす この中てもっとも簡単なのが④てす。実行 てはスタートアップルーチンとはコードセ が ) 。もっとよいアイデアを思いっかれた方 するたびに , 「 Null pointer assignment 」が グメントが異なるため , スモールモデルの がいらしたらご教授ください 表示されることさえ気にしなければ , 無視 exit 関数て、終了すると誤動作することになり これ以外にも以下のような方法が考えら してもかまわないはずてす。しかし , これ ます。 れます。 ては皆さん納得されないて、しようし , 気持 その対策として , return て、 far main 関数 ・デ / ヾイスドライバの初期設定ルーチン ちが悪いのて、 , 筆者の取った対策を紹介し から main 関数に戻ってコードセグメントを で常駐部の関数を呼び出す代わりに , 同 ます。 起動時のセグメントに戻します。 じ機能の関数を初期化部にも用意する TurboC / C 十十には直接コードを埋め込 List Null pointe 「 assignment 対策 むための emit 関数があります。この関 数を用いて List 9 のように識別コードを埋め 込んだ関数を常駐部の最初に定義すれば , 「 NuIl pointer assignment 」の問題は完全に 解決します。 ただし , この関数てスタックフレームが 生成されるとまずいため , コンパイルスイ ッチて一 k ーを指定してスタックフレームを生 成しないようにしなければいけません。 CS=DS=SS 対策 この対策が最大の難関て、した。結論を得 るまて、に試みた失敗の過程をすべて細かく 説明しても仕方がないのて、 , 結論だけをい います。 main 関数を List 10 , List 11 のようにふた つに分けて定義します。そして , List 10 は ミディアムモデルてコンパイルし , List 11 はスモールモデルて、コンパイルします。 うすれば List 10 て、 far main 関数をコールす るときに , far コールとなり , CS レジスタの 値が DGROUP に設定されます。 List 10 , List 11 は同じソース中に記述し てもよさそうてすが , 同じソース中に記述 してスモールモデルてコンパイルすると , push cs call near ptr far main といったコードが生成されるため , コード セグメントを変更てきません。 60 C MAGAZINE 1991 7 1 : void copyright( void ) 3 : # i f def i ned ( -emit--CÖu, 0u, 4 : 5 : 6 : 8 : 9 : 10 : / * 4 d 叩 ( 0 ) db db 'Turbo C + + 13 : * / 14 : #e 1 se 15 : 19 : 20 : 22 : db 23 : db 24 : * / 25 : #endif 26 : } / * NUL しポインタチェック用 * / 〉 0X200 ) / * Turbo C + + _TURBOC_ TURBOC ー ) & & ( a n Copyright 1990 Borland tl. / * Turbo C 2.0 * / -emit--( Ou, 0u, 4 dup(0) 'Turb0-C ー Copyright (c) 1988 Borland lntl. メイン関数 int near main(int,char * * ) , 2 : extern int far far_main(int, char 3 : 4 : int near main( argc, argv ) 5 : int argc; 6 : char *argv ロ : return( far main( argc, argv 8 : メイン関数本体 1 「し 1 人ワレっ 0 -4 ロ 0 CD ー 8 / * 実際のメイン関数の処理 return( 0 ) :
語具 1 導 て、 , コンパイルスイッチを以下に示すよう に設定し , コンノヾイル後オプジェクトファ イルを読者のライプラリに追加し , 最後に メインプログラムにリンクする。 urbo C Turbo C て、は , コンパイルスイッチを以 下のように設定するとともに , TurboC コ ンパイラの INCLUDE ディレクトリに ,con fig. h として以下の内容のファイルを入れて おく必要がある。また統合環境てはなく , コマンドライン上てコンパイルする。 tcc -v -K -J -w -ml -c ( デバッガを使用 /char を unsigned char にする / 漢字を使用する / 警告あり / ラー ジメモリモード / リンクは行わずコンパ イルのみ行う ) (Turbo C 用 config. h) #define TURBOC #ifndef MSDOS #define MSDOS #endif #ifndef PROTOTYPE #define PROTOTYPE 示 0 足す 用 0 、 16 ℃ 09 0 0 マっ 06-00 っ 011 ・く・ ア マ 、 ,. 0 ド - っ 01 、ノっ 3 , 98 ー 0 E O , 1 っ 0 肥Øコ皿付刻印肥靆 2 1 1 1 0 るつっ朝 っ 0 っ 0 っ 0 4 -4 -4 O 静 0 Ø 111 1111111 っ 0 っ 0 っ 0 っ 0 つっ 0 つなつなっ 0 っ 0 っ 0 っ 0 っ 0 000000 っ 0 っ 0 -4 -44 4 4 -4 4 -4 -4 - -0 -0 -0 【 0 -0 -- 0 【 0 【 05 【 066666666660 ー々ー 7 行ーー々ー々ー 0 ー行ー 7 888 List 2 #endif S-C MS ー C ては , 従来と同様にコンパイルスイ ッチを設定するとともに , MS ー C コンパイラ の INCLUDE ディレクトリに , config. h とし て以下の内容のファイルを入れておく必要 がある。 CL /AL /Od /W3 /Zi /J /c 関数 . c ( ラージメモリモード /CodeView を使用 / 警告レベルは 3/char を unsigned char に する / リンクは行わずコンパイルのみ行 (MS-C 用 config. h) #define MSC #ifndef MSDOS #define MSDOS #endif #ifndef PROTOTYPE 応用 C 言語 99
はデフォルトて、スタックチェックを行うコ ードを生成しないのて、 , とくに支障はない て、しよう。 ローカルスタックの設定に当たっては Turbo C/C 十十の疑似変数 , SS, SP を使用しています。 SS, SP の切り換え中 は割り込みを禁止にしていますが , 86 系の CPU て、は SS レジスタ設定直後の 1 命令を実行 するまて、は CPU 自身が割り込みを禁止する ため , 本来ならば不要てす ( 2 ) 。しかし , 生 成されたコードを見ると TurboC て、は , SS= DS SP=(unsigned )(newstk 十 sizeof(newstk) ) , と記述しているにもかかわらず , SS レジス タと SP レジスタの設定の間に push ds SS PO P mov offset DGROUP: newstk 十 256 mov sp, bx というように余分な命令コードが生成され ています。本来は , push ds SS POP sp, offset DGROUP: newstk 十 256 mov というコードが生成されなくてはならない のて、す ( TurboC 十十て、は改善されているよ うてす ) 。そのため , この間を割り込み禁止 にしています。 Turbo C の疑似変数は便利 な半面 , 生成コードを確認しながら使用す る必要があるため , このように非常に使い づらい面を持っています。 ストラテジルーチンその 2 1 : uns igned reqhead-off, reqhead-seg : 3 : void far strategy() reqhead_off reqhead_seg X い 0 lSt ストラテジルーチンその 3 1 : struct SREQHEAD far *preqhead; / * リクエストヘッダアドレス * / 3 : void far strategy() asm mov word ptr preqhead, bx; asm mov word ptr preqhead 十 2, es; 割り込みルーチン ラ域 みスュスフ領 ′ i 眈タシタアク ク存 チチチチ カ壊一ッ 一フカッ ッ , 保 テッ出タ 定査成入破テラ ルルルル設検作し ( 非スフ & スパしス ス慊 , 各コ みみみみ 期体闘力続カカカカカカ闘 込込込込初媒丱 2 入連入入出出出出 00 Q. 割割割割 0 ー 1 つな -4 ド 0 ^ 0 ? ー 8 01 よっ 0 常旧旧 0 く O 、田 0 ・ 本し 0 0 0 0 ハしよし 0 ー 0 本し 0 00 0 T' -4 し し - + し 0 cd CO い 0 00 ・ 0 よし十レ + 》 00 ・ 1 ー十し 0 0 Q) 1 よっ 0 っ 0 -4 ・戸 0 CD 7 ー 8 0 》 011 ワ 0 ・戸 0 ^ し 7 ・ 8 0 , 1 ワ 0 っ 0 -4 ・尸 0 0 ー 8 0 、 1 っ 0 、 4 ^ 0 ー 8 01 人っ朝っ 4 戸 0 行ー 8 01 ーワ】 C•-) -4 11 11 一 14 1 よ 1 よ 1 ー 11 1 今 11 1 よ 0 乙ワ 3 ワ 3 ワ 3 っ乙ワワ 0 ワ〕り 0 り乙っ 0 っ 0 っ 0 っ 0 C'*) Øっっ 0 っ 4 ・ -4 ・ -4 : 4 , 4 : 4 , 4 -4 , 4 ・ -4 ・ LO 戸 0 LD MS-C Turbo C/C 十十て、は interrupt 関数てす が , MS-C< は saveregs 修飾子を使用して います。 interrupt 関数だと DS レジスタに DGROUP をセットする箇所がセグメント参 照となり , COM ファイルに変換て、きなくな ります ( ストラテジルーチンも同じ理由て、 interrupt 関数て、は記述て、きません )0Turbo C/C 十十て、はセグメント参照とならないよう 54 C MAGAZINE 1991 7
0 プログラミング研究 特集 List A PC79801 版 ESC / P プリンタドライバのコンバイル 詳細はリストを参照していただくとして , ポイントだけを簡単に説明します。 テパイスへッダ (sdevhead: List 2 ) デバイスへッダを List 2 に示します。 COM モデルて、は CS = DS て、あるため , ストラテジ ルーチンと割り込みルーチンをオフセット アドレスだけて、表すことがて、きます。 Turbo C / C 十十て、は , この部分て、コンパイラが Warning を出しますが , 気にしないて、くだ 0 MS-C て、も Turbo C と同じ記述てよいはず なのて、すが , コンパイルエラーとなるため , ストラテジルーチンと割り込みルーチンの 工ントリーアドレスを main 関数て、設定する ようにしました ( タイニィモデルて、コンパイ ルしているのだから , Turbo C と同様に Warning て、よいと思うのて、すが 。 MS- C のタイニィモデルはスモールモデルと同じ ような扱いのようて、す ) 。 このため , TurboC / C 十十て、はデバイス ヘッダ ( DATA セグメント ) をなんとかし て , 先頭に持ってくるようにリンクすれば CONFIG. SYS に指定可能て、すが , MS-C< は CONFIG. SYS に登録するデバイスドライ バを記述しようとするならば , 別のアプロ ーチを取る必要があります。 属性はキャラクタデバイスと IOCTL ビッ トをセットしています。 / ストラテジルーチン (tar strategy, strategy : List3) ストラテジルーチンは far リターンする必 要があるため , far 関数て記述します。 Turbo C / C 十十てはストラテジルーチンの本体を呼 び出していますが , MS-C< はインラインア センプリ言語を使用しています。 TurboC/C 十十てはストラテジルーチン 特集 TSR プログラミング研究 51 / * コマンド 工ラ cmderr() : disable(); SS ー引 d ss : oldsp; enable() : asm pop es asm POP ds asm POP bp asm pop d i a S m PO p S ー asm POP dx a S m PO p C X asm pop bx asm pop ax 88 : 89 : 90 : 92 : 93 : 94 : 95 : 96 : 98 : 99 : 100 : 10 1 : 10 2 : void done() 10 3 : 104 : 10 5 : preqhead->status ニ 0X0100 : / * DONE ビットのセット 106 : } 107 : 108 : void cmderr() / * コマンドエラ 109 : 1 10 : preqhead->status = 0X8103 ; 1 1 1 : / * 工ラービット , DONE ビットセット無効なコマンド 1 12 : } 1 13 : 114 : unsigned char bss end; 1 15 : 116 : v 0 i d i n i t ( ) 1 18 : preqhead->unitnum 1 19 : preqhead->ptrans 120 : preqhead->status ニ 0X0100 : 121 / * 初期化処理 / * ユニット数 = 1 / * フリーメモリ開始アドレス / * DONE ビットのセット ニ &bss end; テパイスへッダ タ タ ンタ ンタ ダタインタイン ツンポインポイ へ・イのポ名イのポ スポタのルボタの イすンタイすンタ バ指インア指イン デをポイフをポイ スダリポスダ リポ ノヘントパ へ デスエンデス ( エン タイジェタイジェ バテみ クバテみク ラデラ込ラデラ込 ヤの性トりヤの性トり キ次属ス割キ次属ス割 cd cd , 十し E Q) O , ↑ー CZ O - 00 ・ー・ 0- ・ 1 0 0 0 , しーし Z ・ー・ー t.O C CO O ーしーし 本し + し 0 0 > tn CO 一 1 人ワ 3n0 -4 ・戸 0 《 0 っー》 011 っ乙っ 4 ・′ 0 ^ りー 8 1 ・↓ 11 1 よ 11 、ーよ、ー 4 , ー・、ーよ , ー / * キャラクタデパイスファイル名 lSt ストラテジルーチン 1 : # i fdef _TURBOC_ 引 , ds, es, dx, cx, bx ) 2 : void interrupt strategy( bp, di, cx, bx; / * ストラテジェントリ * / 3 : unsigned bp, di, si, = MK_FP( es, bx ) : 5 : preqhead 7 : 8 : VOid far far-strategy() strategy() : 10 : 12 : #else 13 : void far far-strategy() _asm mov word ptr cs:preqhead. bx asm mQV word ptr cs:preqhead 十 2. es 7 * ストラテジルーチンを呼び出すときに DOS が CS = DS に 設定しているため CS: は不要です * / 20 : #endif / * ストラテジェントリ * / / * ストラテジェントリ * / / * ストラテジェントリ * /
00 Q プログラ ミング研究 特 集 Table 1 プロセスの常駐 Microsoft C Optimizing Compiler —dos—keep 関数 (MS-C) Ver. 6. OOA # include く dos. h 〉 Turbo C Ver. 2.0 void —dos—keep (retcode, memsize); Turbo C 十十 Ver. 1 .01 終了ステータスコード unsigned retcode, 常駐するメモリサイズ ( ノヾラグラフ数 ) unsigned memslze,• 三つの処理系を取り上げ , これらのコン パイラが持つインラインアセンプラや疑似 DOS ファンクション 31h を実行してプログラムを常駐させる 変数などの機能を一部利用して , C 言語だけ keep 関数 ()u 「 bo C/ C 十十 ) <TSR70 ログラムやデバイスドライバを記 # include く dos. h 〉 VOid keep (status, size); 述する方法を紹介します。 終了ステータスコード unsigned cha 「 status, 本特集て、は誌面の関係て、いろいろなケー 常駐するメモリサイズ ( ノヾラグラフ数 ) unsigned size; スごとの詳しい紹介がて、きないのて、 , より DOS ファンクション 31h を実行してプログラムを常駐させる 割り込みべクタ 0 , 4 , 5 , 6 を起動前の状態に戻すため注意すること 詳細に知りたい方は参考文献 [ 3 ] もあわせ て参照してください タ 4 , 5 , 6 が起動前の状態に戻されるため , 定周期で起動される まずは , 小手調べに簡単な TSR プログラ 注意してください。 PC ー 9801 てはキ ②キーポードハードウェア割り込み , RS- ムを C 言語て記述してみます 0MS-DOS のメ , 皿キーの割り込みがこれらに該当し 232C 割り込みなどカ生したときに起動 モリ管理については簡単に触れるだけにし ますから , TSR プログラムがキー ますから , 詳細については参考文献 [ 2 ] , [ 3 ] される キーに割り込む場合には , ひと工夫必 ③ソフトウェア割り込みにより呼び出され などを参照してください 要てす。 る などの方法て、呼び出されます。 プログラムの常駐 プログラムを常駐させるには DOS ファン クション 31h を実行します。ただし , C 言語 て、はスタートアップルーチンて、 0 除算割り込 通常のプログラムは実行終了後はメモリ Table 1 に示した dos keep 関数 , keep 関 みや浮動小数点用の割り込みなどがセット 領域が解放されますが , TSR プログラムは 数は , いずれも常駐するメモリサイズを指 アップされているため , これらの割り込み プログラムの実行終了後もメモリ内にとど 定しなければいけません。そのためにはメ べクタを元に戻す必要があります。そのた まり , MS ー DOS の標準の機能にない機能を め , 通常の処理系にはライプラリ関数が用 モリ構造を知る必要があります 0Fig. 1 に MS 提供します。 -C, TurboC / C 十十のスモールモデルの一 意されています ( Table 1 ) 。 常駐した TSR プログラムは , TurboC / C 十十の場合には割り込みべク 般的なメモリ構造を示します。 ①タイマ割り込み , VSYNC 割り込みなどで Fig. 1 スモールモテルの一般的なメモリ構造 下位アドレス① MS ー C のスモールモテル 十 0 十 1 十 2 十 3 十 4 十 5 十 6 十 7 十 8 十 9 十 A 十 B 十 C 十 D 十 E 十 F MCB PSP 領域 コード領域 ( 常駐部分 ) コード領域 ( 初期化処理 ) 初期化されているテータ領域 初期化されていないテータ領域 スタック領域 nea 「ヒープ領域 fa 「ヒープ領域 常駐サイズ し」 下位アドレス @Turbo C のスモールモテル 十 0 十 1 十 2 十 3 十 4 十 5 十 6 十 7 十 8 十 9 十 A 十 B 十 C 十 D 十 E 十 F MCB PSP 領域 CS コード領域 ( 常駐部分 ) コード領域 ( 初期化処理 ) SS, DS DS 初期化されているデータ領域 ((unsigned)&end 十 OxOf) 》 4 DGROUP 初期化されていないテータ領域 end nea 「ヒープ領域 スタック領域 fa 「ヒープ領域 ー—PSP —PSP CS SS, DS —PSP DS (——heapbase 十 OxOf) 》 4 c—heapbase DGROUP 上位アドレス 上位アドレス 特集 TSR プログラミング研究 39
特 Fig. 17 コンノヾイル方法 ① Turbo C 2.0 tcc -c -w -ms -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS -zPDGROUP -zRRESl DATA -zTCODE escpresi. c tcc -c -w -ms -zPDGROUP escpinit. c tcc -c -w -mm -zC TEXT -zPDGROUP escpmain. c 0 プログラミング研究 集 ヘッダ (eatcon. h) 初期化部 (eatconi. c) 常駐部 (eatconr. c) すことが可能てす。ソースリストを , tlink escpresi 十 escpinit 十 %TC%*L 旧 *cOs 十 escpmain, escptc, /c, %TC%*L 旧 *cs ただし % TC % は Tu 「 bo C のディレクトリを示す環境変数で , バッチファイルとして記述している @Turbo C 十十 1 .0 tcc -c -w -ms -k- -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS -zPDGROUP -zRRESl DATA -zTCODE escpresi. c tcc -c -w -ms -zPDGROUP escpinit. c tcc -c -w -mm -zC TEXT -zPDGROUP escpmain. c link escpresi 十 %TC%*L 旧 *cOs 十 escpinit 十 escpmain, escptc, /noi, %TC%*L 旧 *cs; ただし % TC % は Tu 「 boC 十十のティレクトリを示す環境変数で , バッチファイルとして記述してる Table 22 tcc コマンドのスイッチー z ( テフォルトはスモールモテルの場合 ) に示します ( 付録ディスク収録 ) 。 この場合は初期化部て常駐部を呼び出し ていないため , main と far main に分ける必 要はありません。これに関しては説明を省 略します。詳細はリストを参照してくださ い。コンパイルは Fig. 18 のように行ってく スイッチ —zAname —zBname —zCname —zDname —zGname —zPname —zRname —zSname —zTname -zX * 要 コードセグメントクラス = name コードセグメントに対するコードグループ名 = name 非初期化テータセグメントグループ名 = name 非初期化テータセグメント名 =name コードセグメント名 = name 非初期化テータセグメントクラス =name 初期化テータセグメント名 =name X に対するテフォルト名を使用する 初期化テータセグメントクラス = name 初期化テータセグメントグループ名 = name ( テフォルト : CODE) ( テフォルト . BSS) ( テフォルト : TEXT) ( テフォルト : BSS) ( テフォルト : DGROUP) ( テフォルト : DATA) ( テフォルト : DGROUP) ( テフォルト : DATA) 例 -zA * → -zACODE 常駐部でライプラリを 使用する方法 3 Fig. 18 コンパイル方法 ① Turbo C 2.0 link eatconr 十 %TC%*L 旧 *cOs 十 eatconi, eatcon, /noi, %TC%*L 旧 *cs, tcc -c -w -ms -zPDGROUP eatconi. c -zPDGROUP -zRRESl DATA -zTCODE eatcon 「 . c tcc -c -w -ms -k- -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS @Turbo C 十十 1 .0 tlink eatconr 十 eatconi 十 %TC%*L 旧 *cOs, eatcon, /c, %TC%*L 旧 *cs tcc -c -w -ms -zPDGROUP eatconi. c -zPDGROUP -zRRESl DATA -zTCODE eatconr. c tcc -c -w -ms -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS ンの仕様が変更されたため , Turbo C と同 じ順序てリンクすると誤動作します。セグ メントのリンクの順番が問題てあるため , 付属するスタートアップルーチンを参考に して , アセンプラなどてセグメントを定義 したファイルを最初にリンクすれば解決し ますが , 今回は C 言語だけて実現することに ポイントを置いたため , リンクの順番を変 更することて解決しました。 なお , リンカに tlink を使用すると外部参 照が変な値に設定されてしまい , 暴走して しまいました ( たとえば , main 関数のコール が callFIBB のようになります )olink は MS -C 5.1 / 6.0 のものて、確認しました。 TurboC / C 十十て、は基本的にスタートア ップルーチンを最初にリンクすることを前 提にしているため , スタートアップルーチ ンを最初にリンクしない場合はいろいろと 小細工が必要になります。 初期化部分を切り離す CON 出力ファイル化 ユーティリティ Part 1 て紹介した CON 出力ファイル化ユ ーティリティ (eatcon. c, 付録ディスク収録 ) も同じ手法を用いて , 初期化部分を切り離 -DATA, ー BSS セグメントの変数を 使用しないライプラリの使用 先ほどは常駐部て、ライプラリを使用する ことはてきないと説明しましたが , ライプ ラリを切り離さずに常駐させれば , 当然 , 使用てきます。かといって , Part 2 て、紹介 したように , near ヒープ領域の直前まて、常 特集 TSR プログラミング研究 63 変更してもかまいません。 escpmain. c を取り去って far main を main に め , main と far main に分ける必要はなく , なお , TEXT セグメントが先頭にあるた のように行います。 このとき Turbo C てのコンパイルは Fig. 20 いことてしよう。 ライプラリを使用しようとすれば仕方がな ライプラリが常駐してしまうことてすが , トアップルーチンや初期化処理て使用した Fig. 19 のセグメント配置の欠点はスター てもそれほど気にならないと思います。 用していないはずなのて , この制約があっ どが DATA, BSS セグメントの変数を使 作や文字列操作などのライプラリはほとん 能な DOS コールを使用していないメモリ操 ラリしか使用てきません。常駐部て使用可 BSS セグメントの変数を使用しないライプ に示します。ただし , この場合は DATA, 常駐部を少なくするセグメント配置を Fig. 19 駐させるのもおもしろくありませんから ,
List 12 ります。しかし , メモリ操作や文字列操作 などの DOS コールを使用しないライプラリ も使用て、きないのは少々つらいところがあ ります。 ただし , ライプラリ関数て、も直接コード に展開される関数は使用可能て、す。たとえ 1 ー 1 十し a. 6 ) 0 + し ー 0 0 4 し . ー CO 0 0 E ・ っ乙っ 0 -4 ・戸 0 0 ー 8 0 ) 0 11 っ乙 CO 4 ・ 0 cD ー 8 0 ー 1 ワ 3 っ 0 4- っ 0 っ 0 っ 00J っっ 0 っ 0 っ 414- -4 ・・ 4 ・ -4 ・ -4 , 4 ・ -4 ・ -4 , ・ -. 0 ド 0 - -0 戸リ / * プリンタレディステータス * / emit_( ) disable( ) enable( ) geninterrupt( ) inportb( ) outportb( ) などの関数は使用可能て、す。 inportb 関数 , escptc. c( 付録ディスク収録 ) て、は , outportb 関数て、入出力が可能となり , genin *(unsigned far * ) reSl_PSP terrupt 関数て、 ROM BIOS をコールて、きるの 初期化部分を切り離す MK FP()P SEG(presidev), & psp) , は助かります。 PC=9801 版 ESC/P て、すが , escpinit. c( 付録ディスク収録 ) て、 これらの関数はデバイスドライバには必 プリンタドライバ は , 要不可欠て、しよう。 reSl PSP なお , geninterrupt( ) は割り込み番号が定 前記の間題点を解決して escptc. c( 付録デ FP SEG(presidev) ー 0X10 : 数の場合は , イスク収録 ) を書き直した初期化部分を切り INT xxh としています。 escptc. c て、はスタートアップ 離す PC ー 9801 版 ESC / P プリンタドライバの という 2 バイトのコードが生成されます。し ルーチンて、設定される変数 psp が常駐して ソースリストをそれぞれ , かし , 割り込み番号が変数の場合はフラグ いますが , escpinit. c て、は常駐していないた 常駐部 (escpresi. c) をブッシュして割り込みべクタが指し示す め , このような差が生じます。 escpinit. c ( 付 初期化部 (escpinit. c) アドレスを far コールするコードを生成する 録ディスク収録 ) て、はコードセグメントの直 main(escpmain. C) ため , TurboC て、は ES, BX, CX レジスタ 前の 100h が PSP 領域て、あることを利用して ヘッダ (escp. h) が破壊されます (List 12 ) 。また , Turbo います (Fig. 15 ) 。 に示します ( 付録ディスクに収録 ) 。 C 十十て、は ES , BX, AX レジスタが破壊さ 使用法などは Part 2 て、示した escptc. c と同 れます。 じて、す。 コンバイル これらのレジスタを引数に持つソフトウ 詳細な説明は省略しますから , 今まて、の ェア割り込みをコールする場合は注意して 説明を参考にして escptc. c と比較して理解し ください てください。今まて、の説明て、足りないポイ geninterrupt( ) 関数の引数を変数にしたの ントを以下に簡単に補足します。 は , ほかの MS ー DOS マシンへの対応をある 常駐部でのライプラリ呼び出しの変更 程度考慮したためて、す ( たとえば , IBM-PC 互換機への対応 ( 機種の自動判定 ) は簡単て、 ① int86 関数→ geninterrupt 関数 ②ストラチクャの代入→ far ポインタメモリ そうて、はなく特定のマシン専用を前提と コヒ。ー fmemcpy 関数の作成 するのてあれば , 引数を #define などて、定義 ③シフト JIS コード→ JIS コード変換 jmstojis して定数にしてもまったく問題ありません 関数の作成 ( なお , TurboC 十十て、は AX レジスタが破 常駐部の PSP セグメントの求め方の変更 壊されるため , 引数は # define て、定義してい 62 C MAGAZINE 1991 7 PO p bp ret ready_prn endp ?debug C E9 TEXT endS ます ) 。 コンノヾイルは Fig. 17 のように行ってくだ Part 2 て、示した escptc. c て、は escptc.com が生成されますが , escpresi. c, escpinit. c, escpmain. c, escp. h( 付録ディスク収録 ) て、は escptc. exe が生成されます。 ファイル名が同じになっているのて、 , 注 意してください なお , あまり馴染みのない tcc コマンドの スイッチ -z の説明を TabIe 22 に示します。 Turbo C 十十て、はスタートアップルーチ
スを返しているだけて、す。 0 TL 入力 コンド番号 . 3 , ioctlin) 常駐を確認するための手段として , ①常駐部のデバイスへッダアドレス ②常駐チェック用の Magic No. を返しています。常駐チェック用の Magic No. は "ESCP" て、す。 IOCTL 入力は DOS ファンクション 4402 h(Table 18 ) の IOCTL キャラクタデバイスか らの制御データの受信時に呼び出されます。 今回のケースとは異なりますが , TSR プ ログラムをあえてデバイスドライバとして 記述する場合があります ( したがって , Part 1 の CON 出力ファイル化ユーティリティ (eat- con. c, 付録ディスク収録 ) をデバイスドライ バとして記述することも可能て、す ) 。デバイ スドライバて、は IOCTL 入出力て、 , ここて、示 したようにほかのプログラムと簡単に通信 て、きるため , TSR プログラムをデバイスド ライバとして記述することには十分なメリ ットがあります。普通に TSR プログラムと して記述して , 常駐を確認するための手段 を提供しようとすれば INT 2Fh システムコ ール (TabIe 4 ) に割り込む必要があります。 0 TL 出力 ゴンド番号 . 12 , ioctlout) 漢字モードと ANK モードの指定データ ( 俗 にいう漢字 IN / 漢字 OUT のコマンド ) を切り 換える機能て、す。スイッチー E の 8 通りの機能 は IOCTL 出力を呼び出して切り換えていま トヨ 〒 H 十十十十十十十十 十十十十十十十十 す。データの並びは TabIe 19 のように合計 32 バイトて、す。指定コマンドが 15 バイト未 満の場合は残りにダミーデータ ( たとえば 00 h) を追加してください リクエストヘッダの転送バイト数 が 32 バイト以外の場合は Table 20 が指定さ れたものとしてコマンドをプリンタへ直接 出力しています。これはプリンタの簡単な コマンドを直接プリンタへ出力することを 想定した機能て、す。 IOCTL 出力は DOS ファンクション 4403 h(Table 21 ) の IOCTL キャラクタデバイスへ の制御データの送信時に呼び出されます。 刀期設定 ( コマンド番号 .0 , prninit) 初期設定はスイッチー E て、指定されたプリ ンタモードに応じてプリンタを初期化して , フリーメモリ開始アドレスを返しているだ けて、す。 Turbo C/C 十十て、は _heapbase の内容 ( ワード値 ) が near ヒープ領域の先頭アドレ スを示します ( Fig. 8 ) 。また , MS ー C て、は変 数 end がスタック領域の先頭を示しています (Fig. 9 ) 。したがって , この値を指定して near ヒープとスタック領域を切り離します。 MS ー C て、は疑似変数が使用て、きないため , se gread 関数て、取得したセグメント値を使用し ただし , escptc. c ( 付録ディスク収録 ) の tsr exit 関数て、は TSR プログラムの参考のため に , 初期化処理て、返されたフリーメモリ開 始アドレスは使用せず , 直接 heap base(Turbo C / C 十十 ) または end(MS (C) まて、を常駐させています。 main 関数以降ては次のことを行っていま す。 ・前述の Fig. 13 のようにして , デバイスド ライバの追加と除去を行う。このとき内 部 FCBI ロック内のデバイスへッダのア ドレスも切り換えている。 ・常駐の確認は , DOS ファンクション 4402 h(TabIe 18 ) て、返される情報によって行 ・常駐後の再起動て、は DOS ファンクション 4403h(Table 21 ) て、スイッチー E て、指定され たモードに切り換える ( 自作プログラムて、 モードを切り換える場合はリストを参考 にしてください ) 。 ・ Turbo C/C 十十と MS ー C て、異なる部分は 常駐終了のための tsr exit 関数て、す。 MS ー C て、は疑似変数が使用て、きないため , se gread 関数て、取得したセグメント値を使用 イ main 関数以降 する。 PC59801 版 ESC[P 丿ンタドライバの説明 Turbo C/C 十十 tcc -w -mt escptc. c イルしてください TurboC / C 十十ては以下のようにコンパ TabIe 15 NEC 漢字の罫線コード ( シフト』 S : 86a2h ~ 86edh ) Table 16 新 J 旧の罫線コードと特殊記号 J 旧 2C20 2C30 2C40 2C50 2C60 0 シフト』 S E 1 F ト 2 0 ト 3 1 ト 4 2 ト 5 3 ト 6 4 ト 1 7 5 8 6 869E 86AE 86BE 86CE 86DE 9 7 ヨ A 8 L ヨ B 9 L ヨ C A ヨ D B ヨ E C ヨ F D ヨ JIS 2840 2830 2820 2260 2250 2240 0 シフト』 S E 1 F L 2 0 ト 3 1 4 2 ト 81 B E 81 CE 81 D E 849E 84AE 84BE 5 6 3 4 亠十 7 8 9 A B C D E F 5 6 7 8 9 A B C D ト丁ヨ亠十一一「 1 ト〒ヨ亠十ト T H 亠 Z 亠 十 56 C MAGAZINE 1991 7