「どつぼ」の記録・その6


ダイヤルアップできない!

1998/03/13

プログラマーというのは、結構身勝手な生き物だと思うことがあります。私だけかも知れませんが、あからさまに指摘しないと不具合(bug)は認めない傾向があると思います。特に自分のマシン上で再現しないbugを言われると「そんなの貴様のマシン環境が腐っているせいじゃないの?」と喉まででかかります。もちろんそんな事は絶対口にしませんが、そう考えたことがないなんてのはWindowsプログラマーだったらウソじゃないかと思っています。実際OSの再インストールをしたら嘘のように不具合が解消されたなんてのは良く聞きます。

実は私は「バグ報告にはケンカ腰」で対処する方です。bugを見つけてくれてくれた人に対しては、ありがたいというのはもちろんありますが、現象を確認できるまで、絶対そのbugの存在を信じません。すぐに再現できるようなbugだとさっさと直して、修正手続きをして一件落着なのですが、自分の所で再現できないようなbugだと鬼の首を取ったように「てめえ、こんなの検査ミスじゃねえのか?再現しないぞ!」とか怒鳴り込みます。

私の会社では検査関係の部署は比較的パートタイムとかアルバイトの人が多いので、私が怒鳴り込むと、その検査を担当した人は正社員の私と比べると、比較的立場が弱いこともあって、生きた心地がしないでしょう。おずおずと検査手順を繰り返します。で、検査室を出る時に自分の間違いを思い知らされて、顔を真っ青にしているのはいつも私の方です。怒鳴り込む度に大恥をかいています。(^_^;

人間が出来ていないのかもしれませんが、bugが見つかった場合見つけてくれた人には感謝しますし、見つからないより見つけた方がいいに決まっていますが、プログラマーとしてはbug報告を受けて無邪気に喜ぶ気にはなれません。大抵自分の頭の悪さを「これでもか」という位に思い知らされた気分になって(事実良くないけど)、機嫌が悪くなります。まあ、検査側の人とか、今まで動作検証を手伝ってくれた人達がいい人ばっかりだったので、なんとか仕事を続けられているんでしょうね。(^_^;

Windows95と一口に言っても、実際のマシン環境には色々バリエーションがあるので、特定の環境じゃないと再現しないbug、システムDLLが壊れているせいで起こるbugもまれにありますが、確かにあります。でも大抵は自分自身のプログラムが引き起こしたbugです。ですから短気は損気、最近ではいくら自信があってもbug報告があったらじっくりプログラムを見直したり、テストをするようになりました。「7回疑って人を疑え」のことわざではありませんが、それくらい慎重に確認すると何かプログラムの問題点が見えてくるものです。それが普通です。

で、その普通じゃないところで、おなじみ「どつぼ」が始まるわけです。

私の今のお仕事はダイヤルアップ接続関係のツールです。これが、あるWindows95マシンで動かないというのをOEMの取引先から指摘されました。そのプログラムではPPP接続を実行する場所があるのですが、それがうまくいかない、PPP接続するどころか、ダイヤルもしないということでした。

ダイヤルもしないという事は、モデムの初期化までのところでつまずいている訳です。とりあえず動作確認して見ますが、私のマシンで試してみると、モデムの初期化までは一瞬で終ってしまうし、ちゃんとダイヤルもします。まあ、環境依存で出るbugかなぁ、と思ってソースファイルのめぼしい所をもう一度読み直して見ますが、環境依存のコーディングなんてのはどこにもありませんでした。MicrosoftのAPI解説なども読み直して見ますが、見れば見るほどAPIを普通に使っているプログラムです。訳が分かりません。(^_^;

どこでエラーが起きているのかさっぱり分からなかったのですが、とりあえずダイヤルする部分を中心にスタブを埋めこんで見ました。APIが失敗した時のエラー表示をサボっている所を復活させて、ちゃんとどんなエラーが起きたのか表示させるようにした上で、相手先に「このプログラムで試してくんな。メッセージが出るから、どんなメッセージが出たか教えてくんな。」と連絡をします。返事を待っている間もずっとプログラムのテスト、ソースの見直しをやっているんですが、依然問題になるような所は発見できません。一体何が悪いんだか…。

1時間ほどして答えが返ってきました。「『ハードウエアのエラーです』って表示されたけど、これってどんなエラーなの?」私も結構回数だけは沢山PPP接続したと思うのですが、このようなエラーが起きたのは初めてです。でも、ハードウェアのエラーじゃソフト的にはどうしようもないので、目が点になってしまいました。(*_*;)解決方法の見当どころか、どうしてこんなエラーが起きるのかの見当さえ付きません。

状況からみると、RasDialというAPIが失敗しているようです。でも、取引先では以前からPPP接続をこのマシンでやっているので、PPP接続できないという事はちょっと考えにくい。ふみゅみゅ〜。どうして。どうして。ゲバゲバパパ〜ヤ〜(古い)と思わず叫んでしまいます。(^_^;

こっちで何回テストしてもそのエラーが再現しないのですが、相手先もbugを潰したいのは一緒なので、色々状況を教えてくれたり、環境をとっかえて試して見てくれました。ありがたい事です。色々状況を聞いて見ると、モデムドライバーを沢山インストールしているみたいです。じゃあ、という訳でテストマシンに10個くらいモデムドライバーをインストールしてみますが、やっぱり再現しませんでした。予定では今日発送になっているので、遅くとも今日の7時頃までには原因を突き止めて、解決しないと間に合いません。こうなったら恥も外聞もありません。上司をつかまえて「すいません。テストに協力して下さい。」

快く引き受けてくれた上司のNさんがテストを始めてから20分ほど経った頃でしょうか?「お〜い、vyama。再現したぞ。」とお声がかかります。問題の再現は問題解決の第一歩です。実際、再現しない事には何が原因かも分かりません。ちょっとほっとしました。ところが…。

N「な、お前の書いたプログラムを俺のノートパソコンで実行すると『ハードウェアのエラーです』って出るだろ。」
v「確かに出ますね。ふ〜ん、おかしいなぁ。ソースファイルに変な所は見当たらないんだけど。(既に泣き声)」
N「まあ、もう一度ソースファイルを見直して見ろや。」
v「はい、すいませんでした。調べなおします。私のプログラムで接続しようとした時だけ、エラーが起きるんですよね。」
N「そう。さっきダイヤルアップネットワークのアイコンを叩いてアクセスしたら出来たからね。」
v「もう一回やってもらえます?」
N「疑り深い奴だな。まあ、いい。こうやってダイヤルアップネットワークのアイコンを叩くだろ。そうすると、ほら、ちゃんとダイヤルしていますって出るぞ。」
v「それにしては、なんか電話かけているようにみえないんですけど。」
N「そんなバカな。」
そこに飛び出るWindowsからのメッセージ。
W「モデムにアクセスできません。」
v & N「これってWindows95のbugじゃないの?」

色々調べて見ると、ノートパソコン、特にCom3とかを使ってダイヤルアップをするPCM CIAカードを使っているというのが肝のようで、それ以外ではどうやっても再現できませんでした。現象としては、PCM CIAカードドライバーと外付けモデムドライバーを混在させた状態で、PCM CIAカードを使ってアクセスしようとすると、ダイヤルアップしてもモデムの初期化に必ず失敗しまうようです。(Nさんのノートマシンでしか確認していないのですが。)不思議な事にモデムに直接アクセスしてATコマンドを叩いて見ても、パソコン通信ソフトを起動しても、何の問題も起きません。

ただ、PCM CIAカードを使っている場合にしか起こらないんだったら、カードスロットルのない私のディスクトップマシンで検査しても再現させようがありません。ただ、これはひょっとするとWindowsのバージョン依存、マシン依存のbugかも知れないなとは思いました。ところが、それを確かめようにも手元のマシンでPCM CIAカードを使えるマシンは、Nさんのマシンだけだったので、確かめるとすれば、NさんのマシンのHDを一旦フォーマットして別のWindows95のバージョンを入れるしか確認のしようがありません。冗談半分で「この現象を確かめるためにはNさんのマシンのHDをFormatするしか手はありません。」とか言ったら、Nさんの顔が引きつっていました。(^_^;でも、実際それ位しか確かめようがないので困ります。月曜日になれば何とかならないこともないのですが…。

そんな時に、取引先から連絡が入りました。「実際に使っているモデムドライバー以外を消去したら、チャンと動作するようになった」ということ。状況から見て、ドライバーのコンフリクト、つまりある種のドライバーを同時にインストールすると発生するようなエラーだと断定してもいいでしょう。

まあ、PCM CIAモデムドライバーと外付けモデムドライバーをちゃんぽんにインストールしているなんてのは、相当特殊な状況ですし、こっちの予想が正しければ、OSを書き換えるのでもない限り、アプリケーションからどうこうできる問題ではありません。

と言う訳で、その旨を取引先に説明をしました。私自身もこのエラーについては納得していないし、実際丸1日かかってこの程度の結論しか出ないとがっかりしますし、どっと疲れます。取引先もかなり不本意そうでしたが、幸い何とか理解してもらい、この件についてはドキュメント対応ということになりました。やれやれ。

MSDEVの殺し方

1998/03/7

最近は、昔書いたプログラムのメンテナンス作業とか、ちょこっとした修正+修正確認検査などが主な作業になっているので、あまりどつぼな事が起こりません。(閻魔様に舌を抜かれるかな?)私にとってみれば、仕事が順調に進み、胃の痛い思いもせず、残業もあまりやらなくてすみますから、大変いい事です。

でもこのページを読んでくださっている方から、時々「つぶやきばっかり更新していないで、たまにはどつぼの記録のほうも更新してください。」とお願いされます。どつぼの記録が更新するには、私がおおはまり状態に陥るという、とても不幸な事が起こらなくてはいけません。どうやら皆さん「他人の不幸は蜜の味」ということらしいですね。ええ、とっても良く分かります。私も自分以外の不幸を見るのは大好きですから。(^_^;

冗談はさておき、今回の話題はC++、特にVisual C/C++とMFCを使っている人向きの話題です。また私の使っているVisual C/C++(以下MSDEVと略)のバージョンは4.2です。現時点での最新は5.0ですが、色々理由があって、最大の問題は面倒臭いって事ですが(^_^;、未だにVersion Upしていません。これから書く事は、Ver4.2だけで起こる、さらに私のマシンだけで起こる現象かも知れませんので、その点はお断りしておきます。<(_ _)>

事の起こりは、ダイヤルアップ接続のためのプログラムを書こうとした事でした。Windows95がリリースされて間もない頃は、ダイヤルアップ接続関係のAPIはとても貧弱で、あまり使い物になりませんでした。仕方がないので、あの手この手を使ってダイヤルアップエントリーを作ったり、DNSなどの設定をするプログラムを書きました。ところが、この「あの手この手」というのが非常に曲者で(^_^;、要は「あのボタンを押したら、こんなWindowが出てきて、こんな項目があるはずだから、それを選びなさい」なんてことをSendMessageとか、FindWindowとかのAPIを使って実現する訳です。(^_^;こんなのWindowsのバージョンが変わる度に「Cancel」が「キャンセル」になったりしたらその度に修正です。英語版ではもちろん動きませんし、NTでも動かないでしょう。(よい子の皆さんは決して真似してはいけません。)

最近になって…なのかはよく知りませんが、MicrosoftのWWWサイトからダイヤルアップエントリーを作ったりDNSを設定できたりというDLLが公開されました。ところが、それでも足りない機能がありました。「ダイヤル中…」とかいったダイアログを表示するAPI、RasDialDlgはWindowsNT SP3以降でないと、つまりWindows95では使えません。

どうせWindowsが使っているなら、公開くらいしてくれよ、と言いたい所ですが、まあダイアログを作って、接続状態に合わせて「ダイヤル中...」とか「パスワードの確認中...」とか表示して、おまけにアイコンアニメーションを表示すれば完成ですから、いくらイモプログラマーの私でも何とかなりそうです。他の言語で書いてあるモジュールからその機能を呼び出さなければいけないので、ダイアログはモーダルレスダイアログをDLLに実装する事にしました。

私はDLLを新規に作る場合、まず単独の実行ファイルの中の中に、埋めこんでみてdebugします。今回は簡単なダイアログアプリケーションを作って、ボタンを押すとダイヤルアップが始まって、モーダルレスダイアログが出てくるような単独アプリケーションを書いてみました。私にとっては最初からDLL化してdebugするよりも、この方がdebugしやすいので、良くこの方法を使っています。

最初のコードは、あまり良く憶えていませんが、大体こんな感じです。


---testdlg.cpp---

//ダイヤルアップ接続プログラムのテストプログラム



//接続状態を表示するdialogのインスタンス

CRStatDlg MyRasDlg;



//接続を開始する

void CTest::OnButton1()

{

    //ダイヤルするためのパラメータ設定など

    ...

    //ダイヤルをかける

    //RasDialFunc1で、MyRasDlgにステータス変更を通知する

    DWORD dwRet = RasDial(..., RasDialFunc1, ...);

    

    //接続状態を示すダイアログをモーダルレスで表示する。

    MyRasDlg.Create();

    MyRasDlg.ShowWindow(SW_SHOWNORMAL);

}

---rstatdlg.cpp---

//接続状態を表示するダイアログクラス



//接続を中断する

void CRasStatDlg::OnCancel()

{

    //電話を切る

    ...

    //接続状態表示ダイアログを消す

    MyRasDlg.DestroyWindow();

}

MyRasDlgをグローバル変数にしている所が気になるかも知れませんが、ダイヤルアップの接続状態を示すダイアログなんてのは1つでいいんだから(え?ISDNの2回線使って別々の所に同時にダイヤルアップ接続を試みたらどうなるんかって?いやいや、エヘヘ。実は考えていません。(^_^;)、そのためのインスタンスも1つで十分。それだったら最初にnewして、後でdeleteを明示的にやるよりは、こっちの方があとでメモリ解放ルーチンを自分で呼び出さなくてもいいので楽かな?と思った訳です。どうせ、ダイアログオブジェクトのメモリ占有量なんて大したことないはずだし。で、多少紆余曲折がありましたが、ちゃんとダイヤルアップ接続も出来るし、これはこれでうまくいったみたいです。

後はこれをDLLにするだけです。こんなコードを書いてみました。今度はtestdlg.cppとrstatdlg.cppは別プロジェクトになっています。rstatdlg.cppがDLLのコードで、testdlgがDLLのテストプログラム本体です。


---testdlg.cpp---

#include "rstatdlg.h"



//接続を開始する

void CTest::OnButton1()

{

    //ダイヤルアップ接続をする

    MyRasDlg(...);

}

---rstatdlg.cpp---

//接続状態を表示するdialogのインスタンス

CRStatDlg MyRasDlg;



extern "C" DllExport BOOL WINAPI MyRasDlg(...)

{

    //ダイヤルするためのパラメータ設定など

    ...

    //ダイヤルをかける

    //RasDialFunc1で、MyRasDlgにステータス変更を通知する

    DWORD dwRet = RasDial(..., RasDialFunc1, ...);

    

    MyRasDlg.Create();

    MyRasDlg.ShowWindow(SW_SHOWNORMAL);

}



//接続を中断する

void CRasStatDlg::OnCancel()

{

    //電話を切る

    ...

    //接続状態表示ダイアログを消す

    MyRasDlg.DestroyWindow();

}

で、これも試してみます。CTestのButton1を押すと、おお、ちゃんと接続状態のダイアログが出てくる。すかさずキャンセルを押します。当たり前だけど、ちゃんと接続状態のダイアログが消えてくれるし、電話も切れているようです。さて、もう一度、Button1を押して、最後まで接続するか確かめます。これもOK!と言う訳で、今回はどつぼにはまらなくてすんだようです。(^_^;やれやれ、もう一度念入りに動作チェックをしてやればいいな、と思ってテストアプリケーション(CTest)を終了させた瞬間、「カリカリカリ…」
アサートに失敗しました。
あちゃ〜、どこかで間違えて変なパラメーターを渡してしまってNULLへの参照でもしてしまったに違いない。(^_^;まあ、よくある事だから…、と思って、Call Stackを見てみると…、あれ?call stackに私の書いたプログラムが入っていない。全部Kernel.dllとMFC内部での処理じゃないか。まあいい、もう一度全体を確認することにして、一旦プログラムを停止させよう、と言う訳で、debugの中止コマンドを選択します。「カリカリカリ…」あれ?ハードディスクから音がする…。
このアプリケーション(MSDEV)は不正な処理を行いました。
がちょ〜ん。(死語)debugプログラムだけじゃなくて、MSDEVが死んじゃった。(;_;)まあいいや、とりあえずもう一度MSDEVを起動すればいいんだから。で、OKボタンプチ。「カリカリカリ…」
このアプリケーション(MSDEV)は不正な処理を行いました。
(がちょ〜ん)^2。更に何回か「OK」ボタンを押してみますが、「カリカリカリ」の後、返ってくる返事は
このアプリケーション(MSDEV)は不正な処理を行いました。
だけ。(;_;)うむむ、仕方がない。ここはリセットしかないか…、ということでマシンをリセットしてみました。リセットした後、下手をするとMSDEVも立ち上がらないかな?と思ったのですが、さすがにそれはなかったので一安心。(^_^;で、もう一度先程のテストプロジェクトを開いて…、「げ、Project Fileが消えている!」

幸い、Makefileとソースファイルは残っていたので、Project Fileを再構築してdebugのやり直しです。Project Fileが消えていた時には「他のファイルも巻き添えになって1から作りなおしかよぉ」とあせりましたが、なんとか他のファイルは大丈夫だったようです。やれやれ。

注意深くソースコードを追っかけたつもりだったのですが、例の「カリカリカリ…」+ MSDEVがOSごとお亡くなりになってしまう現象は確実に再現してしまいます。MFCの中も少しは追いかけてみたけど、こちらからのパラメータ設定には特に問題がないようだし、死んでしまう場合のコールスタックを見るかぎり、私の書いた部分を呼び出しているようには思えないし…。久しぶりに胃が痛くなってきます。

とにかく、「カリカリカリ…ドカ〜ン!」の状態をなんとか回避してまともなプログラムにしないと駄目です。「あそこかな?」とちょこっといじってみて、どっか〜ん、駄目。「こっちかな?」とちょこといじってみて、どっか〜ん。主にダイアログ関係のパラメーターをいじっていたのですが、これを2時間くらいやっていました。(;_;)

で、やっぱりこのWWW Pageで書くくらいだから、何とか解決は出来ました。でも、解決は出来たんですが、何故解決できたのか、よく分からないんです。(^_^;解決したコード(DLL側だけ)は次のとおりです。


//接続状態を表示するdialogのインスタンス

CRStatDlg *pMyRasDlg = NULL;



extern "C" DllExport BOOL MyRasDlg(...)

{

    //ダイヤルするためのパラメータ設定など

    ...

    //ダイヤルをかける

    //RasDialFunc1で、MyRasDlgにステータス変更を通知する

    DWORD dwRet = RasDial(..., RasDialFunc1, ...);

    <

    if (!pMyRasDlg) {

	pMyRasDlg = new CRStatDlg;

    }

    pMyRasDlg->Create();

    pMyRasDlg->ShowWindow(SW_SHOWNORMAL);

}



//接続を中断する

void CRasStatDlg::OnCancel()

{

    //電話を切る

    ...

    //接続状態表示ダイアログを消す

    MyRasDlg.DestroyWindow();

}

静的にとっていたClass Instanceを演算子newで取るように変えただけで、嘘のように動いてしまいました。え〜、なんでそれだけで動くようになるの〜?(?_?)と言いたい所ですが、とにかく動かないものが動くようになったんだから仕方がない。What is a amazing storyです。(ここでは省略していますが、new演算子で確保したCRStatDlgオブジェクトは用済みになったらdeleteしないといけません。)

誰か、ものの分かっている方はどうしてこんなことが起こるのか、教えてください。<(_ _)>私と同程度に分かっていない人達に忠告。DLLでCObjectのインスタンスを作る時には必ずnew演算子を使う事。何故なのかは分からないんですけどね。(^_^;


1つ前の「どつぼ」の記録

A HREF="dotubo.htm">「どつぼ」の記録 目次へ

作者(vyama_at_janis_dot_or_dot_jp)への感想・意見など。
spamよけのため、_at_や_dot_は適宜@や.に入れ替えてください。

目次ページ