FARLAND PARTY ひびきつくねの ファーランドパーティ |
Last Update : 2008.04.04 Copyright T.Hibiki 2008 |
Windows 豆知識
|
空文字は、半角スペース? SQL Server 2000(以降?)の話である。 SQL Server では、データベースの設定として、「データベースの互換性」というものがある。詳細は省略するが、ここで言いたいのは、これ。 空文字(からもじ)をセットすると、半角スペース 1 個(シングルスペース)に置き換わる。 なんのこと? と思われる方もいるかもしれない。 データベースの互換性を 65 にすると発生する事象である。 テーブルの文字列フィールドに、NULL ではなく空文字をセットした場合、長さがゼロになってほしいと思うだろう。ところが、長さは 1 である。これは、空文字をシングルスペースに置き換える仕様があるからである。 同じ理由で、REPLACE(<Any>, '') は、半角スペース 1 個を返す。'2008/12/31' の '/' を '' に置き換える、としようとしても、'2008 12 31' となってしまうのである。 数式 at Excel 通常、Excel では、セルに計算式(例えば =A3+B3)を入力すると、その計算結果が表示される。今回はこれについて。 (画面キャプチャは、Excel XP (2002) によるもの) まず、表示が左揃えになる。次に、「数式」チェック用の列幅に変わる。長い文字列の場合、「セル内で折り返す」の設定をしていなければ、隣のセルにはみ出て表示されるが、はみ出なくなる。 なお、この「数式」オプションは、シートごとのオプションである。ブック(ファイル)全体のオプションではない。 1900/02/29 by Excel Windows の Excel のセルを「日付」という型にして、0 や 1 といった数値を入力する。 すると、「1900/01/01」や「1899/12/31」、バージョンによっては「1900/01/00」などが表示されるだろう。 試しに、60 を入力してみよう。「1900/02/29」が表示されるだろう。 グレゴリオ暦では、以下のような定義がある。 西暦で、4 の倍数の年は、閏年(うるうどし)で、2 月 29 日がある。 ただし、4 の倍数であっても 100 の倍数である年は、閏年ではない(平年)。 ただし、100 の倍数であっても 400 の倍数である年は、閏年である。 西暦 1900 年は、上記の定義に当てはめると、閏年ではない。ところが、1900/02/29 と表示されてしまうのである。 これにより、1900 年 3 月以前の曜日を取得する際に、問題が出る。存在しないはずの 2/29 が存在してしまうため、曜日の取得が 1 つずつずれてしまうのである。 なお、Excel であっても VBA であれば、上記の問題はない。また、Access やその他のデータベースツールでは上記の問題はない。 Excel の列名・列番号 表計算ソフト Excel では、行と列を使ったセルの指定を行う。行番号が 2、列が C だった場合、C2 のように表現する。 VBA をコーディングしたことのある人ならば、「Range("B4").Select」や「Cells(3, 5).Select」といった記述をしたことがあるのではないだろうか。 ここでは、この A や C といった、列の指定について考える。 アルファベットは、列の指定である。A は 1 列目、B は 2 列目、C は……といった具合である。 ところが、VBA ではこのアルファベットよりも、1, 2, 3 といった数値で指定できたほうが助かることがある。この数値とアルファベットを相互変換する方法を考えてみた。 A〜Z を使って表現する: 26 進数か? A は 1, Z は 26, AA は 27: 26 進数とちょっと違う ここで筆者は、数字を使わずにアルファベット 26 字のみを使って表現するところから、(勝手に)「拡張 26 進数」と呼ぶことにした。拡張も何もあったもんじゃない、という意見もうにゃむにゃ いろいろ考えてみた結果、さらに次のようにまとめられた。 A〜Z を使って表現する: 拡張 26 進数 A: 0, Z: 25 として 26 進数計算を行い、最終的に +1 する 26 進数計算と言ってもピンと来ない方のために、簡単に説明しておこう。 一般的に使われる数(整数)は、0, 1, 2, ……と進むと、9 の次に 10 となる。これは、一桁目が使用可能な文字(0〜9)を使い切ったので、「繰り上がり」が発生したのである。0 と 00 は同じと考えると、"09" の次は 10 の位に +1 して、1 の位を 0 に戻し、"10" となる。10 個の字(0〜9)の次に、繰り上がりが発生するのが「10 進数」である。 2 進数や 16 進数、26 進数も、考え方は同じである。 「26 進数」と言った場合は、26 個の字(A〜Z)の次に、繰り上がりが発生する。0 を 0000 と表現しても同じなように、A を AA や AAAA と表現しても同じ……と考えたいのが、26 進数である。(AA はゼロではない→「拡張 26 進数」と呼ぶ) Excel の列では、A (0), B (1), C (2), …… と進み、Z (25) の次は、AA(26) である。つまり、AA は 0 ではなく 26 なのである。26 進数であれば、AA は 0 であって欲しいところだ。 では、AA (26), AB (27), AC (28), …… と 2 桁で進んでみよう。AX (49), AY (50), AZ (51), BA (52), BB (53), …… と進み、最後は ZX (699), ZY (700), ZZ (701) である。その次は、AAA (702) と 3 桁に突入する。 A :  0×1 +(0) =0 Z : 25×1 +(0) =25 AA :  0×1 +  0×26 +(26) =26 ZZ : 25×1 + 25×26 +(26) =701 AAA:  0×1 +  0×26 + 0×26×26 +(26+26×26) =702 ZZZ: 25×1 + 25×26 + 25×26×26 +(26+26×26) =18,277 (最初の×1 は、26 のゼロ乗) これらより、桁数と値の関係は、以下のように表現することが出来るとわかる。 桁数を d とすると、 Σは総和を表す。26 の n 乗。 Σ、総和は、d>=2 の時に成り立つ。d=1 の時は総和=0 (うまいこと表示できなかったら、ごめんなさい(−−;) 最終的に出た値に +1 すれば、列番号の完成である。 逆に、列番号からアルファベットの列名を生成したい場合は、まず桁数に着目する必要があるということである。 Excel の場合、列は最大で 256 なので 2 桁あれば十分だが、256 より上も考慮し、また 32 ビット整数(約マイナス 21 億〜約 21 億)を範囲とする場合、 = 6.6... (単純な対数計算ではないけど……目安) よって、6 桁まで(〜ZZZZZZ)であれば、32 ビット整数で表現できる。 を飛ばす on HTML HTML データとして、以下のようなデータを生成したとする。 <INPUT TYPE=text NAME=namae VALUE=""> この記述によって、ブラウザにはテキストボックスが表示される。ユーザーは名前(例えば「あいうえ おかき」)を入力して、submit を行う。 このとき、姓と名の間には半角スペースが含まれているとする。 スクリプト側ではそれを受け取り、「これでよろしいですか?」の確認画面を表示する。その際、以下の記述も含めている。 <INPUT TYPE=hidden NAME=namae VALUE="あいうえ おかき"> データ部に HTML での特殊文字(< や >," 等)がある場合は上記のようにエスケープすることが多い。上記は半角スペースもエスケープ処理を行ったものである。 上記の場合、その後の submit で送信されるデータが、ブラウザによって異なることがわかった。 Internet Explorer: namae=あいうえ おかき Mozilla: namae=あいうえ おかき ブラウザには、&# と ; の間に文字コードを記述することで、その文字を表示してくれるという機能がある(最近のブラウザだけか?古いブラウザもか? 未確認)。 例えば、' と記述すればアポストロフィ、シングルクォーテーション ' が表示される。   もこれと同様である。文字コード 160 の文字を表示しようとしているのである。 じゃあ、160 ってなんだ? という話になる。16 進数にすると A0。こんな文字は……制御コードか? と思うものである。 試してみると。 ブラウザ上では単なる半角スペースに見える文字が表示される。 Mozilla だけでなく IE でも同様で、 と記述されている部分は、半角スペース 0x20 ではなく、半角スペースに似ている物 0xA0 を表示している。 見栄えさえ良ければそれでいい場合は特に問題はないが、ブラウザに表示されている文字をコピー&ペーストでテキストエディタに貼り付ける場合には注意が必要である。見た目は半角スペースなのに、実は違う文字コードなため、半角スペースの検索をしても見つからない、となってしまうのである。 GetPrivateProfileString の文字数制限 Win32API のひとつ、GetPrivateProfileString を使った際のことである。 この API は、設定ファイル(INI ファイル)からデータを取り出すことが出来るもので、レジストリを使わないアプリケーションではよく使われている。 まずは、ヘルプファイルの記述を見てみよう。 この関数は、 16 ビットの Windows ベースのアプリケーションとの互換性のために提供されています。 旧 Windows(3.1 まで)は 16 ビット Windows であったので、int 型は 32 ビット変数ではなく 16 ビット変数であった。この制限のため、GetPrivateProfileString 関数の内部には、16 ビット処理、unsigned short の処理が存在する。 .ini ファイルの内部に、key=nagai_data_wo_kaite... といった長〜〜〜いデータを用意して、読み込ませてみた。 データ部を 10 万バイトとした時、これは unsigned int の最大値である 65535 をオーバーしたため、34464 バイトという長さを取得した。データ部を 65536 バイトとした時、同じく 65535 をオーバーしたため、0 バイトという長さを取得した。 ピンと来た方も多いだろう。 上位ビット(17 ビット目以上)が切り捨てられたのである。 個人的には INI ファイルは非常に使い勝手が良く、出来ればこれからも使い続けていきたいと思っている。上記のサイズ制限には注意したいところだ。 データ部の文字列には実際にはどんなバイトデータが入っていても良く、デリミタ、区切り文字である改行コード(0x0D, 0x0A)と NULL である 0x00 以外はなんでも良い。もしもデータに改行や 0x00 を含めたい場合は、文字としては存在しない文字コード(0x01〜0x07 や 0x0E〜0x1F 等)に置換すると良いだろう。 チェックボックス on HTML HTML でのチェックボックスについて、小さな一つを。 Perl や Java、ASP といったスクリプト言語を使いこなすようになると、チェックボックスの存在を知るだろう。 <INPUT TYPE="checkbox" NAME="check1"> こんなように書く。 Web サービス側にスクリプトファイルを置き、Internet Explorer でアクセスすると、ブラウザにはチェックボックスが表示される。チェックを付けて submit を行うと、Web サービス側にはチェックが付与されているという意味を持つ check1=on というデータが送信される。 ところが。 ここには、大前提がひとつ挙げられている。 クライアントが Internet Explorer である、ということだ。つまり、「IE であれば =on というデータが飛んでいく」ということ、「IE 以外であれば =on で飛んでいくとは限らない」ということである。 例えば筆者が持つ au の携帯電話 EZWeb であれば、Web サービス側には「check1=0」が飛んでいった。 もしも処理をするスクリプトが「データが "on" であれば」という比較をしていた場合、これはバグである。 では、どうしたら良いか、ということになる。 単純である。 <INPUT TYPE="checkbox" NAME="check1" VALUE="on"> 上記のように、VALUE 句を追加すれば良いのである。ここは on でなくても構わない。1 であったり check という文字列であったりしても良いだろう。 一般的には、「ネットワーク上に流れるデータ量を小さくする」や「処理する際の速度を上げる」という理由から、短い数字文字列が好まれるだろう。既に IE 向けに作られている場合は If Check1="on" Then のような条件式を多く持っている可能性があるので、無難に VALUE="on" を追加すると良いだろう。 VB でのマルチスレッド処理 Visual Basic(試したのは 5.0 と 6.0)でマルチスレッド処理をしたいと思うことがある。しかし、VB では直接 CreateThread をコールしてはならない。(詳細は省略するが、プログラムが落っこちる) では、「直接コールしてはならない」のならば、「VC, VC++ で作った DLL 内でコールしよう!」と思うとどうであろうか? これは OK である。多くの部品(DLL, OCX など)で、内部で子スレッドを生成し、マルチスレッド処理を行わせている。 ここで気を付けたいのは、VB コードのフォーム生成(例:Set frm = New Form1)である。これを、子スレッド側で行ってはならない。VB ではすべてのフォームを同一スレッドの生成物として管理している。子スレッドから VB 内のコードにジャンプした場合、そこでフォームを生成してはならない。(フォームだけでなくすべてのフォーム用コントロールも) さて。そもそも、「どうやって、VC DLL 側から VB の関数 Function を呼び出すんだ?」という話もあるかもしれない。 関数コール、関数ジャンプ、というのは、「関数アドレスへのジャンプ」という意味である。よって、関数のアドレスさえわかっていれば、コールは可能である。あらかじめ VB 側から VC 側へ AddressOf を利用して関数アドレスを通知しておき、VC 側からそのアドレスへジャンプさせるのである(VB 6 から使用可能)。 マルチスレッドは、性能、処理速度を上げることが出来る非常に有意義なものだ。プログラマーは、排他処理や終了時のクローズ処理などに気を付けながらコーディングしたいものである。Public Declare Function NotifyAddress Lib "Sample.DLL" ( _ ByVal lFuncAddress As Long) _ As Long '---------- Function Func01( _ ByVal hWnd As Long, _ ByVal uiMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) _ As Long ... End Function ... lRet = NotifyAddress(AddressOf Func01) window.close で IE7 が落ちる Web コンテンツを作成する際に、クライアント側でお手軽に使用できるスクリプトとして JavaScript がある。ここでは他の子ウィンドウを開いて親ウィンドウと連携することを考えていただこう。 親ウィンドウにてリンクをアンカータグ <A HREF=xxx TARGET=_blank> として作成し、子ウィンドウを表示する。 子ウィンドウでは、ボタンの押下によって JavaScript が動きだし、window.close で子ウィンドウ自身を閉じる。 Internet Explorer 7 では、これで強制終了となる。(2007 年秋現在) なお、親ウィンドウから開く時に TARGET=_blank を使うのではなく、同じく JavaScript の window.open を使用すれば window.close 時に IE が死ぬことはない。 IE7 からの新機能、セキュリティ対策の処理にバグがあるのだろう、と筆者は思っている。 操作マスタ Windows サーバーに Active Directory をインストールすると、そのマシンはドメインコントローラーになる。 ここでは、5種類ある「操作マスタ」について簡単に触れてみる。 なお、以下の内容は Windows Server 2003 のヘルプドキュメントから取り出したものである。 1.スキーママスタ スキーマの更新に必要。 フォレスト全体で1つ。 2.ドメイン名前付けマスタ フォレスト内でのドメインの追加と削除に必要。 フォレスト全体で1つ。 3.相対 ID(RID)マスタ ドメインコントローラーに RID を付与するために必要。 フォレスト内の各ドメインに1つ。 4.PDC エミュレータマスタ 旧 Windows もしくは NT 4.0 BDC が存在する場合に必要。 既定ではドメインのタイムサーバーとなる。 フォレスト内の各ドメインに1つ。 5.インフラストラクチャマスタ 他ドメインコントローラーにドメインリソースを複製するために必要。 ドメインコントローラーが複数ある場合、グローバルカタログと同居してはならない。 フォレスト内の各ドメインに1つ。 ドメインコントローラーはフォールトトレランスのために複数台用意することが多い。Windows Server 2003 の「Active Directory サイトとサービス」コンソールを表示すると、どのマシンにグローバルカタログを持たせるかをチェックボックスで設定することが出来る。もしも DC 1台→2台にした時点であるなら、「5.インフラストラクチャマスタ」に注意しよう。ヘルプドキュメントに記述がある通り、DC 複数台の場合はグローバルカタログと同居してはならないのである。追加した DC に、操作マスタを移してしまおう。 環境変数(主に PATH) Windows において「環境変数」とは、「シンボリック名」と呼ばれるキーワードとそのキーが持つ「値」の組み合わせのことである。いずれも文字列で、シンボリック名には使用できない文字がある(通常は半角アルファベットを用いる)。シンボリック名は、大文字と小文字を区別しない。 set KEY=DATA コマンドプロンプトで設定をする場合、上記のようになる。 使用する場合は、% と % で囲んで記述する。 例:さて。 環境変数は、大きく3つに分けられる。 1.システム 2.ユーザー 3.アプリケーション 「システム」とは、Windows が起動してすぐに読み込まれる、全ユーザー共通の情報である。「ユーザー」の情報はログオンするユーザーごとに異なり、ログオン前には使用されない。 Windows にログオンすると、「システム」情報と「ユーザー」情報を合わせた物が生成される。「システム」と「ユーザー」で同じシンボリック名が存在する場合、PATH や LIB といった環境変数は接続(例: シ:C:\abc ユ:D:\def →C:\abc;D:\def)となり、その他は「ユーザー」の情報での上書き(例: シ:C:\abc ユ:D:\def →D:\def)となる。その区別は Windows が行う。 「アプリケーション」、いわゆる実行ファイルは、実行されると「システム」+「ユーザー」のコピーを持つ。起動直後は Windows にログオンした時と同じ情報を持っている。コピーであり、アプリケーションは環境変数を自由に変更することが出来る。もちろん、コピーに対して変更をかけるので、システムや他のアプリケーションには影響を及ぼさない。 Windows サービスプログラムの場合、ローカルシステム(SYSTEM)で実行ファイルを実行することが出来る。この場合、「ユーザー」の情報が存在せず、「システム」情報のコピーを使用する。 また、「システム」や「ユーザー」の情報を変更した場合、Windows は「システム」+「ユーザー」情報を作り直す。その後起動されるアプリケーションには、この作り直した「システム」+「ユーザー」情報のコピーが渡される。起動中のアプリケーションの「システム」+「ユーザー」情報は、何も変わらない。更新された環境変数を使用したい場合、アプリケーションの再起動が必要となる。 次に、環境変数の登録/更新について見てみよう。 環境変数は、コマンドプロンプトでの SET コマンド、コントロールパネル「システム」の「詳細設定」等で表示することが出来る。 Windows 9x 以前の場合、Windows 起動時に AUTOEXEC.BAT が実行されるので、ここに SET KEY=DATA を記述しておく。また、Windows 起動時にはいつでも MS-DOS プロンプトで SET コマンドが使用でき、即時に反映される。Windows 9x 以前には、「システム」と「ユーザー」という大きな区別が無かったのである。 Windows 2000 や XP であれば、コントロールパネル「システム」→「詳細設定」に「環境変数」というボタンがあり、この中で環境変数を編集することが出来る。システム用とユーザー用と、2段になっている。 最後に、編集、特にパス(フォルダ名)についての注意点を述べてみる。 (1)複数記述 PATH や LIB といったフォルダ名を記述する環境変数では、複数のパスを ; で区切って記述することが出来る。a;b と b;a ではどちらも2つのパスを持つが、先に記述されているほうが優先順位が高い。 (2)\ の数 こちら(93. インストーラーに求められる一般的事項について)でも触れている通り、\ の数に注意しなければならない。プログラムで記述する場合は、複数 \ を1個に置換するような仕組みがあっても良いだろう。 (3)絶対パスと相対パス パスの記述には、絶対パス(例:C:\Windows)と相対パス(例:..\)のいずれも使用できる。 (4)終わりの \ Windows 標準のデフォルト値は、終わりに \ が付いていない。付けても構わない。 一見、なんの変哲もない注意点である。 ところが。 いざアプリケーション開発者の環境変数チェック/更新の処理を見てみると、これらはあまり考慮されていないことが多い。 (A)順番 PATH に C:\Projects\Bin を追加したは良いが、それを最後に記述したため、C:\Projects\Bin\xxx.exe ではなく C:\Windows\xxx.exe が実行されてしまう、「他の場所にある同一ファイル名の実行」という不具合が発生する。逆もまたしかり。 もちろん、Windows が標準で持っていないファイル名であれば問題はないが、オープンソースのツールや気付かない物(特に DLL ファイル)が複数のフォルダに存在することは、あり得る話である。 (B)重複登録 インストール,アンインストール,インストール、と繰り返した場合に、同じフォルダ名が繰り返し登録されてしまう。 同じ物が複数登録されていても、Windows の動作には支障がない。ただし、PATH には「文字数制限」がある。最近の Windows では 2048 文字、Windows NT 以降は 1023 文字、旧 Windows ではわずか 127 文字である(詳細は省略)。これを超えた分は切り捨てられてしまう。 よって、いじわる試験を行うと、簡単にこの文字数を超えてしまうのである。 前者「順番」のチェックは、そのマシンによって優先順位が異なるので追求が難しい。企業や店舗での業務用アプリケーションのような(一般的ではない)ソフトを使用する場合は、それぞれの特徴を把握する必要があるだろう。 後者「重複登録」は、インストール時、およびアンインストール時に登録/抹消の処理を行うことで対応できる。具体的には、以下のような手順が必要となるだろう。 こうして修正した文字列で、コピーされた環境変数のパス群が使用可能となるのである。例: PATH に対して「N:\」を処理する場合。 (0)取得 Win32API の ・GetEnvironmentVariable で PATH を取得する。ExpandEnvironmentStrings はダメ。 (1)存在チェック すべて大文字または小文字に変換 or 大文字/小文字を区別しない。 パスの終わり \ の有無を統一する。 A.完全に一致 B.;N:\ と終わりが一致 C.N:\; と先頭が一致 D.;N:\; が途中に存在 いずれかの場合、存在する。 なお、N:\ のみで検索すると N:\abc や NN:\ (←記述ミス) も引っかかってしまう。 (2)追加 A.N:\; を先頭に追加 B.;N:\ を終わりに追加 C.;N:\ を途中に追加 どれを選択しても構わない。 (3)新規作成 PATH でない場合は新規作成も考えられる。 N:\ で新規作成する。 (4)削除 置換。 A.;N:\; → ; B.先頭の N:\; → (先頭を削除) C.終わりの ;N:\ → (終わりを削除) D.N:\(完全一致) → (全削除) (5)保存 Win32API の ・SetEnvironmentVariable で PATH を保存する。 補足。 環境変数は、レジストリに記述されている。 レジストリを直接編集しても、Windows がそれを取得するのは、Windows 起動時(ログオン時)と「変更があった」イベントを受け取った時だけなので、すぐには反映されない。 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment] もしくは [HKEY_USERS\(各ユーザーのキー)\Environment] レジストリを直接更新する場合、文字列の取得と保存以外は(1)〜(4)とまったく同じとなるだろう。 IFRAME タグ HTML 文書を作っていると、文書の途中に別枠を用意して、そこに別ファイルを表示させたいと思うことがある。この場合、IFRAME タグを使うと実現できる。 <IFRAME SRC="iframe.html" WIDTH=200 HEIGHT=40 BORDER=2 ALIGN=CENTER> IFRAME タグ部分 </IFRAME> 便利なものである。 ところが。(以下、個人的意見) とあるページでは、IFRAME を濃い写真の広告ページのために使っており、しかもドキュメントのいいところに配置したため、ドキュメントを読みたいのにその濃ゆい写真が目に入ってとてつもなく気になるということがあった。 「そんなキモい顔写真、見たくないんじゃ!(`□´)」と叫ぶわけである。 それで、自マシンの Internet Explorer には、自分でカスタマイズしたスタイルシートファイル(.css)を読み込ませ、IFRAME タグを非表示にした。 例: IFRAME { display: none; }これで、すべての IFRAME タグは非表示となり、顔写真広告を見ることなくドキュメントを読めるようになったのである。 ちなみに、CSS ファイル(カスケードスタイルシート)の設定は、インターネットのプロパティで「全般」タブの「ユーザー補助」ボタンを押下すると行える(Internet Explorer 5, 6 で確認済み)。 余談。 うちでは、CSS ファイルに以下も記述してある。 TH, TD, INPUT, SELECT { font-family: "MS ゴシック"; } 当然、すべての Web ページが対象となるわけで、IFRAME タグに本文が入っているようなページはさっぱりわからなくなる。非表示は一時的に行ったほうが良いだろう。 わたしは常に非表示にしているのだが、これが大きく影響するページが1つあったので紹介しておこう。 Windows ライセンス認証 原因がわかった時には、がっくしだった。 Windows XP 以降では、「Windows のライセンス認証」という機能がある。これはインターネットや電話でアクティベーションを行うものである。 で、このウィンドウが…… IFRAME タグ表示だったのだ。 教訓。 ライセンス認証時には、IFRAME タグを表示させる。 InternetExplorer コンポーネント Internet Explorer 4.0 以降をインストールすると Windows に存在する、InternetExplorer コンポーネント。shdocvw.dll というファイルで IE ウィンドウを制御できるようになる。今回はこれについて。 WindowsXP SP2,IE 6,VB 5.0 と 6.0 で動作確認中。 VB のメニュー「参照設定」で、上記ファイル「Microsoft Internet Controls」にチェックを付けると、プログラムから IE 画面を生成し、それをいろいろ操作することが出来るようになる。 ある人の目的を、だいたい以下のようなものとする。 (A)親ウィンドウの指定部分に IE を表示したい (B)タイトルバー,アドレスバー,ステータスバーは不要 (C)特殊な URL アクセスは内部で横取りする(ジャンプ処理無し) (D)GET/POST 送信データの更新 (E)GET/POST 受信データの更新 難しいかな〜 と思いながらも、とりあえず動作確認開始。 ------------------------------ (1)プロジェクト生成 VB で IE コンポーネントを有効にする。親ウィンドウに IE ウィンドウを直接表示するのではなく、子ウィンドウを1個用意し、そこに IE フルスクリーンとした(IE.FullScreen = True を使用)。 (2)コーディング開始 Private IE As InternetExplorer ではなく Private WithEvents IE As InternetExplorer とする。 Set IE = New InternetExplorer でインスタンス生成。 内部でのページジャンプ(GET/POST)は、IE.Navigate2 [URL] を使う。 (3)イベントコーディング BeforeNavigate2, NavigateComplete2, NavigateError イベント処理を記述。 BeforeNavigate2 の URL に特殊文字列があれば、Cancel に True をセットし、何もしない、もしくは別ページへジャンプ。 NavigateComplete2 で URL を(表示用に)別コールバック関数へ通知。 NavigateError で親ウィンドウに通知。 ------------------------------ いろいろ思った通りに動かなかったので、全部書いてみる。 ------------------------------ (1−1)IE コンポーネントは別プロセス New InternetExplorer で、iexplore.exe が起動した。別プロセスである。 よって、イベント横取り手法であるサブクラス化は不可能である。 (1−2)子ウィンドウ(IE ウィンドウ)位置 IE.Left と IE.Top で位置指定できるかと思ったが、値セットをしても、次の瞬間には異なる値になっていることがあった。 Win32API MoveWindow を使って IE.HWND を操作。 ちなみに、親ウィンドウの指定は Win32API SetParent を使っている。 (1−3)アクティブウィンドウ 他のウィンドウが子ウィンドウおよび IE ウィンドウを隠した場合、たとえ親子関係になっていたとしても、IE ウィンドウクリックでは子ウィンドウはアクティブにならない。 (2−1)Navigate メソッド Navigate2 メソッドではなく Navigate というメソッドもある(引数は同じ)。ちょっと違う。 (2−2)POST PostData(Navigate メソッドの引数 PostData As Variant)は、後述の通り、Byte 型配列(VarType() = vbByte + vbArray)でなければならない。 "a=1" のような String 型文字列ではなく、 Dim b() As Byte ReDim b(4) b(0) = Asc("a") b(1) = Asc("=") b(2) = Asc("1") b(3) = 0 'NULL 止め のように変換し、この b を引数としなければならない。 (2−3)IE ウィンドウクローズ IE.Quit では IE ウィンドウがクローズされない。 Win32API PostMessage/SendMessage で WM_CLOSE や WM_DESTROY を飛ばしてクローズさせる。 (3−1)URL や PostData の更新 BeforeNavigate2 イベントの引数が xxx As Variant なので、書き換え出来るかなと思ったが、出来なかった。 いったん Cancel=True でキャンセルし、書き換えようとした値で再度 Navigate2 をコールすることで対応した。 GET の場合、PostData には Empty 値が入っている。しかし、IsEmpty(PostData) としても、GET, POST に関わらず False となる。正しく判定されない。 VarType(URL) は vbString で、VarType(PostData) は、0 である。本来は VarType(PostData) = vbByte + vbArray なのだが、これも正しく取得できない。 これらより、以下のように手を加える。 Dim vPostData As Variant vPostData = PostData コピーを作ることにより、IsEmpty(vPostData) や VarType(vPostData) は正しく値を返してくれる。 余談だが、ASP VBScript で Request.BinaryRead を使用して取得したデータも、上記のように不確定なバイト型配列である。VarType(vData) は vbByte + vbArray となっているが…… わたしは VB 6.0 で ActiveX DLL を生成し、以下のようにコピーを生成した。 (4−1)IE ウィンドウクローズPrivate m_bData() As Byte Public Sub MakeCopyPostData(ByRef vData) Dim x As Long Dim lUBound As Long Dim vCopy As Variant vCopy = vData lUBound = UBound(vCopy) ReDim m_bData(lUBound) For x = 0 To lUBound m_bData(x) = vCopy(x) Next 'Win32API CopyMemory を使えばループ使わず行ける End Sub IE ウィンドウが別プロセスであるため、単純には IE ウィンドウ単体の終了(例:Alt+F4 キー押下)を防ぐことが出来ない。 前述の通りサブクラス化が不可能なため、それでもイベントを監視する場合、別 DLL を用意し、その中でフック(Win32API SetWindowsHookEx(WH_CALLWNDPROC, ……))する必要がある。 (4−2)IE ウィンドウクローズ 内部で IE ウィンドウをクローズする際には、SetParent を使って、親ウィンドウを元のウィンドウ(通常はデスクトップ)か別のプロセス等に移してから行う。これをしないと、IE インスタンスが綺麗にクローズされず、自プロセスがずっと CPU 使用 90 %以上となってしまう。 ------------------------------ 以上のようなお約束を取り入れながら、元々の目的を達成するのであった……。 Windows サービスパックの展開 (ここでは AT 互換機、DOS/V 機等と呼ばれるマシンについて説明しているため、フォルダ名が I386 となっている。NEC PC-98 シリーズであれば NEC98 に読み替えていただきたい) Windows には、NT の頃から「サービスパック」というインストーラーが存在する。これは、バグ対応 & パッチあて & 新機能搭載のために、マイクロソフトから提供されるものである。 2006 年春の時点で、Windows NT 4.0 では SP6a、2000 では SP4、XP では SP2、2003 では SP1 がある。(これらより新しいバージョンは、わたしが未チェック) サービスパックは、たいてい1圧縮ファイル(EXE ファイル)で提供されている。それを実行すると、まず展開(解凍)が行われる。自動的に一時フォルダを生成してそこにファイルを展開し、展開された update フォルダにある update.exe が実行される。 通常、1 OS に対して同一のサービスパックインストールは1回きりなのでこの手順でまったく問題はないが、なんらかの理由で何度もサービスパックをあてる必要があることがある。例えば、 ・NT 4.0 でネットワークサービスをインストールした ・OS インストールの練習 ・システムファイル破損 ・リムーバブルメディアへのコピー これらのような時にサービスパックの再インストールを行うことが考えられるが、圧縮ファイルの展開時間がかなりの操作停止時間となってしまう。また、特殊な例として NT 4.0 で IE 5.0 以上をインストールした後に直接サービスパックのインストーラーを起動しても、「セキュリティの問題でうんちゃらかんちゃら」のエラーメッセージが表示され、インストールできない(update フォルダ update.inf ファイルの1行 CheckSecurity.System32.files セクションの SCHANNEL.DLL をセミコロン(;)でコメントアウトしなければならない)。 サービスパックファイル名: Windows NT 4.0 → sp6full_i386.exe Windows 2000 → W2KSP4_ja.exe Windows XP → XPSP2.EXE 等 展開専用コマンド: <EXE FileName> -X EXE ファイルに、引数として X スイッチを付ける。これは、インストール作業を行わずに、ファイルの展開だけを行わせるためのものである。 さて、これをふまえた上で、先に挙げた例の1番目「NT 4.0 でネットワークサービスをインストールした」について、2点触れておこう。 (1)NT 4.0 の CD を求められる コントロールパネルの「ネットワーク」で、DNS サーバーや DHCP サーバーといったネットワークサービスをインストールすることが出来る。通常、サービスのインストール時には、I386 フォルダの場所を問われる。CD を挿入し、サービスのインストールを行い、Windows を再起動して完了である。 ところが、CD からファイルを取り出してインストールするということは、すなわち「CD 内の(旧)バージョンのファイルをコピーする」ことである。既に新しいバージョンとなっている状態で SP なしや SP1 のファイルが上書きされてしまうのである。よって、それらを新しいファイルにするため、サービスパックのインストールが必要となる。 (2)SNMP の DLL ファイル NT 4.0 の SP3〜SP6 のファイルのうち、SNMP に関するファイルの1つ(ど忘れ…… SNMPAPI.DLL かな……?)にバグがある(と思っている)。UNIX 系マシンの SNMP サービスが NT マシンの情報を取得できない、という不具合が発生したことがある。この時、NT 4.0 SP1 の同ファイルを、新しいファイルに上書きして動作することを確認した。 では、NT 4.0 に限らず、(1)のように「新しい SP あてたけど、作業の中で CD 挿入(I386 フォルダの場所指定)を言われてしまった」が発生することは、今後も有り得るだろう。その場合、次の方法で対応が可能である。 まず、CD に収められている「I386 フォルダ」を、ごっそりハードディスクにコピーする。 次に、サービスパックのファイル解凍を行う。(-X スイッチで) 最後に、update フォルダにある update.exe に、-s:<I386 のあるフォルダ> というスイッチを付けて実行する。 例: D:\Windows\I386 に CD の I386 フォルダの中身をコピー update -s:D:\Windows (←"I386" は不要) こうすると、ごっそりコピーした I386 フォルダの旧内容が、サービスパックの新内容で上書きされるのである。 Windows が「I386 フォルダの場所を指定しろ」と言ってきたら、この新しくなった I386 フォルダを指定すれば良い。これで、旧バージョンファイルが動作中のシステムに投入されることはない。(Windows Update は別途必要かもしれない) CD/DVD ドライブが搭載されていないマシンや、マシン管理者が楽したい場合(笑)に、有効だろう。 なお、update.exe に /? を付けて実行すると、サービスパックインストーラーで有効なスイッチ一覧が表示される。 (勘違いしていたので、ざっくり削除) 以下、勘違い内容。 int FuncSample1(String *pstrData) { pstrData=pstrData->Substring(1); return 0; }これ↑で呼び出し元を書き換えるつもりだったが、何も変わっていないことに気付いた(汗) int FuncSample1(String **ppstrData) { (*ppstrData)=(*ppstrData)->Substring(1); return 0; }(& を使わずに)やるなら、↑こうだね(−−; 指摘じゃー! --> コンパイル後の出力ウィンドウ消去 Visual Studio .NET でのお話。 Visual Studio 6.0 までと .NET とでキーが変わってしまった。何が変わってしまったかというと、「出力」ウィンドウの消去方法。 Visual C++ 6.0 までは、コンパイル,ビルドや「ファイルから検索」のグレップ作業で、「アウトプット (標準では Alt+F2)」という1フレームを使ってその結果を表示する。 わたしは(標準の)F7 キーでビルド、Ctrl+F7 キーで1ファイルのコンパイルを行い、結果を確認したらすぐ ESC キーでアウトプットフレームを消す、ということをやっていた。ところが、Visual Studio. NET では、ESC キーを押下しても(「アウトプット」フレームにあたる)「出力」フレームが消えない。 標準のキー割り当ては Shift+ESC。 しかも、 「テキストウィンドウで ESC を押したら消える」だったのに、 「出力ウィンドウで Shift+ESC を押したら消える」になってしまって、これが実に使いづらい(*へ*; コンパイルやったー、前までのクセで ESC キー押したー あ、フォーカスがテキストウィンドウに移ったー ぎゃー、Shift+ESC 押しても出力ウィンドウ消えーん! なんで ESC キーで消さなくしたんだろ…… キーマップを変更してもダメだし…… SELECT 文で異なるフィールドを取得する SQL でのお話。試したのは Oracle 8.1.7 と PostgreSQL 8.0.4。 日時フィールドが2つあって、片方はレコード作成時に、片方はそのレコードへの更新が発生した時に書き込まれる、後のほうは1回目の更新が発生するまでは NULL である、というテーブルがあったとする。 CREATE TABLE table_name ( id int4 PRIMARY KEY, status int4 DEFAULT 0, date_create char(14) NOT NULL, date_update char(14) );このテーブルについて見てみよう。なお、 ・int4 の部分は NUMBER(10) や int など、SQL サービスごとに置換が必要。 ・id=0 の1レコードのみが保存されているものとする。 INSERT INTO table_name (id, date_create) VALUES ((SELECT MAX(id)+1 FROM table_name), TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS')); INSERT INTO table_name (id, date_create) VALUES ((SELECT MAX(id)+1 FROM table_name), TO_CHAR(NOW(), 'YYYYMMDDHH24MISS')); Oracle の場合は SYSDATE、 PostgreSQL の場合は NOW()、 SQL Server の場合は GETDATE() が現在日時を表す。 余談 for SQL Server: UPDATE table_name SET date_update= REPLACE( REPLACE( REPLACE( CONVERT(char, GetDate(), 20), '-', '' ), ':', '' ), ' ', '' ); なんか、もうちょっと気の利いた書き方はないもんか…… UPDATE table_name SET status=1, date_update=TO_CHAR(NOW(), 'YYYYMMDDHH24MISS') WHERE id=1; | id | date_create | date_update | |--------------------------------------| | 1 | 20060123123456 | 20060701000000 | | 2 | 20061231235959 | (null) | |--------------------------------------|id=1 と id=2 のレコードがあり、id=1 のレコードは date_update が NULL ではない。 作成されたばかりであれば date_create を、更新されたことがあるなら date_update を取得する、という場合は、以下の SELECT 文を用いる。 SELECT id, CASE WHEN date_update IS NULL THEN date_create ELSE date_update END FROM table_name;なお、NULL でないほうが欲しいだけなら、以下の SELECT 文でも良いだろう。 [Oracle] SELECT id, NVL(date_update, date_create) FROM table_name; (列1が NULL なら列2、NULL でないなら列1) SELECT id, NVL2(date_update, date_update, date_create) FROM table_name; (列1が NULL なら列3、NULL でないなら列2) [PostgreSQL, SQL Server] SELECT id, COALESCE(date_update, date_create) FROM table_name; (最初に見つかった NULL でないフィールド)COALESCE(こぅあれす)、を初めて使ったので書いてみた。そんだけ。 C++.NET で Thread クラスを使う いわゆる、マルチスレッドプログラミングである。 VC 6.0 までであれば、いろいろな方法で新規スレッドを生成できる。本題に入る前に、ここで Win32API の CreateThread を使った方法を紹介する。 こんなような感じ。//グローバル変数 typedef struct tag_DataInfo { int i; char c[4]; } DataInfo; DataInfo gstData ={0, }; DWORD gdwThreadI D=0; DWORD WINAPI ThreadProc(DWORD *pdwArg) { BOOL fFinished =FALSE; DataInfo *pstData=(DataInfo *)pdwArg; while(1) { //スレッドの外で作ったイベントハンドルでウェイト DWORD dwRet=WaitForSingleObject(hEvent, 1000); switch(dwRet) { case WAIT_OBJECT_0: //SetEvent された break; case WAIT_TIMEOUT: //イベントセットされなかった continue; break; default: //想定外のイベント //終了へ fFinished=TRUE; break; } if(fFinished!=FALSE) break; //実処理 } ExitThread(0); return 0; } { ... HANDLE hThread; hThread= CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (DWORD *)&gstData, //バッファの有効期間に注意 0, &gdwThreadID); if(hThread==NULL) return ERROR_EXISTED; CloseHandle(hThread); //監視の必要がなければすぐクローズ return ERROR_NONE; } CreateThread の第4引数には、スレッドの開始時、および必要な時までずっと有効であるバッファのアドレスを渡すこと。ローカル変数のアドレスを渡す時は Access Violation に注意する。 CreateThread+ThreadProc の組み合わせではなく、_beginthread+thread_func の組み合わせを使うと、もう少し簡易になる(ここでは記述しない)。 CreateThread(_beginthread や _beginthread)のコール回数だけ、ThreadProc(thread_func)を使ったスレッドが開始する。 で、いま、Visual Studio .NET 2003 の C++.NET で、managed のスレッド開始コードを作ってる。Thread というクラスで作れる。 ところが、MSDN Library や世の中に転がっている記述とは、ちょっと違う。VB.NET や C# と、C++.NET ではコードが違うのだ。これについて、述べてみよう。 いつまで存在するかわからんけど、参考:マイクロソフトのページ のクラスインスタンスを生成するところを抜粋した。 太字にしたところを見ていただこう。[VB] Public Class ThreadWork Public Shared Sub DoWork() Dim i As Integer : Thread.Sleep(100) Next i End Sub End Class '別クラスのあるメソッド内にて Dim myThreadDelegate As New ThreadStart(AddressOf ThreadWork.DoWork) Dim myThread As New Thread(myThreadDelegate) [C#] public class ThreadWork { public static void DoWork() { : Thread.Sleep(100); } } //別クラスのあるメソッド内にて Thread myThread = new Thread(new ThreadStart(ThreadWork.DoWork)); [C++] public __gc class ThreadWork { static void DoWork() { : Thread::Sleep(100); } } //別クラスのあるメソッド内にて Thread *myThread = new Thread(new ThreadStart(0, &ThreadWork::DoWork)); 違いは1点。ThreadStart のコンストラクタに渡す引数が、1つと2つ、で異なるのだ。 VB と C# の第1引数と C++ の第2引数は同じで、「別クラスのパブリックな静的メソッド」である(static でなければならない)。よって、同一メソッドにてスレッドをたくさん生成する場合、異なる情報を渡してあげたいものだ。 VB や C# のサンプルは、ググったらたくさん見つかった。ところが C++ のサンプルは、ほとんど見つからない。実際、MSDN Library を検索しても、「ThreadStart の引数が2つ必要である」ことは、ぱっと見たかぎり、明記されていない。(不満) コーディングをしていると、"(" を入力した段階で (System::Object __gc *, System::IntPtr) と表示され、引数が2つ必要なことがわかる。メソッドだけ(1つだけ)を記述してコンパイルしても、コンパイルエラーとなる。第2引数の System::IntPtr が、メソッドのアドレスである。先ほどの話、アドレスを渡さなければならないので static である必要がある、というのは頷けるところだ。 では、最大の難関である、第1引数、Object * について考えてみよう。 ThreadStart は、デリゲート(__delegate)である(デリゲートはコールバック関数のインターフェースだと思ってる)。 わたしは Socket クラスの BeginXxx(BeginAccept や BeginConnect 等)を使った時に初めてデリゲートを知った。それを例として…… Socket::BeginXxx メソッドでは、非同期の処理を行うために別スレッドが自動生成される。そのスレッドから Invoke されるのが、デリゲート AsyncCallback である。BeginXxx メソッドには、引数として任意のクラスインスタンスアドレスを1つ渡すことが出来、別スレッドではそのクラスインスタンスアドレスを(IAsyncResult::AsyncState を使って)取り出すことが出来る。グローバル変数を定義することなく別スレッドに様々な情報を教えてあげることが出来るのである。 ちょっと Socket の例も出しておこう。 で、やっと本題だ。[C++] public __gc class SockInfo { public: SockInfo(); ~SockInfo(); int iState; } void SocketControl::AcceptCallback( IAsyncResult *ar) { SockInfo *pSockInfo =static_cast Thread クラスのコンストラクタと Start を使おうと思ったんだけど…… ThreadStart の第1引数は、どうやって使うんじゃ?(*へ*; 生成されたスレッドのほうで Thread::get_CurrentThread で Thread クラスインスタンスのアドレスをもらい、その中を見るが…… ThreadStart の第1引数で渡したアドレス(pNanika)は、どこにも入っていない。[C++] public __gc class Nanika { public: Nanika(); ~Nanika(); int iData; } public __gc class ThreadWork { static void DoWork() { Thread *pThread=Thread::get_CurrentThread(); : Thread::Sleep(100); } } //別クラスのあるメソッド内にて Nanika *pNanika = new Nanika(); pNanika->iData=1; Thread *myThread = new Thread( new ThreadStart(pNanika, &ThreadWork::DoWork)); 何か違う方法でアドレスをもらうことが出来るのか? 新規スレッド側が終了する時に使われるのか? 答えは、No である。この第1引数は、違う用途に使われるのである。 追記を知るまでは、シングルトンなクラスを作り、そこに静的な ArrayList クラスインスタンスや自前のキュークラスインスタンスを用意し、pNanika をそこにセットすることで情報の受け渡しを行っていた。この場合、大元のクラスインスタンスを複数生成しても、同じリストやキューが使われてしまうことに注意が必要であった。 で、調べてみたところ(ここから 2006/02/16 追記)、以下のような感じになった。 第1引数 0、第2引数 static でないパブリック関数、としてコンパイルしたところ、 「NULL オブジェクトインスタンスを delegate コンストラクタに渡す場合、静的メンバ関数のアドレスも渡す必要があります。」 というエラーとなった。 ……なぬ? これは、「第1引数が NULL の時は、第2引数は static な関数アドレスでなきゃいけない」、そういうことだよな…… ビンゴ。 「第1引数がクラスインスタンスの場合、第2引数にはそのクラス(インスタンス)の関数アドレスを指定できる」。 これで、自クラスインスタンスの(static でない)関数アドレスでスレッドを起こし、処理をすることが出来るようになる。[C++] public __gc class ThreadWork { デリゲート関数というのは、上記が「お約束」のような感じがする。 余談を1つ。 先の Socket クラスで使う AsyncCallback や Timer クラスで使う TimerCallback の関数は、システムが持つスレッドが処理を行う。「同じスレッド」で処理を行う。(Thread::get_CurrentThread())->GetHashCode() を使ってスレッドのハッシュコードを見てみたところ、AsyncCallback のコールバック関数と TimerCallback のコールバック関数で、同じ値を取得した。 IIS で Perl を動かす Windows には、Internet Information Service(IIS)というプログラムがある。これは、Web や FTP といったサービスを提供するためのものである。 Windows NT 4.0 は、サーバー製品であれば標準で IIS 2.0 が付属していた。また、Option Pack というものでは IIS 4.0 をインストールすることが出来た。これらは Web と FTP のサービスを提供することが出来る。 Windows 2000 になって、IIS は 5.0 にバージョンアップした。SMTP や POP3、NNTP といったサービスも提供することが出来るようになった。Windows XP では IIS 5.1、Windows 2003 では IIS 6.0 と、バージョンアップを繰り返している。 IIS では、外部のスクリプト解析プログラムを動作させることが出来る。個人的には、常に Perl CGI を動作させたいと思っている。そこで、その設定方法について見てみよう。 用意したものは、ActiveState の ActivePerl(手元にあるのは 5.6.1 Build 638)。これを、F:\Perl にインストールしたとする。この時、Windows の環境変数 PATH には、F:\Perl\Bin が追加されているだろう。無ければ手動で追加する。 次に、IIS の管理コンソールウィンドウを開く。(細かい説明は省略) Web サイト(「既定の Web サイト」やマシン名アイテム、「Web サイト」と表示されているところ)のプロパティを開き、WWW サービスとして「ホームディレクトリ」の「構成」を編集する。拡張子「.cgi」について、PerlIs.dll を関連付ける。 IIS 5.1 までであれば、これで完了である。 Windows 2003 の IIS 6.0 では、これだけでは .cgi が動作しない。続いて、「Web サービス拡張」の設定を行う必要がある。 画面キャプチャの「Perl ISAPI Extension」が無ければ、適当に「Perl」といった拡張項目を追加すれば良い。ここに、先ほどの PerlIs.dll を登録し、「許可」することで、IIS 6.0 で Perl を動作させることが出来る。 余談。 NTFS アクセス権が IUSR_ この画面キャプチャでは、IUSR_F___ が所属する Users グループに対して、「読み取り」と「実行」の権限しか付与されていないため、Web ブラウザを操作してファイルを生成したり更新したりするようなスクリプトでは、エラーが発生してしまう。Users グループのアクセス権を変更する、新たに IUSR_xxx にアクセス権を付与する、そもそも細かいアクセス権はなくし、「Everyone: フルコントロール」にしてしまう、などの対応策が考えられる。 ハードディスクの使い方 まず、「冗長化」という言葉を挙げてみよう。 冗長、という言葉を調べてみると、「くだくだしく(長すぎたり細かすぎたりして、くどい)長いこと。無駄が多くだらだら長いさま。」(大辞林より)とある。「冗長化」は、意味をそのまま取ると「無駄を作る」という意味である。 ハードディスクの世界では、「フォールトトレランス」のことを指す。新語として「過失許容性」と登録されていた。 「ディスク1つにデータを保存すればいいのに、わざわざ2つ目にも同じデータを保存する」ことで、片方のディスクが故障してしまった場合に、もう片方のデータで運用が可能である。これをミラーリング(RAID 1)と呼んでいる。 「ディスク1つにデータを保存すればいいのに、わざわざ複数(n個とする)に分割して、かつパリティ(誤り訂正符号)をさらにもう1つに、合わせて(n+1)個に保存する」ことで、1つのディスクが故障してしまった場合に残りのn個のデータで運用が可能である。わたしはこれを、分散パリティストライピングと呼んでいる。RAID 5 のことである。 ミラーリング、分散パリティストライピング、ともに、ディスクが複数壊れてしまうと、運用不可能となる。 次は、パーティションの作り方を考えてみよう。 その前に、「パーティション」について簡単に触れておこう。「区切り」である。データを保存する領域をいくつかに分けておくと、便利なことがある。例えば、第1パーティションをシステムドライブとして、第2パーティションを個人データドライブとして使っているとしよう。この時、システムに不具合があり、再構築を行わなければならなくなった場合、個人データを完全に残したまま、システムドライブのみをクリアすることが出来る。パーティションが1つだけだった場合には、別のバックアップ作業を考えなければならない。ただし、パーティションサイズは、いつでもいくらでも変更可能、というわけではない。あらかじめ、システムドライブにどれだけのサイズが必要かを判断して、「区切り」を入れる必要がある。 (1)ディスク1つに1以上のパーティションを作成する (2)ディスク2つ以上にまたがって1つのパーティションを作成する (3)ディスクを1つ余分に使って1つのパーティションを作成する (4)ディスクのパーティションを他ドライブの追加ディレクトリとして扱う 最初の(1)は、先に述べたものである。 次の(2)は、合わせると複数パーティションであるが、1ドライブとして見せたい場合の手法である。「スパン」(上から順番に詰めていく)や「ストライピング」(分散保存)という方法がある。ストライピングは、RAID 0 と呼ばれる(こともある)。 次の(3)は、先に述べた、RAID 5 のことである。データの保存に2以上のパーティションを、パリティの保存に1パーティションを使用するため、ディスクが3つ以上必要となる。 最後の(4)は、作成したパーティションをドライブとして見せるのではなく、他のドライブのサブツリーとして見せるテクニックである。例えば、2つ目のディスクに1パーティションを作り、それを C:\data\ として見せるといったものである。この場合、C:\ の空き容量と C:\data\ の空き容量は違う値になる。 ストライピング、ミラーリング、分散パリティストライピングは、すべてのパーティションが同じサイズでなければならない。 NEC PC-98 シリーズと AT 互換機では、パーティションの考え方に違いがある。AT 互換機では、システムを起動するための「基本パーティション(1ドライブ)」と、複数の論理パーティション(ドライブ)を作ることが出来る「拡張パーティション」という違いがある。追加のハードディスクであれば、システム起動が不要として基本パーティションを作らないことが考えられる。Windows 98, Me 以前では、基本パーティションは1つだけ存在もしくは0でなければならず、基本パーティションと論理パーティションの合計が4つ以下でなければならなかった。これらに対して、PC-98 シリーズでは、基本と拡張という概念はなく、4つ以下か5つ以上必要かを判断するだけで良い。 (わたしが PC-98 マシンに最後に触ったのは、2000 年か…… ←自宅のはちゃんと動く(はず)) 余談。 Microsoft の SQL Server 2000 の場合、データファイルをフォーマットしていないパーティションに保存することも出来る。 タブインデックス 画面のあるプログラミングをしていると、必ず一度は設定しなければならないのが、タブインデックスである。「タブキー押下」でフォーカスが変わることはご存じだろうか。この順番のことである。ウィンドウ(ダイアログ)を表示した時に、テキストボックスやボタンといったコントロールの、「タブキー押下」でのフォーカス移動の順番を決めるものである。最近では、上から下へ、左から右へ、に統一されているウィンドウが多い。 マウスでしかウィンドウを操作できないプログラマーはこれを意識することを知らず、めちゃくちゃなフォーカス移動をするウィンドウを作る。これを指摘されて怒る人も多い…… Visual Studio C++ では、メニューの「レイアウト」→「タブオーダー」を使って、Visual Basic では各コントロールのプロパティ「TabIndex」の値を設定することで、タブキー押下によるフォーカス移動の順番を設定する。 ここでは、HTML 文書でのフォーカス移動について、見てみよう。 Web ページを表示すると、アンカー(リンク)やテキストボックス、ボタンといった「フォーカスがセットされる」箇所が見つかるだろう。これらは、HTML 文書として、アンカータグ(A タグ)やインプットタグ(INPUT タグ)が記述されていることで表示されるものである。タブキーを押下すれば、様々な箇所にフォーカスが移動していくだろう。 HTML の新しい規格(HTML 4.0 以降)では、TABINDEX=xxx という記述を使って、タブキー押下によるフォーカス移動の順番を決めることが出来る。 例えば、 <A HREF="index.html" TABINDEX=3>インデックス</A><BR> <INPUT TYPE=text TABINDEX=2><BR> <INPUT TYPE=button VALUE="設定" TABINDEX=1><BR> のように記述すれば、上から順番であったはずのフォーカス移動順序が、TABINDEX の数字の昇順、「小さなものから順番」に変わる。この例で言うならば、最後のボタンが1番目、テキストボックスが2番目、最後がアンカータグリンクである。 なお、TABINDEX を指定しないリンクは、上から順番である。TABINDEX を指定した箇所よりも優先順位が劣る。 Internet Explorer 6.0 で試したところ、TABINDEX には -1〜32767 が指定可能なようである。32768 以上の値では正しくオーダーセットされなかった。0 をセットすると、「アドレスバー」(URL を入力するところ)よりも先に移動するようになった。-1 をセットすると、フォーカス移動の対象とならなくなった。 これにより、以下のように優先順位付けされていると言える。 1.TABINDEX 指定 0 2.TABINDEX 指定 1, 2, 3, ……, 32766, 32767 3.TABINDEX 指定なし or TABINDEX 範囲外指定 (上から順番) 4.TABINDEX 指定 -1 (フォーカス移動しない) レジストリデータの読み取り Win32API RegQueryValueEx を使うと、レジストリからデータを読み取ることが出来る。今回はこれについて。 Get は以下の手順。 1.RegOpenKeyEx 2.RegQueryValueEx 3.RegCloseKey Set は以下の手順。 1.RegCreateKeyEx or RegOpenKeyEx 2.RegSetValueEx 3.RegCloseKey それぞれ、同じレジストリキーの値を連続で何かする場合は、2の関数コールを3(クローズ)の前に繰り返せば良い。 Set についてはうまくいくのに、アクセス権も付いているのに、Get がうまくいかない、ということがある。Visual C++ 6.0 や Visual Basic .NET で発生したことがある。 原因は、RegOpenKeyEx の第4引数を KEY_ALL_ACCESS にしていたこと。KEY_READ とした場合、正常に取れる。 KEY_ALL_ACCESS= ((STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) & (~SYNCHRONIZE)) =(0x000F003F) KEY_READ= ((STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & (~SYNCHRONIZE)) =(0x00020019)KEY_ALL_ACCESS は KEY_READ のオンビットを全部持っているのに、RegQueryValueEx でダメ(GetLastError()==ERROR_ACCESS_DENIED)と言われる。謎な現象である。 ぱっと思いつくのは、「読めるけど書けないアクセス権になってるんじゃないの?」というもの。いちおう試したんだけど、KEY_ALL_ACCESS でちゃんと取得できた。うーん、謎。 Active Directory と DNS 最近知った、お約束の1つを紹介してみよう。 Windows 2000 および 2003 では、Windows ドメインを構築する際に「Active Directory」というディレクトリサービス(LDAP サービス)のインストールが必要である。「dcpromo.exe」というファイルを実行すると、Active Directory のインストール(ドメインコントローラーになるためのセットアップウィザード)が始まる。また、Active Directory が既にインストールされているマシンであれば、Active Directory のアンインストール(セットアップウィザード)が始まる。2000 であればドメインから完全に離れたサーバーとなり(だったような気がする)、2003 ではドメインのメンバーサーバーとなる。2003 でも最後のドメインコントローラーだった場合はワークグループとなる。 Active Directory の動作には、DNS サービスが必須である。Windows サーバー OS を標準インストールしただけでは、DNS サービスはインストールされない。DNS サービスを本格的に動作させるためには、3つの手順が必要である。 1.インストールDNS サービスは、「前方参照ゾーン」と「逆引き参照ゾーン」という2種類のゾーンを使って名前解決を行う。前方とは、名前(www.yahoo.co.jp や mail.goo.ne.jp のようなもの)から IP アドレスを解決するものであり、逆引きとは、IP アドレスから名前を解決するものである。Active Directory では、前方参照ゾーンが必須で、逆引き参照ゾーンは任意となっている。 数年前までの DNS サービスの構築とは、それぞれのゾーンに対して異なるマシンに「プライマリ」と「セカンダリ」を用意することであった。プライマリ側には実際の名前解決に使用するデータベースを用意し、セカンダリ側にはデータベースを置かない、その代わりにプライマリ側は、データベースに変更がかかった時に、登録されているすべてのセカンダリマシンに対して、その変更点を通知する。自データベースでは解決できない名前や IP アドレスを他の DNS サービスに問い合わせることを、フォワーディング(転送)と言い、その問い合わせる自分のことを「フォワーダ」と言う。 Windows サーバーの場合、それぞれのゾーンに対して、旧 DNS サービスと同様に「プライマリ」と「セカンダリ」を作成することが出来、かつ「Active Directory 統合」ゾーンを作成することも出来る。これは、通常複数台による運用となるドメインコントローラーすべてが同じデータベースを持つ、というものである。旧 DNS サービスでは、プライマリ側マシンが障害でダウンした時に、セカンダリだけではデータベースの更新が行えなかった。Active Directory 統合とすれば、1台のドメインコントローラーがダウンしたとしても、他のドメインコントローラーで DNS データベースを更新できるのである。 話が長くなったが、本題に入ろう。 上記「1.インストール」と「2.ゾーンの作成」は、先に行っても、Active Directory のインストールと同時に行っても構わない。dcpromo のセットアップ中に、DNS サービスを一緒にインストールしますか、という問い合わせがあるので、これに任せても良い。 というか、任せたほうが良い。 今回言いたかったのは、これだけである(^^; Active Directory のインストール中に、DNS サービスのインストールも行わせよう、という意味である。その理由は、「3.レコードの作成」にある。 単にホスト名と IP アドレスを解決させるだけのレコードであれば、いつでも誰でも作れるし、そんな難しい作業でもない。 ・前方用レコードを作った時に同時に逆引き用のレコードを作るか? ・メールサーバーを複数台おったてる時に重み付けをどうするか? ・外部に見せないほうが良い(エイリアスを付けたほうが良い)ホスト名はどれか? といったことは考えなければならないが、たぶん問題ない。 しかし、「ドメインコントローラーとして必要なレコード」をすべて手動で作るのは、漏れが発生する可能性があり、危険である。 「漏れなく手動で構成しろ」と言われたら、わたしは断る(ぉ あとは余談。 Windows DNS サービスおよび新バージョンの DNS サービスでは、動的更新という機能がある。これを、DDNS(Dynamic Domain Name System) と呼んでいる。旧 DNS サービスの場合、動的なレコードの更新が出来なかったので、PPP や DHCP のような IP アドレスが固定されない機器の登録が不可能だった。しかし、クライアントがホスト名と IP アドレスを DNS サービスに登録できるようになれば、この問題は解決する。これが DDNS のメリットである。使用条件は、DNS サービスとクライアントマシンの双方が、DDNS に対応していること、である。(DNS サービスが対応していない場合はクライアント側を無効にする必要がある) アップグレードインストール Windows NT 4.0 や 2000 から、2000 や 2003 へのアップグレードインストールを行う時のお約束。 ・NT 4.0 ServerAdvanced Server や Enterprise Edition は、スタンダードなエディションへのアップグレードが出来ない。 インストール時の SCSI/RAID/他 HDD 認識用ドライバ 古いけど Windows NT 4.0 のお話。 NT 系 Windows では、インストール時に「正しく」ハードディスクにアクセスできる必要がある。これは、IDE 接続のハードディスクには無縁のお話だ。IDE ハードディスクでも、例えば Promise のコントローラーで接続する場合には、この手順が必要となる。 NT 4.0 は、インストール時にわずかな種類の SCSI ドライバをデフォルトで持つ。しかし、世の中一般には山ほど SCSI カードや RAID カードがあり、それらのドライバを持っているわけではない。NT 4.0 は、インストール時にハードディスクを検索する。この検索時に正しいドライバが存在しなければ、「ハードディスクが見つかりません」となり、OS のインストールが出来なくなるのである。 このために、インストール時に隠しコマンドが1つ必要となる。 NT のインストーラーが始まってすぐ、黒い画面が数秒あり、そして青い画面となってドライバ読み込みとなる。リブートがかかるまではほとんど青い画面のままとなる。この、最初の数秒の「黒い画面」の時に F6 キーを押すことで、サードパーティ製ドライバをインストールすることが出来るようになる。具体的には、青い画面に変わってから 30 秒前後でキー入力を求められるメッセージが出るので、(その次のメッセージで)S キーを押してドライバをインストールする。必ずフロッピーディスクから読み込む。DOS/V 機、いわゆる AT 互換機の場合は A ドライブにフロッピーディスクを入れろー と言われるので、用意したドライバ FD をセットする。 なお、NT 4.0 インストーラーでは、この「フロッピーディスクからドライバを読み込む」ことを2回しなければならない。なんで2回もやねん、という思いはあるが、しょうがない、諦めよう(ぉ 2回目で、1回目と同じドライバを読み込ませなければ、結局「ハードディスクが見つかりません」メッセージとなってしまう。 なお、2000, XP, 2003 では、インストール時には黒い画面で F6 キーを押すのではなく、青い画面になってから「ドライバ入れるなら F6 キーを押せ〜」みたいなメッセージが出るので、それから押す。 Exchange Server インポート/エクスポート Exchange Server 5.5 の管理ツールには、メニュー「ツール」に「ディレクトリのインポート」と「ディレクトリのエクスポート」というものがある。これらを使うと、メールボックス、配布リスト(メーリングリストのようなもの)、カスタム受信者(転送先アドレス)の一覧を CSV ファイルで入出力することが出来る。 このインポート/エクスポートについて、少し見ていこう。 ・CSV ファイルの形式は、設定で変更できる。 CSV と書いたが、コンマ区切りでなければならない、というわけではない。メニュー「ツール」の「オプション」で、ファイル形式を変更できる。具体的には、 ・桁(フィールド):デフォルトは , 半角コンマ ・引用符 :デフォルトは " 半角ダブルクォーテーション ・プロパティ :デフォルトは % 半角パーセント の3種類である。ついでに、文字セットとして ANSI と Unicode のどちらかを選択可能である。 ・CSV ファイルには、出力される値と出力されない値がある。 エクスポートをしたからといって、メールボックスや配布リストの全属性値が CSV ファイルに出力されているわけではない。例えば、エイリアスや SMTP メールアドレス、隠し受信者(「アドレス帳に表示しない」チェック)フラグは出力されるが、会社名や smtp メールアドレス、制限の値は出力されない。 ・CCMAIL アドレスをインポートするとすべてのスラッシュがダブルスラッシュになる。 CCMAIL: /1234/5678 をエクスポートして、それをインポートすると、 CCMAIL: //1234//5678 次に管理ツールを見るとこうなっている。バグかえ? わたしは CCMAIL を使わない…… ・同じタイプのアドレスが2つ以上あっても1つしか出力されない 先に「smtp メールアドレスは出力されない」と記述した。 Exchange Server で SMTP プロトコル(TCP/25)を有効にするためには、標準インストールではインストールされない機能を追加しなければならない。「設定」の「接続」を見ると、CC や X400 というコネクタは表示されるかもしれない。ここに「Internet Mail Service (<Machine Name>)」を追加しなければならないのである。 具体的には、メニュー「ファイル」の「その他の作成」にある「Internet Mail Service」で SMTP サービスをインストールする。メールアドレスのドメイン名(@ の後ろ)を決める、DNS を使用する/しないを決める、といったウィザードが開始する。これをしなくてもメールボックスは作成できるが、メールアドレスのドメイン名は、Exchange Server インストール時に入力した「会社」と「サイト」名を使って作られる(例:ABC と SAITO とした場合、@ABC.SAITO.com となる)。 インターネットメール機能を追加すると、メールボックス作成時に、 SMTP: <alias>@<domain name> というレコードが自動追加される。この <domain name> 部分が、上記の「メールアドレスのドメイン名」である。 例えば大企業や迷惑メール対策の場合、エイリアスメールアドレスを作ることが考えられる。例えば、aiue@kaisya.jp というメールアドレスを持っていて、さらに aiue1234@kaisya.com を割り当てるような場合だ。外部向けにはエイリアスである後者を見せたい、という場合である。 管理ツールでメールボックスのプロパティを開くと、「電子メールアドレス」タブに SMTP アドレスが表示されている。ここに「作成」ボタンを使って「インターネットアドレス」を追加する。すると、「smtp」アドレスが表示されるだろう。 SMTP アドレス(大文字)と smtp アドレス(小文字)は、異なるものである。 追加した smtp アドレスを標準とするために、「返信アドレスとして設定」ボタンを使う。SMTP アドレスと smtp アドレスが逆になるだろう。これで、外部向けに送信されるメールの From ヘッダーには、新しい SMTP アドレスが表示されるようになる。 余談。 Exchange Server は、(下のほうで書いている通り)メールヘッダーの From 行を、勝手に書き換えてしまうのである。 ただし、「Internet Mail Service」のプロパティ、「インターネットメール」タブの「詳細オプション」で、「インターネットへの表示名の送信を無効にする」という対策で「表示名」だけは勝手に付与しないようになる。 話を戻そう。 上記のように、SMTP アドレスと smtp アドレスが存在する場合、エクスポートで出力されるのはメインである SMTP アドレスだけである。smtp アドレスが出力されない。(個人的には、腐った仕様だと思うんだが……)仕方ないので、インポートの前に CSV ファイルを編集して smtp アドレスを自分で追加する。 Secondary-Proxy-Addresses というフィールドを追加する。 例:これで、インポートを行った時に、2つ目のアドレスが設定される。 ・X.400 アドレスを削除してはならない。 Exchange Server のインストール時に、CC コネクタ、X.400 コネクタをインストールするかどうかを選択できる。ここのチェックは、はずして構わない。インストールしなくても、X400 アドレスは勝手に出来る。これを削除してはならない。挙げるとキリがないくらい、不具合が発生するからである。(暇があるなら、試してみるといいだろう(誰もしないって(−−;) その他、「バックアップデータをリストアすると Directory Service が起動しなくなった」という問題も発生したので、インストール時の「X.400 Connector」は、チェックを残したままにしておいたほうが良いかもしれない。 ・SMTP アドレスにアンダーバー _ が含まれるメールボックスはインポートできない 配布リストなら _ が入っていても問題ないのに、メールボックスはダメなんかい! というものである。インポート作業をさせると、エラーが発生する。 CSV ファイルをエクセル(当方 Excel 2000)で開いてみるとわかるのだが、_ の部分が ? と表示される。Exchange も ? だと認識して処理を行おうとしているのかもしれない。 注意 ここに記述したのは、すべて ANSI 出力の CSV ファイルでの調査。Unicode 出力の CSV ファイルでは結果が異なるかもしれない。 NTBackup NT系に標準で付属しているバックアップツール、それが NTBackup だ。だいーぶ下のほうで、バグあり、あほー、と書いている、アレだ。 下にも追記したが、Windows 2000 Advanced Server SP4 でのスケジューリングは、いちおう正常に動いているみたいだ。ファイルやメールデータの定期バックアップがされていた。何かが改善されたのだろう。 その NTBackup、そこそこ使えるので、それについて見てみよう。 NTBackup は、NT 4.0 から 2000、2003 になって、機能アップした。一番大きな機能の違いは、 ・NT 4.0 では DAT(テープ装置)にしかバックアップできなかった のが ・2000 ではディスクにもバックアップできるようになった ・2003 では Exchange Server のバックアップが出来なくなった だ(とわたしは思う)。気軽にディスクにバックアップが取れるようになったので、それまでの「DAT でなきゃバックアップできない」とか「バックアップ作業には専用ソフトの購入が必要」とかいう考えをしなくてよくなった。 使用中で開けないファイルは、バックアップできない。例えば、ユーザープロファイルの NTUSER.DAT や、SQL Server のデータベースファイル、Exchange Server のデータファイルなどだ。これらは、「ユーザープロファイルのコピー」やそのアプリケーションが持つバックアップ機能を使って別ファイル出力を行う必要があるだろう。 Exchange Server のメールボックスデータとパブリックフォルダデータは、Windows NT および 2000 の NTBackup がバックアップできる。データベースファイルをファイルとしてバックアップするのではなく、稼働中の Exchange Server サービスに対して接続し、「オンラインバックアップ」を行うのである。(補足:Windows NT 4.0 Server+Exchange Server 5.5 に Windows 2000+Exchange Server 5.5(管理ツールのみ)の環境からアクセスした結果をしゃべっている)これに対して、サービスを停止してデータファイルをバックアップすることを「オフラインバックアップ」と呼ぶ。 ・2003 では Exchange Server のバックアップが出来なくなった (追記参照) これが個人的に大きかった。Windows Server 2003 に Exchange Server 2003 の管理ツールをインストールしたのだが、NTBackup では Exchange Server のオンラインバックアップが出来なかった。増えるはずの項目「Exchange Server」が無いのだ。 ↑こういうの。(画面は Windows 2000 Advanced Server+Exchange Server 2003(管理ツールのみ)) (ここから 2006/05/24 追記) Windows Server 2003 に Exchange Server 2003 をインストールすると、ちゃんとオンラインバックアップ出来るようになった……謎。 ↑こういうの。(画面は Windows Server 2003+Exchange Server 2003(本体)) サービスの依存関係 Windows(NT系)には、サービスと呼ばれるプログラム定義が存在する。これは、Windows 起動時に Winlogon というデスクトップを使って(システムアカウントもしくは指定されたユーザーで)プログラムを起動するものである。 NT 4.0 ならばコントロールパネルの「サービス」、2000, XP などでは管理ツールの「サービス」で、登録されているサービス一覧を表示させることが出来る。「Plag and Play」、「Workstation」、「Remote Procedure Call (RPC)」などはどんな Windows にも存在するだろうし、例えば IIS をインストールしてあれば「IIS Admin Service」や「World Wide Web Publishing Service」といったものも存在するだろう。それぞれのサービスの「表示名」が表示されている。 表示名、は、サービス名、とは違うものなので注意が必要である。詳細を表示すれば、「サービス名」を知ることが出来る。「World Wide Web Publishing Service」であれば、「W3SVC」がサービス名である。 では、ここでは「依存関係」について見てみよう。 そのまんまではあるが、「このサービスは、このサービスに依存している」という設定である。 例えば、TCP/IP を使って通信を行うプログラムであれば、Windows 起動時のネットワーク設定の初期化が終わるまでは動き出してはならない、動いても正常には動作しない、ということになってしまう。そんな時に、この「依存関係」を設定する。 この例を考えてみよう。サービスAは、あるネットワークサービスである。TCP によってクライアントから接続され、データのやりとりを行う。この場合、Windows としてネットワーク初期化(IP アドレス設定やルーティングテーブルの生成など)が完了していなければならない。このネットワーク初期化を行うサービスは、(たぶん)サービス名「lanmanworkstation」の「Workstation」サービスである。つまり、依存関係として「lanmanworkstation」を記述しておけば、ネットワークの初期化が終わってからサービスAが開始されるのである。 依存関係は、レジストリに記述保存されている。プログラマーの場合、サービス用 Win32API をコールするとその引数としてこれを設定することが出来る。しかし、プログラミングの知識はないが、このサービスにはこの依存関係が必要だ、と気が付く場合もある。そんな時は、レジストリを使って手動で依存関係を結ばせてみよう。 なお、レジストリを触る場合は、該当サービスをあらかじめ停止させてから行うと良いだろう。 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ServiceA] DependOnService:REG_MULTI_SZ="lanmanworkstation\0\0" サービス名「ServiceA」の「サービスA」は、Workstation サービスに依存している、という記述である。REG_MULTI_SZ であり、複数のサービス名を登録することが出来る。\0 は、16進数で 00 00 を表す。サービス名の終わりと全サービス名の終わりに \0 が必要。複数記述する場合は、\0 で区切る。また、Unicode で記述する必要がある。\0 が 00 00 と2バイトだったのはこのためである。 Workstation サービスにだけ依存していたが、Server サービスにも依存させたい、という場合は、以下のように記述する。 更新前:lanmanworkstation\0\0 更新後:lanmanworkstation\0lanmanserver\0\0 これで、サービスAは lanmanserver に依存することになった。サービスの詳細を表示させ、依存関係のところに追加したサービスが表示されていることを確認しよう。 |
へなちょこ SUS SUS。ステンレス合金……違う、SUS でステンレスが思い浮かぶ人は、そうそういない(自滅) SUS。Software Update Service。 Windows の標準サービスのひとつ Windows Update は、Windows マシンが Microsoft のホームページにアクセスして、最新のセキュリティパッチやホットフィクス、サービスパックなどをダウンロードし、適用する仕組みである。では、SUS との関連は何か? 個人で使うパソコンであれば、定期的なパッチ当てや OS の再インストール時の一括パッチ当ても、たかだか1回か数回で終わる。しかし、企業や学校といった団体であるとそうはいかない。数十台数百台も当たり前となる。そんな環境で、すべてのマシンが Microsoft から巨大なパッチファイルをダウンロードするのは、非常に効率が悪い。大量のダウンロードは、インターネットの回線に大きな負荷をかけてしまう。そんな時に、SUS が役に立つ。 Microsoft からのダウンロードは1回だけとして、ダウンロードしたファイルをサーバーマシンに保存する。数百台のマシンは、インターネットに接続するのではなく、ローカルネットワークのサーバーにアクセスし、パッチファイルをダウンロードする。こうすると、ネットワークの負荷は激減する。この、あらかじめダウンロードしておくサーバーのツール(サービス)が、SUS なのである。 もちろん、ローカルネットワークのマシンは、Windows Update の接続先としてそのサーバーを指定する必要がある。また、SUS は「重要な更新」だけが対象であり、補助的なツールやドライバの更新は対象とはならない。さらに、Windows 2000, XP, 2003 など、2000 以降のマシンだけが SUS の恩恵を受ける。98, NT 4.0 なんかは、残念ながら使えない。 まぁ、旧 OS はもう Microsoft の保証が切れてるし、ネットワーク負荷が小さくなることは間違いない。「おー、すごいじゃん! うちの社内ネットワークにも1台設置しようぜ」という話にもなる。しかーし。この SUS プログラム、へなちょこである。かなりへなちょこである。へなちょこな理由を挙げていこう。 1.%WINDIR% のドライブが、NTFS フォーマットでなければならない インストーラーは、「SUS をインストールする場所は、NTFS フォーマットのドライブでなければならない」と言う。じゃあ、C ドライブが FAT や FAT32 でも、D ドライブが NTFS だったら、D ドライブにインストールすりゃええやん、と思う。でも、インストーラーが、タコなのである。C ドライブが NTFS でないため、インストーラーが、勝手に「インストールできない」と判断して、終わってしまうのである。 2.IIS が必須 インターネット インフォメーション サービス(IIS)は、Windows 2000 以降に標準で搭載されている機能のひとつである。Web サービス、FTP サービス、SMTP サービスなどが、簡単に立ち上げられる。だが、セキュリティホール満載でへなちょこだから使いたくない(代わりに Apache 使ってる)、とか、もしくはもう別の用途で TCP 80 番ポートを使っている、とかいう場合、軍門に下らなければならない。 SUS は、IIS 上で動き、80 番ポートを使う、これを変えることは出来ない。 3.SUS 用ルートディレクトリはドライブルートでなければならない インストール時に、SUS 用のファイルをどこに保存するかを尋ねられる。通常は C:\SUS のように、ルート直下に SUS フォルダを作るよう指示される。しかし、これが気にくわない場合もある。わたしなんぞは、F:\Program Files\Microsoft\SUS と指定した(ダメだったので F:\Microsoft\SUS も試した)。 これが、ダメなのである。インストールが完了しても、再起動をしても、管理用の画面 http://localhost/SUSAdmin/ が、開けないのである。開けない理由……ファイルにアクセスできないわけではない。default.asp にはアクセスできる、パスも合っている、だが、asp ファイルの処理が出来ないのである。HTTP エラーとなってしまう。 原因は、(想像だが)http://localhost/SUSAdmin/ のルートパス / と、実際のパス \SUS\vroot\ が一致 しないためではないだろうか。SUS\vroot ではない、\SUS\vroot である。つまり、「ドライブのルートに SUS フォルダがなければならない」といったところだ。F:\Microsoft\SUS は、この条件に沿わないため、アウトとなる。 4.インストール後は再起動が必要 サービスの登録だけなんだから、すぐ使えそうなもんである。しかし、IIS コンソールで新たに追加された「SUS」を開始しようとしても、エラーが発生して SUS を起動できない。 なお、IIS Admin や WWW、SUS などのサービスを再起動かけてもダメだった。OS の再起動が必要となる。 5.SUS は「手動 Windows Update の代わり」にならない わたしはずっと、「手動 Windows Update の代わり」になると思っていた。だが、そうではなかった。「自動更新」の接続先を、SUS をインストールしたサーバーとする、なのである。 というわけで、 1.旧 OS とデュアルブートにするため C ドライブが FAT32 2.IIS 嫌い 3.直下にフォルダ作んな(`□´) 4.サーバー OS に再起動させんな(怒) 5.クライアントには「自動更新」させない という理由により、個人的には使えません(@@; 会社は、しゃーなし。 ただ、5に関しては、「勝手にインターネットアクセスされる」のがイヤだから絶対にしない、というものだったけど、「絶対にローカルの SUS サーバーにしかアクセスしない」のであれば、わたしは OK とする。 コモンコントロールのお約束 VB や VC(MFC 除く)でコモンコントロールを使う時のお約束について。 まず、コモンコントロールとはなんぞ? という疑問から始まる。Common Control(共通の制御部品)は、Microsoft から提供されている部品で、Windows において、ツールバーやツリービュー、リストビューのようによく使われるコントロールをまとめたものである。具体的に言うと、msvb_lib_toolbar や TreeView20WndClass といった、ウィンドウクラスの集まりである(よくわからんという人は、飛ばしてしまおう)。直接 CreateWindowEx 関数でウィンドウを作ることも出来る。指定するウィンドウクラスがいろいろ詰め込まれているので、興味があれば調べてみると良いだろう。 じゃあ、コモンコントロールって便利なんだし、使っちゃおう! ということになる。 VB のデザインビューや VC のリソースエディタでフォーム(ダイアログ)を生成し、ツリービューや IP アドレスといったアイテムをぺたぺた貼っていけば良い。 VC の場合、標準でタブコントロールや拡張コンボボックスなどコモンコントロールのアイテムがあるので、そこから選択するだけで良い。しかし、VB ではそんなものはない。あるのは標準の、ボタンやラベル、テキストボックスといったものだけである。VB でコモンコントロールを使うためには、メニューの「プロジェクト」→「コンポーネント (Ctrl+T)」で、 「Microsoft Windows Common Controls 5.0 (SP2)」や 「Microsoft Windows Common Controls 6.0 (SP4)」 といったものにチェックを付ける必要がある。これで、アイテムリストにプログレスバーやリストビューなどが追加され、使えるようになるのである。 実際に EXE ファイルを動作させるときには、COMCTL32.OCX や MSCOMCTL.OCX が必要である。コンポーネントの追加でチェックを付ける時に、必要な OCX ファイル名が表示されるので、それを使うということである。 VB の場合は、これで完了である。他に特にすることはなく、ただ実行すれば、ぺたぺた貼り付けたコントロールがウィンドウに表示されるはずである。 このへんは、VB で IP 通信をするための部品 WinSock コントロールや Excel のような表を表示するための部品 FlexGrid コントロールを使う時の手順と同じである。 しかし、VC ではこれではまだ完了ではない。 例えば、VC で IP アドレスコントロール、SysIPAddress32 クラスを使用するとしよう。IPv4 のアドレスを入力することが出来るコントロールである。普通に GetWindowText や SetWindowText を使えば www.xxx.yyy.zzz という文字列を取得/設定することが出来る。256 以上の値が入力されたら勝手に 255 に訂正してくれたりするので、非常に便利だ。 これを使うためには、InitCommonControlsEx という API をコールしなければならない。(注:Windows Server 2003 では、下記がなくても動作した) #include <commctrl.h> #pragma comment (lib, "comctl32.lib") INITCOMMONCONTROLSEX stICC ={sizeof(INITCOMMONCONTROLSEX), ICC_INTERNET_CLASSES}; BOOL fRet=InitCommonControlsEx(&stICC);詳しくは MSDN Library 参照のこと。IP アドレスコントロールをロードするために、ICC_INTERNET_CLASSES という値をセットして初期化している。他のコントロールも同様に、ICC_TREEVIEW_CLASSES や ICC_ANIMATE_CLASS といった値を使ってこの初期化関数をコールする必要がある。 InitCommonControlsEx とは異なる、InitCommonControls という関数もある。これは、上記のように「IP アドレスコントロールだけを使う」といった部分指定ではなく、コモンコントロールの中でもさらによく使われるものだけを一括で初期化するものである。IP アドレスコントロールは、InitCommonControls 関数では初期化されない。 MFC ではこの関数 InitCommonControlsEx が先に呼ばれており、特にコールする必要はない。また、テキストボックスやボタンなどとは違い、「幾分新しいコントロール」であるため、「Internet Explorer の 4.0 以上が必要です」とか、「Windows 95 では動きません」とかの注意書きがある。旧 OS での互換性を考えている場合には使えないかもしれない。 TCP/IP 送受信の注意点 Windows に限らないと思うが、通信プログラムを作る上での基本的な注意点を記述してみよう。 1.TCP と UDP の違い TCP 通信では主に send/recv で送受信を行うのに対して、UDP 通信では sendto/recvfrom で送受信を行う。 なお、TCP 通信でも、ICMP の ping プログラムのように自分で IP パケットを生成しなければならない場合は send/recv ではなく sendto/recvfrom を用いる。TCP はコネクション型であり、IP と UDP はコネクションレス型であるということで区別できるだろう。 2.ブロッキングモードとノンブロッキングモード 単純に recv と関数コールをすると、そこで停止してしまう。これは、実際にデータが届くまで、関数内部で待機しているからである。 バッチスクリプトとしてシーケンシャルに動く、マルチスレッドで動く、といった場合はブロックしても特に問題とはならないが、ウィンドウプログラムのように常にイベントを待ち受けるプログラムでは、勝手にブロックされてしまうと思った動きにはならなくなってしまう。この場合、select や WSAAsyncSelect を使い、ブロックしないような作りとしなければならない。 3.通常送受信 send, sendto でデータを送信し、recv, recvfrom で受信する。送ったデータが受け取られることを確認できるだろう。 4.分割 大きなデータを送信した場合やトラフィックの大きい(負荷の高い)ネットワークに送信した場合、ひとつのデータが複数に分割され、一度の関数コールでは受け取れないことがある。ABCDEFG というデータを送ったが、ABCDE と FG に分かれてしまうようなケースだ。 5.結合 小さなデータを連続で送信した場合、複数のデータが1つに結合され、一度の関数コールでまとめて取得してしまうことがある。ABC, EFG, LMN とデータを送ったが、ABCEFGLMN を受け取ってしまうようなケースだ。 分割と結合は普通にありうることなので、プログラム的にこれに対応しなければならない。 通常行うのは、実データだけを送信するのではなく、IP パケットや TCP パケットと同じように、(独自の)ヘッダーを先頭に付与し、そこにサイズをセットして送受信されたデータを正しく分割/結合することである。一度の関数コールで 64kB を超えるようなデータを送受信することは、あまり行われない。ヘッダー要素のひとつであるサイズフィールドは、2バイトあれば良いのではないだろうか。 わたしがたくさんのデータを送りたい場合は FIRST : 最初 MIDDLE : 最初でも最後でもない中間 LAST : 最後 ONLY : 単独 のようなヘッダー要素をもうけ、アプリケーションとしてあらかじめ分割してから送信するようにしている。RTP パケットのようにシーケンス番号やタイムスタンプフィールドをもうけるというのも手である。 上記が正しく行われることを確認できたら、暗号化や他プロトコルとの統合など、応用プログラムを作っていくと良いだろう。 最小化と最大化のチェック Visual Basic(試したのは 5.0 と 6.0)で、そのフォームが最小化された、最大化された、というイベントを拾うための方法。 コーディングを行うイベントは、Form.Resize。通常ウィンドウサイズが変更されると、この Resize イベントが発生する。最小化および最大化の時も、このイベントが発生する。では、何をもって「ウィンドウサイズ変更」と「最小化もしくは最大化」を条件分岐するか、ということになる。 これには、Form.Left, Form.Top の値を使用する。これらの値を見ることで、その時の表示状態が分かるのである。 最小化: Left: -480000, Top: -480000 最大化: Left: -60, Top: -60 ウィンドウのあるプログラムの場合、終了時のウィンドウ位置とサイズを覚えておいて、次の起動時にはそのウィンドウ位置とサイズにする、という機能を搭載するのが一般的である。しかし、上記のように、最小化および最大化時には値がマイナスとなってしまう点に注意しなければならない。起動時に値の妥当性チェックを行い、マイナスだから強制終了、のようなコーディングはしてはならない、ということになる。また、マイナスであっても正しく起動する(最小化もしくは最大化状態で起動する)、またそれは不自然だから通常表示時の位置サイズで起動する、といった考慮も必要となるだろう。 なお、Me.Left = -480000 という代入は出来ない。Windows 2000 SP4, VB 5.0 SP3 でこれを試すと、Me.Left の値は -245760 という値(-16384 * 15; 15 は Screen.TwipPerPixelX)に変わってしまった。ピクセル単位で -16384 以上しか代入できない、ということである。-480000 であった場合は、PostMessage や ShowWindow をコールして、別に最小化をしてあげる必要がある。最小化で開始したいなら、 ・最小化していること と ・最小化していない時のウィンドウ位置 を覚えておかなければならない。 ウィンドウ位置サイズを最小化中(最大化中)に取得する関数は、GetWindowPlacement である。この関数をコールすると、rcNormalPosition(RECT 型変数)に通常表示時のウィンドウ位置サイズが入ってくる。 On Error GoTo label の入れ子 Visual Basic(試したのは 5.0)で、1関数で2つの On Error GoTo label_x を入れ子にしてみた。 こんな感じだ。処理1で例外が発生した場合はループの外へ、処理2で例外が発生した場合はループを続行する、というタイプである。Private Sub Xxxxx() Do On Error GoTo label_1 (処理1) If xxx = yyy Then On Error GoTo label_2 (処理2) (処理3) label_2: End If Loop label_1: End Sub 結論は、NG。いったん処理2で例外が発生してしまうと、次の例外発生で、プログラムが強制終了してしまう。 処理1も処理2も、例外が発生する可能性がある、VB だから(ぉ でも、1回しか例外がキャッチできないのでは上記のコードでは実現できない。例えば、On Error Resume Next で例外が発生してもすぐ次へ進む、という場合は、Err.Clear を使うといいようなことは聞いたことがある(未確認)。しかし、上記のコードでは、どこに Err.Clear を入れてもプログラムの強制終了を防ぐことは出来なかった。 簡単な回避策は、処理2,処理3の部分を別関数にして、その別関数の中で On Error GoTo を使うこと。1関数内で複数の例外取得をすることは出来ないが、サブルーチンとすれば2関数となるので取得が可能となる。 Pro*C 基本コール Oracle の Pro*C を使ったコーディング例を書いてみる。Pro*COBOL でも流用可能。 ……長いな(*へ*;//------------------------------ // 単独接続 // EXEC SQL CONNECT :vcUserName IDENTIFIED BY :vcPassword USING :cConnectString; //------------------------------ // データベース名(エイリアス名)指定接続 // EXEC SQL CONNECT :vcUserName IDENTIFIED BY :vcPassword AT :cDB1 USING :cConnectString; //ホスト変数による //この場合 Oracle 8.1.6(以前?)では例外が発生する。 //プリコンパイルすると変数配列が //[0] [1] [2] の次に [4] となる。 //------------------------------ // データベース名(エイリアス名)指定接続 // EXEC SQL DECLARE ALIASNAME DATABASE; EXEC SQL CONNECT :vcUserName IDENTIFIED BY :vcPassword AT ALIASNAME USING :cConnectString; //ホスト変数ではない //------------------------------ // コミット/ロールバック // EXEC SQL COMMIT WORK; EXEC SQL AT :cDB1 ROLLBACK WORK; //------------------------------ // 切断 // EXEC SQL ROLLBACK RELEASE; EXEC SQL AT :cDB1 COMMIT RELEASE; //------------------------------ // SELECT // EXEC SQL SELECT f1_int, f2_char, f3_date INTO :stData INDICATOR :stData_i FROM TableName WHERE ROWNUM=1; //NULL チェックにインジケーター変数(標識変数)を使用 //------------------------------ // INSERT, UPDATE, DELETE // EXEC SQL INSERT INTO TableName (f1_int, f2_char) VALUES (:iIndex, :cString); EXEC SQL AT :cDB1 UPDATE TableName SET f2_char=:cString, f3_date=TO_DATE(:cNow) WHERE f1_int=:iIndex; EXEC SQL AT :cDB1 DELETE TableName WHERE f1_int=:iIndex; //------------------------------ // 動的 SQL 文発行 // sprintf(cSQL, "INSERT INTO TableName (f1_int, f2_char) " "VALUES (%d, '%s')", iIndex, cString); EXEC SQL EXECUTE IMMEDIATE :cSQL; //------------------------------ // 明示カーソルの使用(SELECT による複数行の取得)(静的) // EXEC SQL AT :cDB1 DECLARE cursor1 CURSOR FOR SELECT DISTINCT f2_char FROM TableName WHERE f3_date IS NOT NULL ORDER BY f1_int DESC; EXEC SQL OPEN cursor1; //静的の場合 AT 句は不要 EXEC SQL FETCH cursor1 INTO :vcString; if(sqlca.sqlcode==NOT_FOUND) //1403 printf("Count: %d", sqlca.sqlerrd[2]); EXEC SQL CLOSE cursor1; //------------------------------ // 明示カーソルの使用(SELECT による複数行の取得)(動的) // sprintf(cSQL, "SELECT f1_int, f2_char, TO_CHAR(f3_date) " "FROM %s " "WHERE f1_int<:iIndex " "GROUP BY f3_date", cTableName); EXEC SQL AT :cDB1 PREPARE prepare1 FROM :cSQL; EXEC SQL AT :cDB1 DECLARE cursor1 CURSOR FOR prepare1; EXEC SQL AT :cDB1 OPEN cursor1; //動的の場合 AT 句が必要 EXEC SQL AT :cDB1 FETCH cursor1 INTO :stTables INDICATOR stTables_i; if(sqlca.sqlcode==NOT_FOUND) //1403 printf("Count: %d", sqlca.sqlerrd[2]); EXEC SQL AT :cDB1 CLOSE cursor1; //------------------------------ // 配列 SELECT // int iRuiseki=0; struct TableData { int iIndex; varchar vcString[16+2]; //varchar[n] → unsigned char[n+2] + unsigned short VARCHAR vcDate[16+2]; } stTables[10]; struct TableData_i { short sIndex; short sString; short sDate; //==-1: NULL } stTables_i[10]; EXEC SQL AT :cDB1 FETCH cursor1 INTO :stTables INDICATOR stTables_i; //10 件まとめて取れる if(sqlca.sqlcode==NOT_FOUND) { if(sqlca.sqlerrd[2]==iRuiseki) break; iRuiseki=sqlca.sqlerrd[2]; } //------------------------------ // 配列 INSERT // int iCount; int iIndex2[10]; char cString2[10][16+4]; char cDate2[10][16]; shoft sDate2[10]; //インジケーター変数 EXEC SQL FOR :iCount INSERT INTO TableName (f1_int, f2_char, f3_date) VALUES (:iIndex2, :cString2, :cDate2:sDate2); //iCount 数 INSERT される //例えば cString2[0]==""(空文字)だった場合、 //勝手に NULL となる。 //------------------------------ ウィンドウプロシージャとダイアログプロシージャ ウィンドウやダイアログを生成する関数(CreateWindow, CreateDialog, DialogBox など)をコールする場合、そこに発生するイベントを取得するためのコールバック関数が必要である。CreateWindow をコールした場合には WindowProc、CreateDialog をコールした場合には DialogProc といったコールバックプロシージャを記述し、お決まりの4つの引数 HWND, UINT, WPARAM, LPARAM をもってすべてのイベントに対応する。 WindowProc と DialogProc には大きく違う点がいくつかある。ここではそれらについて取り上げてみる。 1.コールバックプロシージャの戻り値 LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM) BOOL CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM) WindowProc の場合、戻り値の型は LRESULT(LONG)型である。イベントを受け取り、自分で処理し、他に処理をさせなくても良い場合、return 0; とする。(0 でなくてもいいのかも) DialogProc の場合、戻り値の型は BOOL 型である。イベントを受け取り、自分で処理し、他に処理をさせなくても良い場合、return TRUE;(0 以外)とする。 2.処理しないイベントへの対応 return DefWindowProc(hWnd, uiMsg, wParam, lParam); return FALSE; 自分で処理しないイベントの場合、もしくは処理したけど他のウィンドウに対してチェインさせたい場合(例:Windows の終了イベント WM_QUERYENDSESSION)のものである。 WindowProc の場合、DefWindowProc という API をコールする。 DialogProc の場合、DefWindowProc をコールしてはならない。return FALSE; とすることで、自動的にチェインしてくれる。 3.上がってこないイベント (確認したもの) エディットボックス(テキストボックス)への/からのフォーカス移動によるイベント、WM_SETFOCUS, WM_KILLFOCUS。タブキー押下やマウスクリックなどでエディットボックスに入ったり抜けたりすると発生する。 WindowProc の場合、普通にイベントが上がってくる。 DialogProc の場合、WM_SETFOCUS や WM_KILLFOCUS というイベントを取得できない。 4.勝手に上がってくるイベント ENTER キーと ESC キーの押下は、特殊な意味を持つ。 DialogProc の場合、かつ DialogBox 関数によってダイアログを表示させた場合、ENTER キーで IDOK,ESC キーで IDCANCEL が発生する。switch case ブロックで、WM_COMMAND を記述し、IDOK および IDCANCEL の処理を記述しなければならない。 余談。 WindowProc コールバック関数側に DefWindowProc の記述がなかった場合、CreateWindow が失敗する。CreateWindow をコールするといくつかのイベントが一気に発生するので、それをデフォルトに処理させなければならない。一気に発生するイベントのひとつ、WM_CREATE をつかまえて、ウィンドウとしての初期化処理を行う、というのはよくある話である。 サブクラス化に関しては、時間があるときにでも別項目として記述しよっかな。検索すりゃ他のとこでいくらでも見つかるけど(−−; 検索キーは、「サブクラス化」と「SetWindowLong」、「GWL_WNDPROC」。サブクラス化を使うと、VB や MFC でも自分で作ったウィンドウプロシージャでイベントを横取りできる。VB だと、デバッグ中になんらかの例外が発生すると、VB ごと死んじゃうのは仕様です( ̄▽ ̄; インストーラーに求められる一般的事項について 社内と掲示板に書いた、インストーラーを作るときの観点をピックアップしてみた。どれを採用するか、採用した場合に何に注意したらいいかが、なんとなくわかるようにしてみた。つもり。 ------------------------------ (1)インストールパスについて ---------- ・デフォルト表示は妥当なものか? 例:C:\ProjectName 例:C:\Program Files\ProjectName ・ドライブが変わっても問題ないか? 例:E:\ProjectName ・半角スペースが含まれるフォルダ名でも問題ないか? 例:C:\Program Files\ProjectName ・ネットワークドライブ,リムーバブルドライブをサポートするか? 例:Z:\ProjectName ・ルートディレクトリ指定を許容するか? 例:F:\ ・パスの最後に \ が有っても無くても問題ないか? 例:C:\ProjectName\ 例:F: ・\ 記号の含まれる全角文字が含まれるフォルダ名でも問題ないか? 例:D:\ProjectName\サーバー機能 例:D:\ProjectName\ソリューション 「能」という文字は Shift-JIS で 0x94 0x5C であり、 "\" 記号(0x5C)を含んでいる。 なお、Shift-JIS で 0x5C を含む全角文字は以下の通り; ―ソЫ\噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌 拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭xx\\ ・2回目以降のインストール時に、1回目に使用したパスを 再利用するか? ・複数の \ が並んでいた場合に処理するか? 例:C:\\\\\\\\ProjectName ←Windows 的には処理可能 ・複数の \ が並んでいた場合、それをファイルやレジストリに そのまま記述するか、もしくは1つまで削ってから記述するか? ・空き容量チェックを、インストールパスで行っているか? 例:C:\ProjectName チェック対象は、C:\ProjectName であって C: ではない。 (別パーティションをフォルダにマウントしている場合に 不具合となる) ・サービスパックやパッチのインストール、また再インストールで 最初のインストール時のパスを表示するか? ・隠し共有パス(Xxx$)を使用する場合、コンピュータごとに一覧を 取得しているか? (C$ や Admin$ といったデフォルト管理共有は、セキュリティを 考慮した削除や変更の可能性がある) ------------------------------ (2)インストールについて ---------- ・スクリプトによる自動インストールをサポートするか? ・インストーラープログラムの位置が異なるインストールを サポートするか? 例:インストール媒体が CD-ROM で Setup.exe を起動する場合 ルートフォルダの Setup.exe は OK ファイルを全部コピーした先 D:\Temp\Setup.exe は OK/NG? ・ID やパスワードが必要か? ・ID やパスワード入力をさせる場合、何回の失敗でインストーラーを 終了させるか? ・NT 系 OS へのインストールで、Administrators 権限が必要か? また、必要な場合、インストール時にそれを表示しているか? ・スタートメニューのプログラムへショートカットアイコンを 登録するか? ・クライアント系 Windows へのインストールをサポートするか? ・サーバー系 Windows へのインストールをサポートするか? ・インストール後に再起動が必要か? また再起動が必要な理由は 明確であるか?(不要な再起動をさせていないか?) ・ネットワークインストールをサポートするか? ------------------------------ (3)アンインストールについて ---------- ・アンインストールできるか? ・アンインストールに、インストール媒体が必要か? 必要な場合、それが表示されるか? ・アンインストールで、コピーされているファイルが削除されるか? ・アンインストールで、登録したレジストリキーが削除されるか? ・アンインストール後に、Windows のコントロールパネルに項目が 残っていないか? ・アンインストールで、登録したショートカットアイコンが 削除されるか? ------------------------------ (4)修復インストールについて ---------- ・修復インストールをサポートしているか? ・修復インストールで何をするのか? またそれは表示されるか? ・修復インストールの中断をサポートするか? ------------------------------ (5)ファイル/レジストリ情報の上書き ---------- ・同名のファイルが存在する時に、インストールを行うものに対して メッセージを表示するか? ・同名のファイルが存在する時に、それをどうするか? →強制上書き/確認してから上書き/タイムスタンプが古い時だけ 上書き/古いファイルの名前変更/インストール作業中止? ・存在するレジストリレコードをどうするか? →更新/変更なし/削除? ------------------------------ (6)インストール未完了時の処理 ---------- ・インストール作業中断時に、再度インストール作業が出来るか? また完了するか? ・インストール作業失敗時に、再度インストール作業が出来るか? また完了するか? ・未インストール時に、Windows のコントロールパネル (アプリケーション/プログラムの追加と削除)に項目が 残っていないか? ・未インストール時に、生成したショートカットファイルが 残っていないか? ------------------------------ (7)再起動時のファイル処理 ---------- ・起動中やその他の不具合によってファイルの更新や削除が 出来ない場合に、自動的にサービスを停止する、マシンの 再起動時にファイルの更新や削除を行う、といった処理を 行っているか? ・再起動時にファイルを処理したか? ------------------------------ (8)バッチコマンド実行 ---------- ・スクリプトファイル,バッチファイルは正しく実行されるか? ・スクリプト,バッチコマンドの実体ファイルはどのような条件で 保存されているか? ------------------------------ (9)一時(テンポラリ)ファイル ---------- ・一時ファイルを生成する必要があるか? ・一時ファイルをどこに生成するか? 環境変数 %TEMP% を使うか? ・一時ファイルを生成した場合、それを削除するか? ・一時ファイルをどのタイミングで削除するか? ・一時ファイルは、インストーラー終了で削除されたか? ------------------------------ (10)レジストリへの登録 ---------- ・regsvr32 を使う必要があるか? ・regasm を使う必要があるか? ・regsvr32 を使う場合、かつ半角スペースが含まれるパスの場合、 ショートファイル名(8.3 形式名)に変換、もしくは "" で囲って登録/解除しているか? ・regsvr32 を使う場合、かつ全角文字が含まれるパスの場合、 regsvr32 が成功してもプログラムは動作しないが、 これに当てはまらないか? ・regasm は、.NET Framework のバージョンに一致しているか? 例:C:\WINNT\Microsoft.NET\Framework\v1.0.3705 例:C:\WINNT\Microsoft.NET\Framework\v1.1.4322 ・サービスプログラムの場合にそのプロパティを設定しているか? ------------------------------ (11)環境変数への登録 ---------- ・プログラムファイルをコピーしたフォルダを環境変数に 追加するか? 例:%PATH% に D:\Project\Bin を追加する %PATH%;D:\Project\Bin D:\Project\Bin;%PATH% ・同一環境変数登録を行っていないか? 例:%PATH% ...;D:\ProjectName →...;D:\ProjectName;D:\ProjectName ・アンインストール時に抹消するか? ------------------------------ (12)アップグレード(ダウングレード) ---------- ・オンラインサービス(HTTP, FTP など)で最新バージョンの プログラムをダウンロードできるか? ・アップグレード後のダウングレード(部分アンインストール)を サポートするか? ------------------------------ (13)マニュアル(例:Readme.txt) ---------- ・マニュアルが用意されているか? ・マニュアルに、少なくとも以下の情報が記述されているか? バージョン番号 日付 著作権情報 稼働条件 改修履歴 ・インストール時,アンインストール時に表示させる必要があるか? ・インストール作業を行わなくても表示できるか? ------------------------------ (14)ユーザー登録 ---------- ・ユーザー登録が必要か? ・オンライン(HTTP, 専用ツールなど)や専用葉書でのユーザー 登録が可能か? ------------------------------ (x)その他 ---------- ・インストール,アンインストールに関するログは出力されるか? また出力する場合、その場所は適当か? ・インストール作業を行うユーザーがまったくの初心者でも 理解できる文章か? ・システムドライブが C 以外でもインストールおよび動作可能か? 例:A:\Windows 例:E:\WINNT ・Windows フォルダがデフォルト以外でもインストールおよび動作可能か? 例:C:\Windows98 (Windows ではない) 例:A:\WINNT2K (WINNT ではない) ・インストールパスやシステムドライブに関係なく、C ドライブに 書き込むファイルが有ったりしないか? ・ファイルを更新するパスが、FAT ドライブでも NTFS ドライブでも 問題ないか? ・システムフォントがどんなものでもどんな大きさでも、すべての 文字が表示され、読めるか? 例:固定ピッチフォントとプロポーショナルフォント 例:フォントサイズ 標準,特大,大 ・NT 系 OS で Administrator という固定ユーザー名を使っていないか? ・NT 系 OS でローカル/ドメインにユーザーを作成するか? また、パスワードの設定は妥当か? ・IP 通信で使用するポート番号の使用チェックをするか? 例:Web サーバーサービスなので TCP/80 をチェックする ------------------------------世の中のインストーラーには、へなちょこなものがたくさんある。デフォルトパスでなきゃ動かないとか、C$ とか ADMIN$ とかいう隠し共有パスをベタに使うとか、テンポラリファイルを作って使って消さないとか、ログがないので解析できないとか…… せめて自分の作るものには、そういうことがないようにしたい、と思うところ。 パス文字列の最後の \ チェック Win32API の _ismbblead を使うと、それが全角文字の先頭1バイトであるかそうでないかを判断することが出来る。例えば、0x81 0x48 という並びの文字(Shift-JIS で言うところの全角クエスチョン "?")がある。また、0x48 という文字(半角の H )がある。0x48 だけを検索していたのでは、それが H なのか ? の後ろなのかはわからない。この時、0x48 の前の数字を _ismbblead 関数に渡すことで、0x48 が全角文字の一部なのか、半角文字単体なのかを調べることが出来る。 ところが、これは言語(コードページ)に依存してしまう。通常、日本語 Windows であれば Shift-JIS が標準コードだが、中国語や韓国語をデフォルトとしている日本語 Windows もある。このような場合、_ismbblead ではチェックが不十分となる(かもしれない)。 char 型配列として文字列を扱う場合、よく見られる不具合は、ディレクトリ名を表現するための \ に関するものである。\ はコード 0x5C である。先の 0x48(H)と同じように全角文字にも含まれる。例えば、 ソ:0x83 0x5C 十:0x8F 0x5C 能:0x94 0x5C 表:0x95 0x5C といったものが、よく使われる全角文字として挙げられる。つまり、「フルパスの終わりに \ が付いているかどうかチェックしよう」として、0x5C を検索していたのでは、 C:\Windows\ D:\機能 E:\Projects\一覧表 この3つ全てが「付いている」となってしまうのである。明らかにまずい。 これは、C 言語において、char 型配列による文字列操作をしている時に起こりうる。VB や C# ではこの問題は発生しない。なぜ発生しないか? それは、文字列 String 型を ユニコード(Unicode)で扱っているからである。Unicode を使うと、世界すべての文字を表現することが出来ると言われている。Unicode についての詳述はここではしないが、C 言語でも文字列を Unicode として扱えば、char 型配列としての文字列操作に起こりうる不具合を回避することが出来るということになる。 長い前振りとなってしまったが、ここで、上記の不具合を回避するための仕組みをひとつ紹介しよう。 char 型配列の文字列(先頭アドレス)を引数として渡すと、その終わりの文字列が \ かそうでないかを返してくれる。Win32API の MultiByteToWideChar という関数を使って、char 型配列の文字列を、Unicode の配列(ワイド文字)に変換しているのである。long CheckBackSlash(char *pcPath) { int iRet; long lRet; long lSize; WCHAR *pwPath; //WCHAR := unsigned short //引数チェック if(pcPath==NULL) return -1; //サイズ算出 lSize=(strlen(pcPath)+1)*sizeof(WCHAR); //バッファ生成 pwPath=(WCHAR *)malloc(lSize); if(pwPath==NULL) return -1; //0埋め ZeroMemory(pwPath, lSize); //マルチバイト→ワイド文字並び iRet=MultiByteToWideChar( 0, 0, pcPath, strlen(pcPath), pwPath, lSize); if(pwPath[wcslen(pwPath)-1]==L'\') //L'\'==0x005C lRet=1; else lRet=0; //バッファ解放 free(pwPath) return lRet; } 例えば、"F:\Program Files\" と "F:\Program Files\\\\\\\\" は、どちらも同じパスを示す。Windows としてはどちらも正常となる。特に問題がなければ、無条件に \ をくっつける、というのが簡単である。 パス文字列の一部を置換する、という場合、 F:\Program Files\Data\P1.jpg の "F:\Program Files\" の部分を "Z:\Backup\" に置換する という場合は、「一致!」→「置換!」、という作業になるので、\ の数も合わせなければならない。もしも無条件に処理をしてしまったら、 「フルパスの中に F:\Program Files\\ なんか見つからない!」とか、 「置換したら Z:\Backup 言われそうなものである。 SQL 文の日時指定 オラクルデータベースへのアクセスで、TO_CHAR と TO_DATE の使い方に(自分の)勘違いがあったので、記述。 SELECT TO_CHAR(set_date, 'YYYY/MM/DD HH24:MI:SS') FROM config_table WHERE set_date <= TO_DATE('20040514123456', 'YYYYMMDDHH24MISS');config_table というテーブルに存在するレコードの、set_date フィールドを表示する SELECT 文。条件として、指定の日時より小さいもの(過去のもの)、としてある。 第1の勘違い 時刻の分は、MM ではダメ(汗) HH:MM:SS × → HH:MI:SS 第2の勘違い 24時で表現したい場合、HH ではダメ(更汗) HH:MI:SS × → HH24:MI:SS Oracle Master GOLD(PL/SQL)は科目合格してるけど、長いこと触ってなかったからすっかり忘れてたよ( ̄▽ ̄; タスクバーへの表示 プログラムを作る時に、「起動時のタスクバーへの表示(ShowInTaskBar)」という設定をすることが出来る。メモ帳や Internet Explorer、ペイントなどは、起動すれば必ずタスクバーにアイコンが表示される。単純なツールや子画面の場合は、タスクバーに表示されないことがある。 タスクバーへの表示/非表示は、イコール「Alt+Tab」キー押下時のアプリケーション切り替え可否でもある。 「よく使うツールが、タスクバーへの非表示設定になっていて、Alt+Tab 押しても切り替えできないよー」というわけである。 これを、自プログラムや外のプログラムから、設定変更してしまおう、というコーディングが、以下のものである。 long lStyle; lStyle=GetWindowLong(hWnd, GWL_EXSTYLE); if(表示する!) lStyle |= WS_EX_APPWIN; else lStyle &= (~WS_EX_APPWIN); //いったん隠す必要がある ShowWindow(hWnd, SW_HIDE); SetWindowLong(hWnd, GWL_EXSTYLE, lStyle); ShowWindow(hWnd, SW_NORMAL);じーっと見ていると、隠す必要があるのでちらつくわけだが、とりあえず表示/非表示はこれで変更可能である。 DLL 作成のデバッグ Visual Studio において(試したのは 6.0 のみ)、DLL ファイルを作るためにコーディングを行い、それをデバッグ(動作試験)する。DLL は単体で実行することが出来ないので、他に EXE を作り、そこから DLL 内部の関数をコールする。 この「他の EXE を作る」ことに、お決まりではあるものの多少めんどくささを感じていた。これを解消する、他の EXE を作らずに DLL 生成の自プロジェクトだけでデバッグを行うことが可能であることを知った。 プロジェクトの設定の「リンク」タブ、「プロジェクトオプション」を触る必要がある。 /subsystem:console →main 関数から始まるコンソールプログラム /subsystem:Windows →WinMain 関数から始まるプログラム /dll →DllMain 関数から始まる DLL プログラム 上記を書き換えるだけで、EXE と DLL の生成を切り替えることが出来るのである。デバッグ版では /DLL を消して /SUBSYSTEM:CONSOLE とし、下記のように main 関数を用意する。リリース版は /DLL のまま。こうすることで、簡単に単体デバッグを行うことが可能となるのである。 デバッグ版では main が有効となり、リリース版では(_DEBUG というコンパイルオプションがないので)DllMain が有効となる。//DLLEXPORT 宣言 #ifdef _DEBUG #define DLLEXPORT #else #define DLLEXPORT __declspec(dllexport) #endif #ifdef _DEBUG //------------------------------ // main // int main(int argc, char **argv) { SampleFunction(); return 0; } #else //------------------------------ // DllMain // BOOL WINAPI DllMain( HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //プロセス起動 break; case DLL_PROCESS_DETACH: //プロセス終了 break; case DLL_THREAD_ATTACH: //スレッド起動 case DLL_THREAD_DETACH: //スレッド終了 break; default: break; } return TRUE; } #endif //------------------------------ // SampleFunction // DLLEXPORT long WINAPI SampleFunction(void) { MessageBox(NULL, "関数コール!", "動作確認", MB_OK); return 0; } なお、DLL の生成にて .def ファイルをプロジェクトに追加している場合、EXE を作っても実行することは出来ない。これは、エントリポイントとして main や WinMain を探しながらも、実は DllMain の引数3つをチェックしようとするからである。なので、プロジェクトには .def を追加することが出来ない。これに対応するため、ソースファイルのどこかに、以下のコードを記述するか、 以下の文字列を、「プロジェクトの設定」画面、「リンク」タブの「プロジェクトオプション」に記述する(リリース版のみ)。#ifndef _DEBUG #pragma comment (linker, "/DEF:path/xxx.def") #endif /def:"path/xxx.def"これで、デバッグ版以外で .def ファイルを取り込むことが出来る。 .NET プログラムで作ったウィンドウへのイベント通知 Visual Studio .NET で作ったウィンドウに対して、.NET より前の Visual Studio で PostMessage/SendMessage してみた。 デバッグ版(「デバッグ情報を生成する」にチェック有り).NET プログラムの場合は問題なく動作したが、リリース版(「デバッグ情報を生成する」にチェックなし)EXE ファイルの場合、2回目のイベント通知で例外が発生した。 結局、イベント通知が出来ないとお話にならない作りにしてしまったので、VC で作った DLL のほうにウィンドウを生成するコードを追加し、その DLL を .NET プログラムからコールする、という形に変更した。 最初は、最適化の問題かと思ったんだわ。でも、「最適化を有効にする」チェックボックスで違いを見るが、動きは変わらず。リリース版で、「デバッグ情報を生成する」にチェックを入れると、例外は発生しなくなった。じゃあ、デバッグ情報を生成したら、リリース版でもいいんだ! ……そんな変な状態で提供なんかしたくない(滅) うちのプログラムだけかもしれんけど(*へ*; ……いちおメモメモ。 割り算 Visual Studio 97 や 6.0 では、割り算の結果を整数型の変数にセットする時は、小数点以下は切り捨てられる。 long lAverage; long lData[4]={1, 2, 0x1000, }; lAverage = (lData[0]+lData[1]+lData[2]+lData[3]) / (sizeof(lData)/sizeof(lData[0]));上記の場合、(1+2+4096+0)=4099 を 4 で割り、1024.75 となる。lAverage は整数型のため、実際に代入される値は切り捨てられて 1024 となる。マイナスの場合も考え方は同じで、-1.5 は、-2 ではなく -1 となる。 ところが、Visual Studio .NET では、小数を整数型の変数にセットすると、切り捨てとはならない。 Dim iData As Integer Dim bData(4) As Byte iData = &H9999 bData(0) = iData Mod 256 bData(1) = (iData / 256) Mod 256上記の場合、iData / 256 = 153.597... である。bData(1) にセットされていたのは、153 ではなく、勝手に四捨五入された 154 だった。もし 153 が欲しければ、 bData(1) = Fix(iData / 256) Mod 256としなければならない。 ユーザープロファイルのコピー NT 系 OS では、Windows を使用するために、ログオンという作業が必須である。ユーザーごとに異なる「プロファイル(というログオン時の情報)」が生成され、共通のマシンを複数のユーザーアカウントが別環境で動かすことが出来る。 よくやるのは、まず Windows インストール直後に Administrator アカウントで基本的な設定(画面の色、フォントなど)、サービスパックあて、パッチあて、ドライバインストール、共通アプリケーションのインストールといったことを行い、その後各ユーザー(例:User01)でログオンする、ということである。 Administrator のプロファイルと User01 のプロファイルは異なるものであり、Administrator で行った基本的な設定は User01 のデスクトップには反映されない。しかし、Administrator の時に設定した情報をそのままごっそり引き継ぎたい場合がある。ここで行う作業が、「ユーザープロファイルのコピー」である。 コントロールパネル「システム」で、ユーザープロファイルという項目を表示することが出来る。そこを見ると、ログオンしたことのあるユーザーのリストが表示される。上記の例の場合は、Administrator と User01 が表示されるはずである。 まず Administrator を選択し、コピーボタンを押す。そして User01 のプロファイルが保存されているフォルダを選択するのである。(NT 4.0 や NT 4.0 からアップグレードした 2000 や XP では %WINDIR%\Profiles フォルダの中、2000 や XP などでは \Documents and Settings フォルダの中、Vista 以降では \Users がデフォルトである) NT や 2000 の場合、気を付けなければならないのは、ログオンしている自分へ、別のユーザープロファイルをコピーすることは出来ない、ということである。User01 でログオンしている時に、Administrator プロファイルを自分にコピーすることは出来ない、したけりゃ User01 以外でログオンしろー、ということである。 XP や 2003 では、さらに条件が増えている。ログオンしている自分のプロファイルを、別のユーザープロファイルにコピーできない、ということである。Administrator でログオンしている時に、Administrator プロファイルを User01 にコピーすることが出来ないのである。この場合、Admin01 といった別の(管理者権限を持った)アカウントでログオンし、Administrator プロファイルを User01 にコピーする必要がある。 VS.NET 2002 と 2003 のプロジェクトファイルの違い まだよくわからん( ̄▽ ̄; Visual Studio .NET の違い。とりあえず間違いないのは、 ・VS.NET 2002 で作ったソリューションは 2003 では開けない(変換を要求される) ・VS.NET 2003 で作ったソリューションは 2002 では開けない(変換できない) ということ。 VB 5.0 と 6.0 のプロジェクトファイルの違い Visual Basic 5.0 と 6.0 で作られたプロジェクトファイル(.vbp)には、1点だけ大きな違いがある。.vbp ファイルは普通のテキストファイルであり、内容をテキストエディタで表示/編集することが出来る。VB 6.0 では、5.0 にはなかった1行が追加されている。 Retained=0 同一 Windows に VB5 と VB6 を共存させることが出来る(みくろそふたーはなんかダメだとか言ってるけど)。でも、.vbp ファイルをダブルクリックしただけでは、後からインストールしたほうのバージョンの Visual Basic が起動してしまう。自ツール(VB456)に拡張子 vbp を関連付けておいて、引数として渡された .vbp ファイルの中を見て、Retained の行があるかないかで VB5 か VB6 のどちらかを起動するよう振り分けている。456 と命名したが、VB 4.0 には未対応(ぉ コントロールの配列 VB5 や VB6 では、デザイン時にコントロールオブジェクトを配列化することが出来る。例えば、Label1 を作り、それをコピー&ペーストすると「既に同じ名前のコントロール 'Label1' があります。コントロール配列にしますか?」と聞いてくる。また、そうしなくても、プロパティの1つ、Index に数字をセットすることで、簡単にコントロール配列を作ることが出来る。 VB.NET では、出来なくなった(涙)そもそも、Index というプロパティが存在しない。Label1(1), Label1(2), Label1(3), ... と出来なくなったため、Label1, Label2, Label3, ... としなければならない。 非常に不便に感じるので、ReDim を使って配列を作り、必要な分だけ New することにした。この場合、ウィンドウへの配置場所が雛形と同じか Windows 任せになってしまうので、Left や Height といったプロパティに別途値をセットする必要がある。 Public textBoxData() As TextBoxこんな感じ。 日付と時刻のプロパティ 上が 2000、下が 2003。すっごい違和感(^^; 時刻表示、なんで中央揃えにしないんだろう…… と、ようやく気が付いた。2003 のほうは、「午前/午後」部分を表示していないだけなんだ。 XP までは、コントロールパネル「地域」を使うと、「午前/午後」を表示する/しないの設定することが出来る。この場合、2000 のようにその部分が左に詰められる。しかし、2003 の場合は詰めずにスペースを残すようだ。 表示するかしないかはともかく、中央揃えにしろー(><; そんなことにこだわらない人が作ったんだろうなぁ…… わたしは気になる。 ディレクトリ生成 CreateDirectory 関数では出来ない、下階層までのフォルダを一気に生成する方法。例えば、フォーマットしたばかりのフロッピーディスク(A ドライブ)に、\01\001\0001 と \01\002 フォルダを作りたい場合、まず 01 フォルダを作り、001 フォルダを作り……としていかなければならない手順が、一気に出来るので楽である。 Win32API の、MakeSureDirectoryPathExists 関数である。imagehlp.dll に含まれる。 使い方は至って簡単、引数にフルパス名(最後に \ が必要)、これ1つである。成功すれば TRUE、失敗すれば FALSE が返る。 アプリケーションの強制終了用関数 FatalAppExit 関数を用いると、「致命的なアプリケーション終了」(Windows Server 2003 で確認)というメッセージボックスを表示させ、即座にプログラムを終了させることが出来る。 出来るんだけど、どこで使ったらいいか、あんまりイメージできん( ̄▽ ̄; 他プロセスのメモリ内容の取得 ファイルマッピングや DLL での #pragma data_seg ディレクティブを用いると、複数のプロセスでメモリ空間を共有することが出来る。これらの場合は、あらかじめ共有のための仕組みを作っておく必要がある。しかし、デバッグ中に、そのような仕組みが存在せず、それでも他のプロセスのメモリ情報を知りたい、ということがある。その時に使用する関数が、Toolhelp32ReadProcessMemory である。9x, 2000 以降では OK だが、NT 4.0 では使えない。 Toolhelp32ReadProcessMemory 関数には、プロセス ID とそのプロセスが確保したバッファのアドレスなどを指定する。例えばプロセスAが、PID 2048、pbData=malloc(128) →0x04000000 で正常に動作しているとしよう。この時、プロセスBは、Toolhelp32ReadProcessMemory 関数の引数に、2048 と 0x04000000、それに取得したいサイズ(16, 128 など)などを指定すると、プロセスBはプロセスAが割り当てたメモリ空間のデータを取得することが出来るのである。 tlhelp32.h をインクルードする。 ベタで書くなら、こんな感じ。 BOOL fRet; BYTE bData[16]; DWORD dwSize; fRet=Toolhelp32ReadProcessMemory( dwPID, //2048 lpvAddress, //0x04000000 bData, sizeof(bData), &dwSize);ちなみに、5つの引数のうち1つだけが違う、tlhelp32.h のインクルードがいらない(windows.h だけで良い)、かつ NT 4.0 でも使える、ReadProcessMemory という関数がある。読み込みには ReadProcessMemory を、書き込みには WriteProcessMemory を用いることが出来る。 じゃあなんでこっちを先に言わないんだ? って話になる。単に、Toolhelp32 のほうを先に見つけた、ってだけ(ぉ 第1引数を、PID ではなく、OpenProcess で返ったハンドルに変更する。 hProcess=OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwPID); //2048 fRet=ReadProcessMemory( hProcess, lpvAddress, //0x04000000 bData, sizeof(bData), &dwSize);このままでは排他制御がかけられないので、ほんとにデバッグ用にしか使えない。書き込みなんて、怖くて出来ない(^^; グローバル変数を随時チェック、ってだけならまぁ。 Visual Studio でデバッグする時、メモリダンプを表示させて、それを編集できるようになっている。これを使っているのだろう。 個人的に大問題なのは、プロセス ID を取得するのがめんどくさい、ってこと(−−; 例外の取得 C++ や C# では、例外が発生した場合に try, catch, finally といったブロック(中カッコ開くから閉じるまで)を使ってプロセスの強制終了を防ぐことが出来る。Visual Studio の場合は C でも可能で、__try, __except, __finally でそれが可能だ。 明示的に例外を発生させる方法として、throw と RaiseException がある。これらの関数をコールすると、自分で指定した番号の例外を発生させることが出来る。__try { : } __except(1, 1) { : } __try ブロックからすぐに抜けるには、break や return を用いずに、__leave を使う。これにより、正常に __try ブロックを抜けることが出来る。これは、__finally ブロックで呼び出す AbnormalTermination 関数への配慮である。AbnormalTermination 関数は、__finally ブロックでのみ使用することが出来、その __try ブロックを正常に抜けたかそうでないかを判断するものである。__try ブロックを goto や break で抜けると、AbnormalTermination は FALSE を返してしまうだけでなく、スタックフレームの検索に時間がかかってしまう可能性がある。 __except ブロックで例外の識別コードを取得するには、GetExceptionCode 関数をコールする。アクセスバイオレーションやオーバーフローといった戻り値を取得することが出来る。 複数プロセスで DLL のデータの共有 通常 DLL ファイルは、プロセスごとに別々にロードされるので、異なるプロセス同士で同一のグローバル変数(メモリ領域)を使うことは出来ない。しかし、いくつかの手順を踏むことで、異なるプロセス同士でも、同一のメモリ空間を使用することが出来る。 SetWindowsHookEx 関数を用いて動作するフックプロシージャ(フックしたイベントを受け取る関数)は、同一 EXE、同一 DLL にも関わらず別プロセスとなるので、変数の値を共有したい場合はこのテクニックが必須となる。 DLL のデータを共有するための条件は、以下の通りである。 (1)グローバル変数を #pragma data_seg ディレクティブを使用して宣言する。これにより、複数のプロセスで同一のセクション(メモリ空間)(上記例では giFlag と gbData)を使用することが出来、データを共有することが可能となる。マルチスレッドの場合と同様に、別途ミューテックスやクリティカルセクションを用いた排他制御が必要となるだろう。 詳しくは、MSDN Library やみくろそふたーのサイト、「[SDK32] DLL での共有/非共有データの指定方法」や「DLL 内のデータをアプリケーションまたはほかの DLL と共有する方法」を参照。 余談。malloc や MapViewOfFile で取得したバッファのアドレスをグローバル変数にセットしても、他のプロセスではそのバッファを使用できない。共有されているのは、アドレスの値が入っているポインタ変数だけ……勘違いしたことがあるのでとりあえず記述。 ボリュームコントロール Windows が標準で持っている、ボリュームコントロール、というツール。マスターボリューム、Wave 用、オーディオ CD 用、AUX 用といった風に、いろいろ分けて音量を設定することが出来る。 Win32API としてこれらを制御するには、mixerXxxxx という関数を用いる。 mixerOpen 関数でミキサーデバイスを識別するハンドルを取得し、mixerClose 関数でそれをクローズする。具体的に、音量をゲット/セットする手順(関数コール)は、次の通り(コメント,値セット,エラー時処理なんかは全部省略); ------------------------------ UINT ui; HMIXER hMix; HMIXEROBJ hMixObj; MIXERLINE stML={sizeof(MIXERLINE), }; MIXERCAPS stMC={0, }; MIXERLINECONTROLS stMLC={sizeof(MIXERLINECONTROLS), }; MIXERCONTROL stMControl[4]={sizeof(MIXERCONTROL), }; MIXERCONTROLDETAILS stMCD={sizeof(MIXERCONTROLDETAILS), }; MIXERCONTROLDETAILS_UNSIGNED stMCDU={0, }; ui=mixerGetNumDevs(); mixerOpen(&hMix, ui-1, 0, 0, 0); //最後のサウンドカードを指定 hMixObj=(HMIXEROBJ)hMix; mixerGetDevCaps((UINT)hMix, &stMC, sizeof(stMC)); //マスターボリューム // stML.dwComponentType=MIXERLINE_COMPONENTTYPE_DST_SPEAKERS; //音楽 CD ボリューム // stML.dwComponentType=MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC; //WAVE ボリューム stML.dwComponentType=MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT; mixerGetLineInfo(hMixObj, &stML, MIXER_GETLINEINFOF_COMPONENTTYPE); mixerGetLineControls(hMixObj, &stMLC, MIXER_GETLINECONTROLSF_ALL); stMCD.dwControlID=stMControl[x].dwControlID; //やっと取得できる mixerGetControlDetails(hMixObj, &stMCD, MIXER_GETCONTROLDETAILSF_VALUE); //音量セット stMCDU.dwValue=0; mixerSetControlDetails(hMixObj, &stMCD, MIXER_GETCONTROLDETAILSF_VALUE); mixerClose(hMix); ------------------------------mixerGetControlDetails 関数で値を取得するための前準備(関数コール)は、以上で良い。ただし、ここで記述しているのは、関数コールの順番と、引数、必須の構造体のみ。上記以外の構造体への値セットは、長くなるので省略した。 単純な音量変更、たとえば Wave ファイルの再生で音量を変更したい場合は waveOutSetVolume、MIDI ファイルの再生で音量を変更したい場合は midiOutSetVolume、といった関数で変更が出来る。 KEYEVENTF_EXTENDEDKEY キーボードの押下をソフトウェアで行わせる API、keybd_event。MSDN Library を見ると、第3引数に KEYEVENTF_EXTENDEDKEY についての説明がある。 「スキャンコードにプリフィックスバイト 0xE0(224)を追加します。」 これで何を理解しろ、と?(滅) 「走査コードに、 拡張キーを示すプリフィックス バイト0xE0を付加します。」 昔の、NT 4.0 の頃の「Win32 API プログラマーズ リファレンス」には、こう書いてある。まだこっちのほうがわかりやすい。 つまり、拡張キーを使う時には、KEYEVENTF_EXTENDEDKEY というフラグを立てろ、という意味である。 では、拡張キーとはなにか? 現在の一般的なキーボードは、106/109/112 個のキーを持つキーボードである。しかし、昔のキーボードには、106 個ものキーは存在しなかった。例えば、右側の Shift、右側の Ctrl、Ins、Del、カーソル(矢印)、などは存在しなかったのである。こういったものが、拡張キーと呼ばれている。 keybd_event をコールする際、上記のような拡張キーをアップダウンさせたい場合、KEYEVENTF_EXTENDEDKEY フラグが必要である。これがないと、例えば Shift+Home を発生させたとしても、ただの Home キー押下にしかならず、Shift の機能が消えてしまう、といった不具合が発生するのである。 ユーザー名 for Windows XP 以降 コントロールパネルの管理ツールや、コマンドラインから、ウィンドウズにユーザーやグループを追加/削除/名前変更することが出来る。コンピュータ名と同じ名前のユーザーを作ることが、NT や 2000 では OK だったが、XP から出来なくなった。 とはいえ、ユーザー名とコンピュータ名を同じにしたい、という意見はよく聞く。その場合、先にユーザー名を設定(追加や名前の変更)し、その後、そのユーザー名と同じ名前をコンピュータ名として設定する。順番を逆にすると、うまくいく。 Message-ID of Outlook 2003 メールを送信するときに、メールヘッダー部に存在させるレコードのひとつ、Message-ID。それぞれのメールを区別するために付与される、一意の文字列である。この文字列は適当に決めていいので、乱数による文字列だったり時刻文字列だったりすることが多い。 Outlook 2003 では、この Message-ID が付与されない。Message-ID なしでメールが送信されてしまうのである。 メールサーバーやメール送受信用アプリケーション(一般的に言うメーラー)では、Message-ID をチェックして、重複メールを保存しない/表示しないようにする機能がある。当然ながら、Message-ID が存在しないメールでは重複チェックを行うことが出来ず、同じ内容のメールが複数届いてしまう、といった症状が発生する。 なお、RFC のドキュメントのどこに「Message-ID は必須である」といった文言があるかわからなかった…… 必須じゃないのかなぁ? Outlook 2003 のバグ? それとも設定で付けたり付けなかったり出来る? うーん。 「ハードウェアの追加と削除」ウィザード Windows 2000 には存在する、コントロールパネル項目のひとつ、「ハードウェアの追加と削除」。ここで削除のラジオボタンを選ぶと、標準では Windows 起動で読み込まれた表示なデバイスを削除できる。また、チェックボックスにチェックを入れることによって、非表示デバイスを削除することも可能だ。 例えば、新しいグラフィックボードを買ってきて、元々ささっていたそれと換装した場合には、新しいグラフィックボード用のドライバをインストールする。この時、前にささっていたグラフィックボード用のドライバは、削除されるわけではない。単に読み込まれなくなるだけである。デバイスマネージャーを見ても、一覧には表示されないだろう。 デバイスマネージャーの場合、メニューの「表示」、「非表示のデバイスの表示」というチェックを付けることで、読み込まれていないデバイスを表示させることが出来る。先の例の、旧グラフィックボードの項目はこれで表示される。削除ウィザードでも同じで、「非表示のデバイスの表示」というチェックボックスにチェックを付けることで、Windows で読み込まれていないデバイスドライバを一覧に表示させ、削除することが可能である。 2003 になって、このハードウェアの「削除」ウィザードがなくなった。ボタンも「ハードウェアの追加ウィザード」に変わってしまった。過去に存在したデバイスを削除する場合、デバイスマネージャーで非表示デバイスを表示させ、そこから削除する、もしくは Windows をセーフモードで起動し、そこでデバイスを削除する、といったことをしなければならない。 ちなみに、デバイスマネージャーで非表示デバイスを表示させて削除した場合、本当に消えるわけではない。内部的には残っているので、注意である。 Telnet サービスの設定 for Windows Server 2003 tlntadmn.exe を使うことで、Telnet サーバーサービスを起動/終了させたり、設定を変更したりすることが出来る。 例えば、ポート番号を変更する時は、次のように入力する。 tlntadmn config port=23 ところが、このツールはアホで、1024 以上のポート番号を指定すると、 「無効な値です。Port オプションには 1024 より小さい正の数字を指定してください。」 などというメッセージを吐くようになった。うちは、10023 とか使いたいんじゃ!(`□´)Windows 2000 の時は、65535 でもちゃんと設定できたのになぁ……(><; で、しょうがないので、直接レジストリを書き換えて、大きなポート番号でも動くようにする。 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TelnetServer\1.0] REG_DWORD: TelnetPort=23 要サービス再起動。 65523 が OK だったので、他の番号でもいけるっしょ。 めんどくさい。 GUID 付きフォルダ わたしは、プロファイルのスタートメニューの中に「コントロールパネル」を作ってある。 スタートボタンを右クリックして(NT 系では All Users)、開くを選んで、そこに「コントロールパネル.{21ec2020-3aea-1069-a2dd-08002b30309d}」というフォルダを作っている。この GUID は覚えたよ♪(謎) 2000 や Me までは、実際の表示されるときには .{...} の部分は表示されなかったのに、XP や Server 2003 では GUID の部分も表示されるようになってしまった。スタートボタンを押すと、上記の GUID が長々と表示される…… めっちゃ悲しい(;;) 作るのやめた(泣) ちなみにうちは、クラシックモード。余談。 VB5 ランタイム for Windows Server 2003 Windows Server 2003 には、VB5 ランタイムファイルである MSVBVM50.DLL と日本語表示用部品 VB5JP.DLL が存在しない。よって、標準では、Visual Basic 5.0 で生成されたプログラムは正常に動作しないだろう。 フォルダのダブルクリック たとえば、デスクトップにあるマイコンピュータをダブルクリックするとウィンドウが開く。この時、エクスプローラのようなツリーは付いていない。しかし、Shift キーを押しながらダブルクリックするとツリーを表示してくれる。 クイックランチャーのフォルダ IE 4 以降のアクティブデスクトップでは、タスクバーにクイックランチャーが表示されるようになった。ランチャーは、あるフォルダ(...\Application Data\Microsoft\Internet Explorer\Quick Launch)の中身を表示している。 このフォルダに新規にフォルダを作ると、ランチャーにはフォルダアイコンが表示される。それをクリックするとそのフォルダを表示してくれる。 ここで、ランチャーのフォルダアイコンを Ctrl キーを押しながらクリックすると、そのフォルダがオープンされるのではなく、フォルダの中身がリストとして表示される。 デュアルブート for Windows Server 2003 Windows 2000(以前?)と Windows Server 2003 の共存(デュアルブート)には、だいぶ制限があるようだ。 基本パーティション1つを C ドライブ、残りを拡張パーティションとし、論理ドライブを3,4つ区切った(D〜F, G)。こんな環境で。 これを回避するために、以下のことを行う。 1.2000 用と 2003 用の3ファイルを別々に用意する ・NTLDR ・NTDETECT.COM ・BOOTFONT.BIN 3ファイルは起動ドライブのルートディレクトリに存在する。 2.起動前に入れ替える(笑) 起動ドライブが NTFS で、入れ替えを忘れると、Windows 起動し直しとなる(^^; ←一手間 まだ試していないが、基本パーティションを2つ作り、それぞれに独自に OS をインストールすれば、2000 と 2003 は共存できるのかもしれない。 ユーザーとグループの一覧 Windows NT 4.0 リソースキットに含まれるツールのひとつ、addusers.exe を使うと、そのマシンもしくはドメインの、ユーザー名一覧、グループ一覧をコンマ区切りのテキストファイルに出力することが出来る。 会社のサーバーマシン(Windows NT PDC)で試してみたところ、[User] セクションと [Global] セクション(グローバルグループ)、[Local] セクション(ローカルグループ)が出力された。 [コマンド例] addusers /d D:\Users_20030707.txt ただし、全角文字はすべて無視される(汗)例えば、グループ名に「_社員全員A」と付けたとすると、作成されたファイルには「_A」と記述される。 この不具合に対応するため、同じくツールのひとつ、showgrps.exe を使う。引数で指定したユーザーがどのグループに所属しているかを出力できる。通常は画面に表示されるので、> [File Name] を付けることでテキストファイルに出力させることが出来る。 addusers.exe で出力したファイルを読み込んで、[User] セクションに記述されているそれぞれのユーザー名に対して使うと良いだろう。 [コマンド例] showgrps Tsukune >> D:\BelongingGroups_20030707.txt (>> は、ファイルの新規作成ではなく、追加を意味する) お気づきの通り、ユーザー名に全角文字が含まれている場合、役に立たない(汗) もうひとつ、ifmember.exe というツールも、ユーザーとグループに関する処理をしてくれるようだが、よくわからなかった……。 なお、addusers.exe に関しては、みくろそふたーのページから新しいものをダウンロードできる。 ftp://ftp.microsoft.com/bussys/winnt/winnt-public/reskit/nt40/ i386 フォルダに、「addusers_x86.exe」というファイルが存在する。オプション /d:u を代わりに使うことで、ユニコードでファイル出力を行ってくれる。つまり、全角文字であっても問題なし、ということである。 [コマンド例] addusers_x86 /d:u D:\Users_20031128.txt FTP スクリプト Windows が標準で持っているツールのひとつ、ftp.exe。これに、-s スイッチでスクリプトファイル名を付けることで、自動的に FTP コマンドを実行させることができる。 [スクリプトファイル例] open ftp_server user_name pass_word lcd D:\FTP_DATA cd /public_html/cgi-bin/ get access.log quitユーザー名とパスワードをベタに書かなければならないが、この例では、実行ごとに cgi-bin 配下の access.log ファイルをダウンロードしてくれる。サーバー管理する人にはタスクスケジューリングできるので楽かも。 なお、Windows XP 以前の FTP サービスには、パスワード無しのユーザーでもログインできる。その場合、上記の pass_word が空改行となるわけだが、これではうまくいかない。ftp.exe に、さらに -n スイッチを付けて、以下のスクリプトを使う必要がある。 [スクリプトファイル例] open ftp_server literal USER user_name literal PASS(PASS の後ろですぐ改行で良い) :これで接続できるはずだ。 さらに補足。Windows Server 2003 以降の FTP サービスへは、パスワード無しのユーザーではログインできなかった。 Windows ログオン前の設定 Windows NT, 2000, XP といった NT 系 OS では、多くの操作の条件として Windows へのログオンが必須となっている。 ログオン後はそのユーザーアカウントの設定でデスクトップやその他もろもろの表示を行うが、ログオン前はデフォルトのユーザープロファイルを用いて表示を行っている。 [HKEY_USERS\.DEFAULT]:デフォルトのユーザープロファイル [HKEY_USERS\S-1-5-21-678901234-12345678-1234567890-1]:(Aさん) Aさんがログオンすると、 [HKEY_CURRENT_USER] は [HKEY_USERS\S-1-5-21-678901234-12345678-1234567890-1] の エイリアスとなる。 (なお、レジストリ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList を見ると、一覧が存在する) 上記のレジストリキー配下を変更することで、各ユーザープロファイルに変更を加えることが出来る。Aさんの場合は、ログオンすればコントロールパネルや各種プロパティ画面から設定を変更することは可能だが、ログオン前の状態は、レジストリエディタやツールを用いて変更しなければならない。 ここでは、背景色の設定を変更する例を取り上げる。 [HKEY_USERS\.DEFAULT\Control Panel\Colors] REG_SZ: Background=xxx xxx xxx 赤,青,緑の順にそれぞれ 0 〜 255 の数値を3つセットする。背景色を赤にしたいなら「255 0 0」と入力すれば良い。 その他、簡単に書き換えられるだろうものとして、 [HKEY_USERS\.DEFAULT\Control Panel\Desktop] REG_SZ: ScreenSaveTimeOut=900 (スクリーンセーバー開始までの時間 [秒]) REG_SZ: SCRNSAVE.EXE="logon.scr" (スクリーンセーバー実行ファイル) [HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders] REG_EXPAND_SZ: Recent="%WINDIR%Recent" (全ユーザーの「最近使ったファイル」ショートカットファイルを同じ場所に保存する) といったものがある。 また、マウスポインタの影について、次に記述した。 マウスポインタの影 コントロールパネルの「マウス」で、マウスポインタに関する設定をすることが出来る。Windows 2000 や XP などでは、ポインタに影を付けるかどうかのチェックボックスが存在する。2000 では「ポインタの影を有効にする(E)」とあった。 しかし、ここで設定できるのはあくまでログオンした後の状態であり、ログオン前のマウスポインタの影を有効/無効にすることではない。 レジストリの値を変更することによって、ログオン前の影の有効/無効を変更することができる。 [HKEY_USERS\.DEFAULT\Control Panel\Desktop] REG_BINARY: UserPreferencesMask=xx xx xx xx バイナリ型4バイトの変数である。このうち2バイト目の上位から3ビット目、0x20 フラグを立てる/落とすことで、影が有効/無効になる。 例えば、0x00 であれば、影が無効になっているので、0x20 とすることで有効になる。 例えば、0x3e であれば、影が有効になっているので、0x1e とすることで無効になる。 ファイルの共有 CreateFile 関数を用いて誰かがオープンしているファイルを、他のプロセスから参照したい場合がある。サービスが常時オープンしているログファイルをデバッグの時にテキストエディタで開きたい、といった場合だ。 もともとのオープンで FILE_SHARE_READ フラグが付いていれば、他プロセスから同じファイルをオープンすることが出来る。 //まずオープン hFile_A=CreateFile( cFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); //例:OK hFile_B=CreateFile( cFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); //例:NG hFile_B=CreateFile( cFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL);共有フラグ(FILE_SHARE_xxxxx)の引数は、先のオープンと一致しなければならない。 片通信発生 パソコン2台で通信しようと思ったところ、ネットワークアダプタを有効にした直後はしばらく通信できて、その後突然切断されることがあった。パソコンAから IP アドレスBへの ping は正常で、パソコンBから IP アドレスAへの ping がタイムアウトする、という現象だった。 原因は、IP アドレスBが、別の場所で使われていたこと( ̄▽ ̄; まさか、IP アドレスの重複だったとわ…… 知らない間に、ルータ周りの IP アドレスが変更されていた。管理者以外が勝手に変更するの、やめてくれ(汗) Exchange Server の1行折り返しバグ Exchange Server 5.5 に対して Outlook を使ってメールを送信すると、変なところ(デフォルトは 72 バイト;半角で72文字)で勝手に改行されて、メール本文が読みづらくなることがある。 会社で使った文章そのものを持ってきた。やっぱり、見づらい(><;例: ---------- 2003年 4 月 7日 何か長い文章を打ち込んだら、うんちゃらかんちゃら……とな ることが わかりました。 また、下記のように引用を繰り返すと、大変なことになります。 > > > > > > > xxです。 > > > > > > > 1.xxxについて > > > > > > > 何も考えずに、「情報は必要だ」と引用 > > しよ > > > > うと > > > > > > して > > > > > > > いた > > > > > > > 人のメールを、ここでゆっくり見てみ > > ます > > > > 。 > > > > > > > 情報は必要かもしれませんが、見づらい > > こと > > > > この > > > > > > > 上ないです。 > > > > > > > しかも、Outlook 同士でやりとりしてい > る > > 場 > > > 合 > > > > > > > には見られないことがある現象です。 > > > > > > > > > > > > > > かなりの人がこの現象を知っており、今更 > > > > > > > 報告する必要もないかと思いますが…… > > 。 以 上。 ---------- ちなみに、みくろそふたーは、Exchange Server 5.5 の SP2 (SP3) の既知のバグである、と言ってるけど、SP4 でも直ってない。IMAP4 Line Wrap Size を、80 にしたり 100 にしたり 200 にしたり……いろいろ試したんだけどなぁ。無駄だった。 DLL ファイル作成 Visual Studio 6.0 で DLL を作成しようとして、関数を外に見せるにはいくつかの方法がある。MSDN Library には以下のように記述されている。 定義をエクスポートする方法には、次の 3 とおりの方法(推奨順)がある。 1.ソース コードでキーワード __declspec(dllexport) を使う。 int __stdcall GetData(int iRecordNumber); ↓ __declspec(dllexport) int __stdcall GetData(int iRecordNumber); とする。 2..DEF ファイル内で EXPORTS 文を使う。 関数定義は上の例の先のままで(__declspec(dllexport) を付けないで) .def ファイルに LIBRARY SAMPLE_XX EXPORTS GetData GetDataAlias=GetData GetDataSousyoku=_GetData@8 のように記述する。 3.LINK コマンドで /EXPORT オプションで指定する。 (やったことないので例なし)確かに、.h ファイルに関数定義を書いて、.lib ファイルをリンクすると上記の関数はコールできる。しかし、1の方法には問題がある。2の方法では発生しなかった問題だ。1の方法では、LoadLibrary と GetProcAddress で、.dll ファイル内の関数アドレスを取得することが出来ないのである。GetProcAddress の戻り値が NULL となる。想像で、VB の Declare Function や VS.NET の [DllImport(...)] で関数を使おうとしても同じかもしれない。 原因を追及するつもりはなかったのだが、ひょんなことから、原因がわかった。 1の方法の場合、GetProcAdderss への引数には、関数名を書いても取得できない。それは、Windows が別の名前のみを外部に見せているからだ。 例えば、上記の関数で DLL を生成したとしよう。その場合、関数名は GetData なのだが、外部に見せる名前は、_GetData@4 となる。アンダーバーが一つ付き、また @ の後ろに引数のバイト数が付く。引数が int 型2つだった場合は @8 になる、といった感じだ。よって、GetProcAddress への第2引数には、"GetData" ではなく "_GetData@4" を記述すれば良いということになる。 わたしがコーディングする時は、1と2と、両方書いている。 MCI コマンド mciSendString に渡す文字列を、よく使うものだけ適当に列挙してみた。 命令を送るのは簡単だけど、それをどうやって使うかが問題。open file.wav close file.wav open "sample file.mid" alias midialias play midialias pause midialias resume midialias open x: type cdaudio play x: close x: open n: type cdaudio alias cdn set cdn time format tmsf set cdn time format milliseconds set cdn door open set cdn door closed status cdn mode status cdn length track 2 status cdn type track 1 open VideoFileName status VideoFileName position seek VideoFileName to start capability VideoFileName has video capability VideoFileName can play play で notify を付ける(例:play midialias notify)と、停止時に、指定したウィンドウハンドルに MM_MCINOTIFY メッセージをポストしてくれる。wParam に停止理由が入っていて、最後まで再生したときは MCI_NOTIFY_SUCCESSFUL、stop や seek、close などで強制停止されたときは MCI_NOTIFY_ABORTED が入る。pause では MM_MCINOTIFY は発生しない。 なお、ウィンドウハンドルは、mciSendString の第4引数にセットする。 ……あれ? ボリュームコントロール(音量調整)ってどうやるんだったか忘れた(汗) (前にどこかで書いたかな?)スレッドをまたがっての命令発行が、9x 系では OK だけど NT 系ではダメとか、同じく NT 系で、スレッドをまたがっての子ウィンドウへの動画表示(open sample.avi style child parent HWND)がフリーズするとか、いろいろ試してみないとちゃんと動作するかはわからない。 見たまんまだけど、ファイル名に半角スペースが含まれている場合は、GetShortPathName や FindFirstFile なんかで短いファイル名を取得するか、ダブルクォーテーション "" で囲っておかないといけない。 GetMessage Win32API GetMessage。今まで特に何も考えずに(1)としていたが、(2)を採用すべきである。 (1)まずい例 while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } (2)まずくないだろう例 while(TRUE) { switch(GetMessage(&msg, NULL, 0, 0)) { case 0: //終了(正常) break; case -1: //終了(異常) break; default: //正常取得 TranslateMessage(&msg); DispatchMessage(&msg); continue; } break; }これは、GetMessage の戻り値が -1 となる場合があることに対応したものである。 Exchange Server メールボックスのリソース 管理ツールで、各メールボックスにどれくらいデータが保存されているかの一覧を表示することが出来る。このリソース表示で、メールが1通も存在しなくても0とならない場合がある。 Outlook を使ったことがあれば、そこに「履歴」や「連絡先」といったサブフォルダが存在することを知っているだろう。このようなデータも、メールボックスのリソースとして保存されている。特に、長い間 Office アプリケーションを使っている人なら、一度「履歴」を見てみると良いだろう。過去に使用したワードドキュメントやエクセルドキュメントのファイル名が、ずらーっと並んでいるかもしれない。 管理上、メールボックスにはサイズ制限を設定してある場合が多い。有効にメールボックスを利用するために、たまには不要な情報を削除すると良いだろう。 フォントサイズ変更 Internet Explorer や、そのシステムを使ったもの(例:オンラインヘルプ)のフォントサイズを簡単に変更する方法。 Ctrl キーを押しながら、マウスのホイールを回転する。これで、フォントサイズを、最小〜中〜最大、変更させることが出来る。ツールバーのメニュー「表示→文字のサイズ」をわざわざ選択しなくて済む。 Vista 以降では、IE 7 が標準である。わたしはここでも、Ctrl キー押しながらホイールころころをやってみた。 表示パーセントが変わった(汗) IE ウィンドウの右下に、表示パーセンテージ(100 %)とか表示されていて、これが 90 % とか 110 % に変わったのだ。つまり、文字サイズが変わるのではなく、表示イメージ全体が拡大/縮小されたのである。 NetBIOS 名エイリアス NT 系 OS でコンピュータ名のエイリアスをネットワークに公開する方法。 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters] REG_MULTI_SZ: OptionalNames="OtherName" REG_MULTI_SZ なので、いくつでも登録できる。1個だけなら REG_SZ でも OK。再起動が必要。ここで登録した名前が、「ネットワークコンピュータ」一覧に表示されるようになる。 これを使うとそのマシンで共有しているプリンタを他で使えなくなるらしい。試したことはない。 DDNS 非対応 Windows 2000 Server ファミリーが持つ機能のひとつ、Dynamic Domain Name System、DDNS。BIND 8 以降は DDNS に対応している。 Windows 2000, XP は、TCP/IP の設定で、標準で「この接続のアドレスを DNS に登録する」チェックボックスにチェックが付いている。 古い DNS システムを利用する際には、上記チェックボックスをオフにすると不要なトラフィック(DNS クライアントが自コンピュータ名を登録しようと要求する→DNS サーバーがエラーを検出する→クライアントが再送する)が避けられる。 ログオン画面 スクリーンセーバーファイル「logon.scr」について。Windows にログオンする前の画面じゃなくって、スクリーンセーバーのタイトルが「ログオン画面」(^^; Windows XP Professional に入っていたこのファイルを他の Windows(NT 系)で起動してもスクリーンセーバーが開始されたので、他の OS のものでも試してみた。 OS: Windows NT 4.0 Server Windows 2000 用を起動すると「Windows 2000 Professional」と、Windows XP 用、Windows 2003 用を起動すると「Windows XP Professional」と表示された。 OS: Windows 2000 Professional Windows NT 用は NT のもの、Windows XP 用を起動すると「Windows XP Professional」と表示された。 OS: Windows 2000 Server/Advanced Server Windows NT 用は NT のもの、Windows XP 用を起動すると「Whistler Server」「Whistler Advanced Server」と、Windows 2003 用を起動すると「Windows Server 2003 Standard Edition」「Windows Server 2003 Enterprise Edition」表示された。 OS: Windows XP Professional Windows NT 用は NT のもの、Windows 2000 用を起動すると「Windows 2000 Professional」と表示された。NT 用のものは、ウィンドウの中の文字の位置が少し右下にずれていた。 OS: Windows 98 Windows NT 用は(VB ランタイムの問題か?)文字化けしたけど起動した。Windows 2000 用、Windows XP 用のものは、実行しようとすると強制終了メッセージが表示された。残念。 OS: Windows NT 4.0 Workstation Windows 2003 用を起動すると「Windows XP Professional」と表示された。 |
証明書サービスのインストール時 自分のマシン Windows 2000 Advanced Server に証明書サービスをインストールしようとしたら、「IIS サービスを止める必要があります」と言われた。 「え〜、じゃあこのマシンの Web サービスと FTP サービス、別のツール使わなきゃいけないな〜」と読み取り、その設定して、翌日。 IIS は普通に動いていた(汗)どうやら、証明書サービスのインストール時だけ、IIS が停止した(すぐ再開した)らしい。 悲しかった。 考えてみると、証明書サービスは何のためにインストールするの? ってのを知らずにやってたわけだな…… いつもこんな感じです( ̄▽ ̄; MCP Web ページ MCP の Web ページで、個人情報を更新しようと思った。別に取り立てて更新しなければならないきっかけがあったわけではない。 ……このページは、個人情報をネットワークに流させるくせに、SSL で暗号化してないんかい、「ご登録情報の変更」ページ。 ……変更したら、 「ご登録情報の更新には1週間程度必要となります。ご了承ください。」 ……ナンデスト? 自動じゃなくて、メール見て手動ってことか……? どんな管理システムやねん!(`□´) 特定の URL にアクセスできない Windows 2000 Server ファミリーで SP3(Service Pack 3)を当てると、特定の URL へのアクセスが失敗するようになった。サービスパック1,2のマシンでは不具合は起きていない。Intenet Explorer 5.01 SP3, 6.0 SP1 のそれぞれで発生したことから、バージョンの問題ではなさそう。「SP3 のせいだ!」という断定ではなく、その可能性がある、ということで。 イントラネットにある Web サーバーにアクセスするため、Internet Explorer のプロキシに関する設定「例外」で、*.kaisya.local といったアドレスを記述すると、アクセスが失敗する。プロキシを使わない、直接アクセスの時に発生するようだ。Linux プロキシを通すと正常にアクセスできた。 なお、Web サーバーにどんな設定が為されているのかは、教えてくれないのでわからない( ̄▽ ̄; プロセスを起動しているユーザー NT 系 OS には、誰がどのプロセス(アプリケーション)を起動したか、という情報が存在する。Windows 2000 でターミナルサービスが起動している場合、また Windows XP の場合は必ず(デフォルトで?)、タスクマネージャーのプロセス一覧にユーザー名を表示することが出来る。ターミナルサービスが開始されていない Windows 2000 や Windows NT の場合、リソースキットに含まれているツール PULIST.EXE を使うことによって取得が可能である。 Inet コンポーネント on VB(2) VB で、Microsoft Iternet Transfer Control を利用して HTML フォームのデータを PUT or POST する方法。 ---------- Private Sub Form1_Load() Inet1.Execute "http://server/cgi-bin/xxx.cgi", "POST", "Data=123" End Sub ---------- Private Sub Inet1_StateChanged(ByVal State As Integer) If State = icResponseCompleted Then MsgBox Inet1.GetChunk(4096) End If End Sub ----------たいてい CGI スクリプトに PUT or POST すると HTML データが返ってくるので、それを取得する。 Outlook Express 6 添付ファイルが削除される 添付ファイル付きのメールを受信し、その添付ファイルをチェックしようとクリップのアイコンをクリックすると、添付ファイルが開けないことがある。これは、OE6 の設定によるものである。 「ウイルスの可能性がある添付ファイルを保存したり開いたりしない」という面白いチェックボックスがある。いったい「ウィルスの可能性がある添付ファイル」というのは、どんな基準になっているんだろう……? ・圧縮ファイル(.lzh, .zip など) ・実行形式関係(.exe, .com, .pif, .cmd など) ・Office ドキュメント(笑) みくろそふたーは、みくろそふたー製品の .doc やら .xls なんかを「ウィルスの可能性がある」ファイルとして認識しているのかなぁ? してなきゃおかしいよねぇ……(=〜= IE6 のバグ(フォルダ切り取り or コピー→貼り付け) あるドライブのフォルダをコピー or 切り取りし、別のフォルダへそれを貼り付けようとすると、必ず「フォルダの上書きの確認」が出るようになった。たぶん、新規にフォルダを作成しているにも関わらず、さらにフォルダ存在チェックをしているんだろう…… ほんとバカじゃのう。 ちなみに、「簡単エクスプローラ拡張」というフリーウェアをインストールしても発生するらしい。わたしは、これを使っていないけど発生する(汗) Exchange Server 転送時に From 行勝手に書き換え Exchange Server のあるメールボックスには、次のような情報がセットされている。 例:表示名:響 筑音 SMTP: tsukune@exchange.sample smtp: tsukune@mail.exchange.sample クライアント筑音がメールを送信しようと考えた。メーラーには自分の設定をする。メールサーバーへの電文には、メールヘッダー From 行として、自分の設定したものがくっついた。 例1:「Tsukune」メールアドレス:tsukune@exchange.sample From: Tsukune <tsukune@exchange.sample> 例2:「つくね」メールアドレス:tsukune@mail.exchange.sample From: つくね <tsukune@mail.exchange.sample> しかし、いざ送信してみると、宛先の人には上記の From 行では届かなかった。2例いずれの場合も From 行には Exchange Server に登録されている情報がセットされていた。 例: From: 響 筑音 <tsukune@exchange.sample> (表示名+SMTP) (全角文字の部分は =?iso-2022-jp? うんちゃらかんちゃら、となった) 会社だからいいけど、もしこんなメールサーバーを使っているプロバイダがあったら。 プロバイダがメールサーバーに個人情報(少なくとも本名)をセットし、クライアントがそれを知らずにメールを投げたら、相手先には自分の本名が送信される。 すんごいイヤだわ(−−; なお、Exchange Server 5.5 の設定としては、以下の場所のチェックボックスで行う。 条件:Internet Mail Service が作成済みである ツリーの「接続」→「Internet Mail Service(MachineName)」のプロパティ→「インターネットメール」タブ→「詳細オプション」ボタン→「インターネットへの表示名の送信を無効にする」 チェックを付けると、From 行の「表示名」の部分は、勝手に空っぽにされる。 勝手に。 例: From: tsukune@exchange.sample ひどいよね(何 Ctrl+Alt+Delete NT 系 OS では、Ctrl+Alt+Delete は特殊な意味を持つ。Windows にログオン/ログオフしたり、ワークステーションロックをかけたりすることが出来るようになる。 キーボード操作 API keybd_event では、この特殊なキー操作を行わせることが出来ない。これはセキュリティを考慮したものとなっている(簡単にソフトウェアで押下させられないようにするため)。これを行わせるためには、GINA(Graphical Identification and Authentication)という OS が持つ DLL ファイルを利用するか、もしくは PostMessage を用いる。 ここでは後者について述べる。 まず、"Winlogon" というデスクトップハンドルを取得する。そしてそのデスクトップを現在のデスクトップとし、そこで PostMessage をコールする。すると、Ctrl+Alt+Delete を押したことになる。 0.CreateThread(以下は新規スレッド内の処理) 1.GetCurrentThreadId 2.GetThreadDesktop 3.OpenDesktop( TEXT("Winlogon"), 0, FALSE, ...); 4.SetThreadDesktop 5.CloseDesktop Winlogon デスクトップハンドルを選択し、 古いデスクトップハンドルをクローズする。 6.PostMessage( HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)); 7.SetThreadDesktop 8.CloseDesktop 元のデスクトップハンドルを選択し、Winlogon デスクトップハンドルをクローズする。なお、通常のアプリケーションとして OpenDesktop 関数をコールすると「アクセス拒否」エラーを返す。セキュリティで許可されている NT サービス上でのみ動作する。コントロールパネルのサービスより、目的のサービスのプロパティを開き、「デスクトップとの対話」を許可してあげる必要がある。 ちゃんと Winlogon デスクトップをクローズしてあげないと、ログオフもシャットダウンも出来なくなる。「ログオフもシャットダウンも出来なくなる」以外の状態に遭遇していなかったが、何故か知らないけどうまくいくようになった(汗) ワークステーションロックのみであれば、LockWorkStation で可能(Windows 2000 以降)。 Exchange Server をインストールするコンピュータ名 Windows に「POST」というコンピュータ名を付けたら、そのマシンにインストールした Exchange Server が正常に動かなかった(^^; 他にも使えないコンピュータ名ってあるかも。GET とか USER とか……? 使用中ポート番号 TCP/UDP で使用中のポート番号の一覧を取得する。DOS コマンド「netstat -a」と同じ動きになる。MSDN Library には、Windows 95 と Windows NT SP3 以前は未対応、とある。要 Iphlpapi.dll。 余談だが、Iphlpapi は IP Helper API の略だとある。また、Iphlpapi.h と Iphlpapi.lib は、VS.NET 開発環境の Vc7\PlatformSDK 配下にあったのでそれを使った。 DWORD dwCount; DWORD dwSize; unsigned char ucData[16384]; PMIB_TCPTABLE pstTCPTable=(PMIB_TCPTABLE)ucData; PMIB_UDPTABLE pstUDPTable=(PMIB_UDPTABLE)ucData; dwSize=sizeof(ucData); if(GetTcpTable(pstTCPTable, &dwSize, TRUE)==NO_ERROR) { for(dwCount=0; dwCount<pstTCPTable->dwNumEntries; dwCount++) { pstTCPTable->table[dwCount].dwLocalPort= (pstTCPTable->table[dwCount].dwLocalPort%256)*256 +pstTCPTable->table[dwCount].dwLocalPort/256; pstTCPTable->table[dwCount].dwRemotePort= (pstTCPTable->table[dwCount].dwRemotePort%256)*256 +pstTCPTable->table[dwCount].dwRemotePort/256; if(pstTCPTable->table[dwCount].dwState==2) pstTCPTable->table[dwCount].dwRemotePort=0; printf("TCP %x:%d\t%x:%d\t%s\r\n", pstTCPTable->table[dwCount].dwLocalAddr, pstTCPTable->table[dwCount].dwLocalPort, pstTCPTable->table[dwCount].dwRemoteAddr, pstTCPTable->table[dwCount].dwRemotePort, cState[pstTCPTable->table[dwCount].dwState]); } } dwSize=sizeof(ucData); if(GetUdpTable(pstUDPTable, &dwSize, TRUE)==NO_ERROR) { for(dwCount=0; dwCount<pstUDPTable->dwNumEntries; dwCount++) { pstUDPTable->table[dwCount].dwLocalPort= (pstUDPTable->table[dwCount].dwLocalPort%256)*256 +pstUDPTable->table[dwCount].dwLocalPort/256; printf("UDP %x:%d\r\n", pstUDPTable->table[dwCount].dwLocalAddr, pstUDPTable->table[dwCount].dwLocalPort); } }ポート番号はビッグエンディアンで取得される。TCP で「dwState=2(LISTEN)」の時は dwRemotePort にゴミデータが入っている。 なお、手元にあるフリーウェアの「FPort」を使うと、どのポート番号がどのプロセスで使われているかを表示させることが出来る。http://www.foundstone.com/ で入手可能。 CPU が複数あるマシン NT 系 OS では、タスクマネージャーを使って、実行しているプロセス一覧を表示することが出来る。そのウィンドウでポップアップメニュー「関係の設定」を選ぶと、マシンに搭載されている CPU のうち、どれを使って処理させるかを選択できるようになっている。デフォルトはすべての CPU。 CPU 0+CPU 1 から CPU 1 だけにすると…… ↓ hProcess=GetCurrentProcess(); fRet=GetProcessAffinityMask( hProcess, &dwProcAffinityMask, &dwSysAffinityMask); fRet=SetProcessAffinityMask( hProcess, dwProcAffinityMask);これで、そのプロセスがどの CPU を使っているか、また複数ある CPU のうちどれを使って処理するかを設定できる。 シングルプロセッサなら常に 1、デュアルプロセッサなら 1+2 =3、CPU 4つなら 1+2+4+8 =15 など。 常に CPU 負荷の高いプロセスを片方にだけ割り当てる、というのが一般的だと思っている。思っているだけ。デュアルで後ろだけに割り当てるなら、0+2 =2 をセットする。 なお、SQL Server や Exchange Server のように、常に CPU パワーを必要とするサーバー系アプリケーションの場合は、メインとなる作業(のプロセス)を後ろ側の CPU に割り当てると良い(らしい)。 二重起動防止 自分でプログラムを作る時に、ダブルクリックしすぎや、リソースの同時使用を防ぐために、二重起動をチェックすることがある。二重起動の場合には、先に動いているほうにフォーカスを移動したり、そのプログラムを終了するためのメッセージを出したり、といったことが考えられる。 よく言われているのが、FindWindow でクラス名タイトル名をキーにウィンドウハンドルを取得する方法。ウィンドウが表示されるプログラムでは一見良さそうに思えるが、これだと、起動に時間のかかるプロセスでは二重起動になる可能性がある。そもそもウィンドウのないプログラムだったらお話にならない。 WinMain の最初で、ファイルマッピングオブジェクトやミューテックスを作っておく。ALREADY_EXISTS みたいなのが返ってきたら二重起動ってことにする。 NT サービスの名前 Workstation サービスや Server サービスは、lanmanworkstation, lanmanserver という名前である。レジストリの [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services] で、「キー一覧≒サービス一覧」だと思えば良いみたい。少し違う。 ちなみに、lanmanxxx は、parameters キーで AutoShareWks, AutoShareServer を使ったことがあった。下記参照。 隠し共有 NT 系 OS のデフォルトの管理用隠し共有(C$ とか Admin$ とか)を有効にしたり無効にしたりする。 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters] REG_DWORD: AutoShareWks=0 (Workstation 用) REG_DWORD: AutoShareWks=0 (Server 用) デフォルトは 1。 アホなインストーラーは、必ずこれ(C$ や Admin$)を使うように設定されていて、「パスが見つかりません」エラーでインストールが中断される。何がアホ、とは言わないけど…… ソフトは悪くない、それを作ったヤツが悪いんだ(ぉ CreateProcess アプリの起動(プロセス起動)のお約束。 CreateProcess コール前に、STARTUPINFO 構造体と PROCESS_INFORMATION 構造体を ZeroMemory(memset 0)でクリアしておく。また、STARTUPINFO 構造体の cb には、構造体のサイズを設定する必要があるので、 STARTUPINFO stStartupInfo={sizeof(STARTUPINFO), }; こんな感じの初期化がいいかも。 引き継ぎ情報が必要なければ、CreateProcess の後ですぐ、PROCESS_INFORMATION 構造体の hThread と hProcess を閉じる。 CloseHandle(stProcInfo.hThread); CloseHandle(stProcInfo.hProcess); 起動したプロセスが終了するのを待つのであれば、上記の hProcess を WaitForSingleObject 関数への引数として使う。 シャットダウン for Windows Server 2003 シャットダウンや再起動のウィンドウに、「シャットダウンの理由」というものが追加された。そこで指定したものが、イベントログに出力される、という仕様である。 なるほど、と思ったが、手順が1つ増えてめんどくさい( ̄▽ ̄; Windows 付属のコマンド、shutdown.exe を使って引数に適当な値をセットすれば、その .cmd ファイルを叩くだけで終了処理を走らせることが出来る。 以下は、shutdown.exe ヘルプの抜粋。 ---------- /d [p:]xx:yy 再起動またはシャットダウンの理由を指定します p は再起動またはシャットダウンが計画されていたことを示します xx は主因の番号です (255 以下の正の整数) yy は副因の番号です (65535 以下の正の整数) ---------- イベントログへの出力が必要ない場合(Windows XP は標準でこっちの設定になっている)は、XP までと同じ終了ウィンドウを表示させることが出来る。ただし、ポリシーの変更が必要である。 コンピュータの構成 →管理用テンプレート →システム ここにある「シャットダウン イベントの追跡ツールを表示する」を「無効」にすれば、終了ウィンドウは変更される。 Windows シャットダウン シャットダウンツール shutdown.exe では、電源まで落ちない。その代わりに、みくろそふたーからダウンロードで入手できる restart.vbs という VBScript を使うと良い。コマンドラインは簡単で、 cscript restart.vbs /S <MachineName> /P である。 なお、2000 以降(?)の Windows Management Instrumentation (WMI) が必要で、NT 4.0 では動かない。 9x 系では、 rundll32 shell32.dll,SHExitWindowsEX 1 でシャットダウンとなる。なお、1 の意味は次の通り(16 以外は全 Windows 共通) 0: ログオフ 1: シャットダウン 2: 再起動 4: 強制終了シャットダウン 8: 電源断 16: 強制終了シャットダウン(Windows 2000 以降) このあたりは、MSDN Library の ExitWindowsEx を見れば詳細がわかる。かなり細かく書いてある。 わけわからずに 4 以上使っても、そんなんしらん(〜へ〜 プロセス ID NT 系 OS では、タスクマネージャーにプロセス一覧を表示している。 プロセス ID がわかると、例えば、プロセス強制終了ツール Kill.exe を、PID 指定で使うことが出来るようになる。取得の手順は次の通り。要 PSAPI.DLL。 ---------- 1.EnumProcesses PID 一覧取得。 DWORD dwPID[256]; EnumProcesses(dwPID, sizeof(dwPID), &dwSize); //dwSize ← 取得した PID の個数 * sizeof(DWORD) 2.OpenProcess 各 PID のプロセスハンドルを取得。 HANDLE hProcess; hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID[iLoop]); (CloseHandle(hProcess) を忘れないように!) 3.EnumProcessModules モジュールハンドルを取得。 HMODULE hModule; EnumProcessModules(hProcess, &hModule, sizeof(hModule), &dwSize); //dwSize ← 取得したモジュールハンドルの個数 * sizeof(HMODULE) 4.GetModuleFileNameEx, GetModuleBaseName ファイル名(フルパス or ファイル名のみ)を取得。 char cFileName[MAX_PATH]; //フルパス GetModuleFileNameEx(hProcess, hModule, cFileName, sizeof(cFileName)); //ファイル名のみ GetModuleBaseName(hProcess, hModule, cFileName, sizeof(cFileName)); 5.(繰り返し) PID とファイル名のマッピングが完了する。 ----------ここから各 PID に対して処理を開始する。めんどくさいなぁ。 Windows 95 では CreateToolhelp32Snapshot(), Process32First(), and Process32Next() で取得できる。ところが、Windows 98 ではこれが正常に動作しない。理由は知らないけど。MSDN Library に、Windows 95, 2000 以降、と書いてあるので未対応なんだと思う。 ちなみに、Windows 98 では、EnumWindows でウィンドウハンドルを取得して、それから GetWindowModuleFileName を使ってプロセス名、GetWindowThreadProcessId を使ってプロセス ID を取得してみた。Windows 98 のプロセス ID (PID)は、NT 系 OS のように 0 から始まるのではなく、0xFFxxxxxx という変な値だった。 マニフェスト 実行ファイルごとに、どのバージョンの DLL ファイルを使うか選択できるようにすること、またその設定ファイル。 Windows XP では、「実行ファイル名+.manifest」がマニフェストファイル。Windows 2000 では……ぺけぴーと同じかどうか知らない(でも、存在自体は Microsoft の技術者から聞いた)。XML 形式で、DLL ファイルのバージョン番号を記述している。 サンプルは、MSDN のページに書いてあるので検索すれば見つかる。 似たようなものに、「実行ファイル名+.config」というのがある。これはアプリケーションの構成ファイルなんだそーだ。 XML1.0 の決まりで、テキストファイルは UTF-8 でないとダメという。2000 とぺけぴーのメモ帳は標準で対応してるらしい。 例でしか見たことないし、自分で触ったことないんだわ(汗) FindWindow と EnumWindows NT 系 OS では、タスクマネージャーにプロセス一覧を表示している。 Windows XP 以降では、複数のユーザーの同時ログオンが可能なので、どのユーザーがどのプロセスを起動しているかがわかるようになった。ただし、Win32API の FindWindow 関数では、他のユーザーが起動したウィンドウハンドルを取得することが出来ない。 試したわけではないが、EnumWindows 系関数で取得できるのではないかと思う。ユーザー名を受け取らなければならないので、他にぺけぴー専用の関数があるのかもしれない。 ブラウザでのフォントの大きさ <TABLE> タグやら <PRE> タグやらの中のフォントサイズ、中と外で違う。見た目、なんか気分悪いなぁ。 スタイルシートでサイズを指定すると(例:font-size: 10pt)、ブラウザで表示フォントサイズを変更しても大きさが変わらなくなる。Internet Explorer だと、最小、小、標準、大、最大、と変えられるが、まったく変わらない。 わたしは目が悪いので、小さい文字で固定されると腹が立つ。ブラウザの設定で、スタイルシートやフォントサイズ指定を無効にすれば、問題なく大小できる。 テキストボックスへのキー押下 for VB5 Text1_KeyPress の処理の中で「KeyAscii=xx」と引数の値を変えてあげると、そのキーコードが押されたことになる。 Sub Text1_KeyPress(KeyAscii As Integer) If KeyAscii < 48 Then KeyAscii = 0 '! " # といった記号をはじく If KeyAscii > 64 And KeyAscii < 64 + 25 Then KeyAscii = KeyAscii + 32 End If 'アルファベットをすべて小文字にする End SubKeyAscii = 8 にしたらフォーカス移動するかなー、と思ったけど、しなかった。残念。 IE6 からの仕様変更(クッキー) タブ \t が、認識されなくなった! なんで全部くっつくねん!(`□´) UserName\tMailAddress なんてふうにクッキーに書いてたのに、\t がなくなってくっついて、めちゃくちゃ…… むきー。\t の代わりに、なんかデリミタ考えなきゃならんやんかー 世の中のクッキーを見たところ、改行 \n をデリミタとしているものがあった。これでうまくいくんだ、といったところ。 デバッグ from タスクマネージャー タスクマネージャー(NT 系 OS)で、「デバッグ」を選ぶとワトソン博士が関連付けられてたり、Visual Studio が開いたりする。これは、レジストリに設定がある。 [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug] REG_SZ: Debugger="...\Bin\msdev.exe"" -p %ld -e %ld" REG_SZ: Auto="0" REG_DWORD: UserDebuggerHotKey=0 Auto=1 にすると、プロセスが落ちた時にデバッガが起動する。UserDebuggerHotKey は ホットキーの指定で、0(デフォルト)は F12 キー。キーコードなので、13 にすれば Enter キー、145 にすれば ScrollLock キー、19 にすれば Pause キー、と変更可能。あ、NEC PC98 シリーズに Pause キーって割り当て、無いわ。 余談。DirectDraw の、ウィンドウ/フルスクリーンモードの切り替え(自作プログラム)に最初 F12 を使っていたのだけど、変な動きしたので変えた。 Visual Studio .NET(2) C# コーディング始めたばっかの頃に思ったこと。 ・#define でマクロが定義できない(シンボルのみ) いつも使ってた #define ERROR_OK 0 #define KIND_INT 1 等が出来ない。 ・#ifdef, #ifndef がなくなった #if のみで「定義されているか?」を意味する。 #if _DEBUG_ #endif と書く。 ・#include が使えない ヘッダーファイルが存在しない…… ・typedef が存在しない 定義できない。値定義が出来なくなったので、今までやってたエラーコード定義は、クラス内変数(固定)に変えた。 private const int Sample_Error_None =0; 外部から見えるようにするならば、public にし、また static を付けたりしても良いだろう。 WinSock コンポーネント VB で、簡単に TCP/UDP 通信をするコンポーネント Microsoft WinSock Control の単純な使い方。 ---------- Private Sub Form_Load() With WinSock1 .RemoteHost = "MailServer" .RemotePort = 110 .Connect End With End Sub ---------- Private Sub Form_Unload(Cancel As Integer) WinSock1.Close End Sub ---------- Private Sub WinSock1_Connect() MsgBox "Connect" End Sub ---------- Private Sub WinSock1_DataArrival(ByVal bytesTotal As Long) Dim strData As String WinSock1.GetData strData MsgBox strData End Sub ---------- Private Sub Command1_Click() Dim strData As String With WinSock1 strData = "quit" .SendData strData + vbCrLf '+ Chr$(13) + Chr$(10) End With End Sub ----------メールサーバーの POP3 ポートに接続し、ボタンを押したら quit を投げて切断するだけ。実処理をするのは DataArrival イベントが発生したときかな〜 Inet コンポーネント on VB(1) VB で、簡単に Web データ(テキストデータ?)を取得するコンポーネント Microsoft Iternet Transfer Control の単純な使い方。 この例では、URL を指定し、その HTML データを取得している。 Dim strData As String With Inet1 .URL = "http://web1.incl.ne.jp/tsukune/index.html" .UserName = "" .Password = "" strData = Inet1.OpenURL Text1 = strData End With認証が必要で、かつそのアカウントのパスワードがない("")場合、Web サーバーが「認証失敗」を返す場合があった。Web サービスの仕様だと思う。 なお、URL だけで、すべてを表示することが可能。 http://Account:Password@WebServerAddress:PortNumber/... や ftp://anonymous:MailAddress@FTPServerAddress:PortNumber/ といった感じ。当然ながら、デフォルトのポート番号(HTTP:80, FTP:21 など)は、自動的にセットされる。 スタイルシート無効 Internet Explorer でスタイルシートを無効にすることが出来る。 レジストリに値をセットする。 [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Internet Explorer\Main] REG_SZ: Use StyleSheets="no" デフォルトは "yes"。 なお、コントロールパネル「アプリケーションの追加と削除」(XP 以降だと「プログラムの追加と削除」)やインターネットエクスプローラでの「検索」などはスタイルシートで表示されているので、スタイルシートを無効にすると、画面表示は極端に崩れる。 DirectX 7.0 コーディング on VC++ プロジェクトに、dxguid.lib をリンクするよう追加する。 Visual Studio の「ツール」→「オプション」→「ディレクトリ」タブで、「インクルードファイル」および「ライブラリファイル」に SDK をインストールしたパス(...\INCLUDE, ...\LIB)を追加しておくと便利である。システムが持っているファイルよりも優先させるべく、追加したパスを「ディレクトリ」リストの一番上に持ってくる。 最小化/最大化/閉じるボタン 最小化,最大化,閉じるの各ボタン_□×が数字や変な記号に変わってしまうことがある。これは、フォントキャッシュファイルが壊れたことによる異常である。 Windows 9x 系でのみ発生するようで、Windows システムフォルダ(例:C:\Windows, A:\Windows)のファイル ttfCache を削除すると良い。再起動すれば(何かのシステムイベントで直るのかも〜?)ボタンの表示は元に戻る。 壊れるたんびに消さにゃあかん、そもそもあんまり役に立たないフォントキャッシュなんぞ、ファイルに読み取り専用属性付けて、固定してしまへ〜〜〜 ウィンドウの最小化/最大化/移動/サイズ変更 初めての VC でのウィンドウコントロールコーディング。 ---------- 1.最小化/最大化ボタンを無効にする方法 ---------- long lStyle; lStyle=GetWindowLong(hWnd, GWL_STYLE); lStyle&=~WS_MAXIMIZEBOX; //最大化のみ無効 SetWindowLong(hWnd, GWL_STYLE, lStyle); ---------- WS_MINIMIZEBOX も同時に無効になると、 _と□ボタンが消えて、×ボタンのみ残る。 ---------- 2.閉じるボタンを無効にする方法 ---------- HMENU hMenu; hMenu=GetSystemMenu(hWnd, FALSE); EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED); ---------- MF_BYCOMMAND は =0。 ---------- 3.移動/サイズの変更を無効にする方法 ---------- HMENU hMenu; hMenu=GetSystemMenu(hWnd, FALSE); RemoveMenu(hMenu, SC_SIZE, MF_BYCOMMAND); RemoveMenu(hMenu, SC_MOVE, MF_BYCOMMAND);無効にしたもの(メニューから削除したもの)を戻すには、GetSystemMenu をコールし、その引数として FALSE ではなく TRUE を渡す。 ---------- ウィンドウハンドルがわかれば、アプリケーションの外部からでも最小化や最大化のボタンを有効や無効にすることが出来る。ただ、ボタンを押したり再表示したりすると外部からの設定が消えてしまい、デフォルトに戻ることもある。もしずっと外部からの設定を有効にしたいと考えるなら、タイマーを使って定期的にボタンの状態をチェックするとか、変更があったらイベントを上げるようにするとか(こっちは試したことない)、何か手を考えなければならない。 Win32API 使用方法 on C# C# コーディングで、Win32API を含む DLL 関数をコールする方法。 ------------------------------------- using System.Runtime.InteropServices; class Class1 { [DllImport("Kernel32.dll")] extern static void Sleep(int iMilliseconds); [STAThread] static void Main(string[] args) { Sleep(5000); } } ------------------------------------- using System.Runtime.InteropServices; class Class1 { [DllImport("Kernel32.dll", EntryPoint="GetPrivateProfileStringA")] extern static int GetPrivateProfileString( string SectionName, string KeyName, string DefaultValue, IntPtr ReturnData, int BufferSize, string IniFileName); [DllImport("Kernel32.dll")] extern static int WritePrivateProfileString( string SectionName, string KeyName, string Data, string IniFileName); [STAThread] static void Main(string[] args) { byte[] bData=new byte[256]; System.IntPtr ptr=Marshal.UnsafeAddrOfPinnedArrayElement(bData, 0); iRet=WritePrivateProfileString( "Section", "Key", "Data", "D:\\Sample.ini"); iRet=GetPrivateProfileString( "Section", "Key", "Default_Value_String", ptr, 256, "D:\\Sample.ini"); strData=Marshal.PtrToStringAnsi(ptr); } } -------------------------------------後のほうは、引数の定義が文字列の先頭アドレス渡しになってるタイプ。Get なんちゃら関数には、文字列をもらうものも多くあるので、サンプルとして出してみた。 ネットワークコンピュータ一覧に自分のPCを公開しない Windows 98 以前や NT4.0 以前だと「ネットワークコンピュータ」、Windows 2000 以降だと「マイネットワーク」、を開くと、ワークグループ名やドメイン名の一覧を表示することが出来る。また、さらにそれを開けば、そのワークグループやドメインに所属しているコンピュータの名前一覧が表示される。これを利用して、他のマシンの共有フォルダにアクセスすることはよくある話だ。 しかし、自分以外が使用するネットワークにおいては、一部のメンバー、もしくは自分だけがその存在を知り、他のメンバーや知らないパソコンにはその情報を表示させたくない、という意見もある。 レジストリに値をセットすることで、これを実現することが出来る。 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameter] REG_DWORD: Hidden=1 デフォルトは 0。 LanmanServer というのは、コントロールパネルの配下にある「サービス」で表示できる「Server」というサービスが使用する場所である。このため、(細かく調査したわけではないが)Server サービスの再起動、もしくは Windows の再起動が必要となるだろう。 なお、Windows 2000 以降(2000, XP, 2003 等)で可能っぽい。 IE6 のアドレス入力(http://www. .co.jp) こぴぺした文字列に、勝手に http://www. と .co.jp がくっつく。 具体的には、ある文字列(例えば "\\Server\Share")をこぴっておく、エクスプローラを開く、アドレスのところに Ctrl+V で貼り付ける、貼り付けた後 0.2 秒以内にエンターを押す。これで発生。http://www.\\Server\Share.co.jp を探しに行ってくれる。再現性抜群。むむー。 ちなみに、実際の発生方法は「Ctrl+Enter」によるもの。「http://www. .co.jp」(日本語版以外だと「http:// .com」)を付ける機能。 そんなショートカットキー、いらないよー!(TωT) .zip, .cab 自動チェック for Windows XP Windows XP 以降で標準の、圧縮ファイルの内部自動チェックを行わない/行う方法。 regsvr32 /u zipfldr.dll regsvr32 /u cabview.dll 再度チェックするときは、/u スイッチを付けない。 互換性モード for Windows 2000(SP2 以降) Windows 2000 SP2 以降や Windows XP で、実行ファイルのショートカットに、互換性タブを付ける/外す方法。 regsvr32 %systemroot%\apppatch\slayerui.dll 解除するときは、/u スイッチを付ける。 どこまで互換性があるかはわからないが、自作ツール内で GetVersionEx をコールすると、OS とは違うバージョンが返された。 バックアップツール NTBackup for Windows 2000 バックアップするタスクを作って、スケジューリングさせても、エラー吐いてバックアップできない。詳細はこんな感じ…… Windows 2000 標準のバックアップツール NTBackup.exe を使って、毎日特定のフォルダをネットワークドライブにバックアップするタスクスケジューリングを組みました。コマンドはこんな感じです。Windows 2000 SP2 で確認。SP3 にしたけど一緒だった。もう知らん。 補足。 Windows 2000 Advanced Server SP4 でのスケジューリングは、正常に動いていた。ファイルやメールデータの定期バックアップがされていた。何かが改善されたのだろう。こちら(102. NTBackup)もご覧くだされ……。 マルチバイト(全角文字)の1バイト目チェック Win32API _ismbblead。コードページ 932 の場合、有効な範囲は 0x81〜9F と 0xE0〜FC。 こちら(92. パス文字列の最後の \ チェック)もご覧くだされ……。 読み書きアクセス可否チェック そのアドレス空間に対して、読み書きしようとした時に、Access Violation が発生するかどうかを知ることが出来る。 Win32API IsBadReadPtr と IsBadWritePtr。チェック対象の先頭アドレスとサイズを指定する。TRUE が返ってきた場合、指定バッファに読み書き不可の部分が存在する。 Explorer.exe 引数 Windows 2000 以降では、エクスプローラの起動状態がフォルダごとに違う設定を持ってるからそれぞれ違う大きさで表示される。わたしは、常に同じ大きさ、同じ位置で表示されてほしい。「なんとかならんかい!?」と思って最初に調べたのが、コレ。 /e /e,[Path] フォルダツリー付き /n /n,[Path] フォルダツリーなし /s /s,[Path] フォルダツリーなし /select,[File/Folder Name] ファイル/フォルダ選択統一できるかな〜と思ったけど、ダメだった。 前は、フリーウェアの Fasie を使ってた。Windows XP でも使えた。でも、ウィンドウの大きさ設定中になんか作業したらエクスプローラが落ちたのであきらめて、「エクスプローラ位置修正ツール for Windows」を自作した。(まだバグあるっぽいけど) URL 文字数制限 IE だけかな? 2080 バイトくらい。 Web ページでのフォームで、Submit するときに method=POST を付けるのと付けないのの差。付けなかったらすべての変数が URL にくっついちゃうので、掲示板なんかで、長い文章なんかを投稿できなくなってしまう。 mciSendString Win32API のひとつ。音楽関係を制御する。で、その機能のひとつ、「音楽CDをオープンする」というもの。 基本の構文 open cdaudio とすると、コントロールパネルで設定されているCDドライブがオープンされる。複数のCDドライブを持っているマシンでは不都合だったりする。複数ドライブでも選択できるようにするための構文が、これ。 open <DriveLetter>: type cdaudio alias <AliasName> open x: type cdaudio alias cdn 「x:」のところに、CDドライブレターをセットするとOK。play cdn(エイリアス名)とすれば、再生される。 また、オーディオトラックのみが書き込まれているCDであれば特に問題はないが、いわゆる「ミックスCD」や「エクストラCD」の場合、1トラック目や最終トラックがオーディオではなくデータの場合がある。データトラックは再生できないので、たとえ play data_track としてもエラーにはならないが、次トラックが再生されてしまうので問題となる可能性がある。オープンの直後にトラック長さを取得する status cdn length track 1 を発行すると思うので、一緒に status cdn type track 1 を発行し、そのトラックがデータなのかオーディオなのかをチェックしておくと良いだろう。 なお、データトラックの場合は「その他」、オーディオトラックの場合は「オーディオ」という文字列が取得される(英語 Windows で試したら「other」と「audio」だった) mciSendCommand、まだ使ったことない。 余談。エイリアス名 cdn ? わたしは、使うマシンすべてのメインCDドライブを「Nドライブ」とするので、cdn とした。Mドライブだったら cdm だし、Zドライブだったら cdz。 n は、Nドライブって意味だわさ。 Visual Studio .NET (2002 年夏) バグだらけ(滅)置換作業しようとしたら落ちる。参照設定で DLL ファイルを再登録しても前の情報持ったまんま。switch 文入れたらとんでもないインデントかかる。中カッコ閉じる } を入れたりコピペしたら勝手にインデントかける。設定画面やボタンに固定ピッチフォントを使うと範囲に収まらない。そもそも IE6 がないと動かない。 最低。 IE6 嫌いなんじゃ! フォルダツリーの適当なところクリックしただけで勝手に表示フォルダ変わるし……。 &H BASIC 言語で、16 進数を表す時に使うやつ。&H100 って書くと、10 進数の 256 を示す。&H00FF00 と書いたら、わたしは 65280 になると思っていたのに。勝手に &FF00 に変わり、かつ 2 の補数なのでマイナスの値に変わりよった。VB のバカ(`□´) MsgBox CStr(&HFF00) → -256 が表示される。 ちなみに、これは Integer 型(16 ビット)になるのでマイナスなわけで、後ろに & を付けることで Long 型(32 ビット)として扱われ、65280 となる。 MsgBox CStr(&HFF00&) → 65280 が表示される。 Perl CGI 使っているのは、ActivePerl 5.6.1。 Web サービスの違いで、引数($ARGV)の中身が変わるみたいだ。具体的には、IIS だと $ARGV[x] には何も入って来なくて、Apache だと全部 $ARGV[0] に入ってくる。どっちも困る(汗) # 引数取得(IIS 用) if($ARGV[0] eq "" && $ENV{QUERY_STRING} ne "") { $query=$ENV{QUERY_STRING}; $query=~s/\n//g; @ARGV=split(/&/, $query); } # 引数取得(Apache 用) if($ARGV[0]=~/\\&/) { $ARGV0=$ARGV[0]; @ARGV=split(/\\&/, $ARGV0); }これで、統一されたことになる。 Word 97 表示フォントのバグ Word 97 で作ったファイル、Word 2000 で開いて更新したら、表示フォントが変わってしまう件。こっちはMSゴシックにしとるんに、なんで Arial になるんじゃ!(`□´) は、みくろそふたーも認識しているバグっぽいので、それを直す方法を発見! フォント指定の全置換(爆)そんなんしかないんかい! でも綺麗になったから、いいや♪ PCMCIA ぷちゃむちゃ、と読むそうです(笑) ノートパソコンでよく使われている拡張カードの種類とか、そんなことを言いたかったわけではないです(爆) ぽぽるフォント わたしの好きな、NEC 製かな、フォント「FA ぽぽる」。FANGOT5.TTC というファイル。が、ぺけぴー(Windows XP)では「不正なフォント」として怒られた(;;) なお、Windows 2000 でも、SP4 をあてると不正なフォントとして見なされる。 NEC は、「Font Avenue」という新しいパッケージの中で、ぺけぴーでもちゃんと表示されるフォントを提供している。表示したい場合は別途購入しなければならない。 改行コード of au わたしが使ってる携帯電話(au)の改行コードは、0x0D 0x0D 0x0A という、なんとも特殊なものだった。 ShiftJIS だと 0x0D 0x0A、UNIX 系(?)だと 0x0D なのかな? CHR$(13) はキャリッジリターン CR に、CHR$(10) のラインフィード LF が含まれているかいないかが問題となる。CR LF と表現するのは、昔のタイプライターの動きが元だからである。タイプライターは、かたかたかたかたちーん、とキーを叩く。文字を打って、改行するときに「まず先頭に戻ってから(CR)1行下がる(LF)」、というもの。ただ、すべてのタイプライターがこれに当てはまるかというと、そうでもないらしい。メーカーによっては、改行の時に「1行下がりながら先頭に戻る」ものもある。 他のメーカーの携帯電話までは調べようがないなぁ〜 余談。 キーボードのキー配列は、このタイプライターから来ている。じゃあ、なんでこんなキー配列になったかというと。 昔のタイプライターはアルファベット順に並んでいて、ユーザーはとても速くキー入力が出来た。ところが、ハードウェア処理が追いつかず、キー配列をバラバラにすることによって「ユーザーのキー入力を遅くさせた」のである。 |