こける Wired-Winsockを使ってみようぜ-7.固まらない住所調べ'97/12/15

7.固まらない住所調べ


今回は、「住所と氏名」と同じ「IPアドレスと本名表示」を「非同期検索」で作ります。
コードのかなめは「コンポーネント」に仕立て上げます。
コンポーネントの作り方はさらっと流しちゃいます。

「IPアドレスと本名表示」の要のコードは「gethostbyname」でした。
これの非同期検索版はWSAAsyncGetHostByNameです。頭にWSAAsyncが付いただけ。(^^)
でも型は大違い。
function WSAAsyncGetHostByName(HWindow: HWND; wMsg: u_int; name, buf: PChar; buflen: Integer): THandle;
HWindowとwMsgが「結果を送るWindow」と「結果メッセージの番号」です。
#Delphiには関係無いことながら、VBの場合予めVBで取得できるメッセージしか取れません。
#で、裏技ですが、Pictureコンポーネント等を不可視で作成してWM_MOUSEMOVEでも送ってもらえばイベントが発生しますし、パラメータは座標のXとYを加工すれば取得できるって話しです。
メッセージ番号はWindowsが使わない値を選ばなければなりません。
Windowsが使わないあたりは、MessagesユニットのWM_USERという定数で宣言されています。
しかし、これだけでは駄目です。Delphiのライブラリが使うあたりも止めましょう。
こっちは定数が宣言されていないのです。経験的にはWM_USER+$100あたりからが良いようです。
nameはgethostbynameと同じ、「検索するべきホスト名」です。
bufはPCharになっていますが、これがちょっとだけ厄介「使用するバッファ」です。あとで説明します。
buflenはbufの大きさ。固定でMAXGETHOSTSTRUCTです。つまりbufの大きさはこれだけ必要なのです。

返り値がまた厄介。処理ハンドルです。
複数の検索を同時に行った時、どの検索の事なのかを判別するために使います。
あまり同時には検索しませんから関係ないのですが、検索を途中で打ち切るときにも使います。
結果のメッセージのwParamにも入っています。
エラーのときは0です。WSAGetLastErrorで調べましょう。

で、bufの説明に戻ります。
gethostbynameの時は、HostEntはWinsockの方で用意した領域にありました。
しかし、WSAAsyncGetHostByNameでは、呼び出す側が用意しなければならないのです。
ま、この方が自然に思えますけどね。(^^)
でも、THostEntだけ用意するのでは足りません。WSAAsyncGetHostByNameが使う作業領域もこちらで確保するのです。
それやこれやを合わせたサイズがMAXGETHOSTSTRUCTというわけです。
結果のTHostEnt型の変数は、この一番上+0の所にあることになってます。
さて、THostEnt型のサイズはMAXGETHOSTSTRUCTもありませんし、かといってarray [0..MAXGETHOSTSTRUCT-1] of BYTEではTHostEnt型の変数として使うのは面倒です。
変数はどうやって宣言しましょうか?
Case使うのが面倒が無くて良いと思います。CやC++でいうところの共用体unionです。
というわけで、Record型としてこんなのを用意しておきましょう。
type
PGetHostStruct=^TGetHostStruct;
TGetHostStruct=Record
        Case Integer of
        0: (buf: array [0..MAXGETHOSTSTRUCT-1] of BYTE);
        1: (hostEnt: THostEnt;);
end;
この型ならサイズはMAXGETHOSTSTRUCT分ありますし、THostEnt型として使うのも楽です。
ここでは説明しませんが、TNetEntやTServEnt、TProtoEntが必要になったときは、この型に押し込むと良いのではないでしょうか。

ついでにgethostbyaddrの非同期検索版WSAAsyncGetHostByAddrも示しときましょう。
function WSAAsyncGetHostByAddr(HWindow: HWND; wMsg: u_int; addr: PChar;
  len, struct: Integer; buf: PChar; buflen: Integer): THandle;
型だけで嫌になるほど長いです。
順番に説明していきましょう、ってWSAAsyncGetHostByNameとgethostbyaddrを足して2でわったようなものなので、見リャ判るって話もありますが。

HWindowはWSAAsyncGetHostByNameと同じ、結果を送ってもらうウィンドゥハンドルです。
wMsgもWSAAsyncGetHostByNameと同じ、結果を送ってもらうウィンドゥメッセージ。
addrはgethostbyaddrと同じ、検索すべきIPアドレスを設定したTInAddr型の変数のアドレス。
PCharだからといって文字列ではないんですよ。このへんがWinsockの古いとこ。C言語版でvoid*ではなくchar*なのです。
lenはaddrの大きさ。だから4固定ですね。
strucはAF_INETを設定します。
bufはWSAAsyncGetHostByNameと同じ、TGetHostStruct型のバッファを用意してそのアドレスを入れときます。
buflenもWSAAsyncGetHostByNameと同じ、MAXGETHOSTSTRUCTでなければなりません。

返り値もWSAAsyncGetHostByNameと同じく処理ハンドルです。

で、結果メッセージの受け取りかたですが、WSAAsyncGetHostByNameもWSAAsyncGetHostByAddrも同じように処理します。
前回お話したAllocateHWndを使ってWndProcで処理しちゃいましょう。
WndProcの引数は汎用のTMessage型です。
大体こんな風に処理します。
procedure TXXXX.WndProc(var Msg:TMessage);
begin
  if Msg.Msg<>「WSAAsyncGetHostByXXXXに渡したwMsg」 then begin
    {自分が処理したかったわけではないメッセージがきました。デフォルト処理をしましょう。}
    Msg.Result:=DefWindowProc(「AllocateHWndが返したHWnd」,Msg.Msg,Msg.wParam,Msg.lParam);
    Exit;
  end;
  THandle(Msg.wParam)が、WSAAsyncGetHostByNameの返した値です。あまり使わないかも?
  もし使うのなら、この時点でどの検索の結果かを判定してください。
  if WSAGetAsyncError(Msg.lParam)<>0 then begin
    検索に失敗しました。
    WSAGetAsyncError(Msg.lParam)でエラーコードを調べてください。
    WSAGetLastErrorと同じコード体系です。
    この時点でWSAGetLastErrorを調べても無駄です。多分0が返ってくるのではないかと思います。
    Exit;
  end;
  検索に成功しました。
  WSAAsyncGetHostByXXXXのbufに設定したバッファをTHostEntとして、gethostbyxxxxの返り値と同じ方法で処理すればOKです。
end;
WSAGetAsyncErrorってのを説明してませんでしたね。
function WSAGetAsyncError(Param: Longint): Word;
メッセージ引数のlParamからエラーコードを抜き出す関数です。
結果メッセージのlParamにはエラーコードが入っているんですが、他の情報も詰め込まれています。
で、その中からエラーコードだけを取ってくる関数というわけです。
この関数はwinsock.pasに関数そのものの記述もあります。どうやって入っているかみるとその簡単さに愕然とするかも知れません。(^^;;

この関数の返り値はWSAGetLastErrorと同じ体系のエラーコードです。
WSAGetLastErrorの返り値がIntegerでWSAGetAsyncErrorの返り値がWordです。
で、同じ体系ってのは、エラーコードはWordの範囲でしか使ってないってことです。
Win16でもWSAGetLastErrorの戻り値はInteger、つまりSmallIntなわけです。
この範囲に収まるようにエラーコードは決められているので、Word=SmallIntで表すことが出来ます。

さて、なかなか検索が終了しない場合の中断方法です。ユーザが「中止」ボタンを押した場合に呼び出しましょう。
function WSACancelAsyncRequest(hAsyncTaskHandle: THandle): Integer;
hAsyncTaskHandleにはWSAAsyncGetHostByNameの返した値を設定します。これでどの検索が中止なのかをシステムに伝えているわけです。
返り値は中断に失敗したかどうか。中止に失敗したとしても、何かリカバー方法ありますか?
私ならデバッグ以外でチェックしません。(^^;;

これでWinsockの部分はいいとして、これをコンポーネントに押し込まなければなりません。
コンポーネントの作り方をさらっと流すと、まずTComponentから派生したクラスを作ります。
type
TKHostInf=class(TComponent)
end;
この時、usesにClassesを加えます。TComponentがClassesで宣言されているからです。

次に、Registerという手続きを宣言します。
procedure Register;
この手続きはだいたいいつも同じです。
procedure Register;
begin
  RegisterComponents('KWSockCmpl', [TKHostInf]);
end;
RegisterComponentsの最初の引数に「デフォルトのコンポーネントパレット」、2番目の引数に「コンポーネントの型」を設定して呼び出します。
2番目の引数には[TKHostInf,XXX,YYY]といった形で複数設定できます。

コンポーネントのコンストラクタCreateはpublic節に決まった型で書かなくては駄目です。
constructor Create(AOwner:TComponent); override;
overrideも必要ですよ。
基本的にオーバライドしたものは、inheritedで親の処理も呼ばなきゃなりません。

基本的にはこんだけですが、イベントの追加方法も説明しておきましょう。
まずイベントの型を宣言します。これはコンポーネントの宣言より先に行います。
type
TKWSockErrEvent=procedure (Sender: TObject; ErrCod:Integer) of object;
最後のof objectが重要です。
また、イベントの最初の引数は慣習的にSender: TObjectです。
あとは自分の好き勝手に引数を決めます。

これをprivate節に変数として取ります。
published節にはpropertyとして宣言します。
type
TKHostInf=class(TComponent)
private
  FOnError: TKWSockErrEvent;
published
  property OnError: TKWSockErrEvent read FOnError write FOnError;
end;
で、必要な時にFOnFindに何か設定されていれば呼び出すわけです。
if Assigned(FOnError) then FOnError(Self,WSAGetLastError);
今度はコンポーネントのアイコンを用意しましょう。
イメージエディタを使って、ユニット名と同じ名前の.dcrを作ります。
Bitmapを新規作成し、この名前をコンポーネントの型名をすべて大文字にしたものにします。
大きさは24x24、色は16色がいいでしょう。
これをユニットと同じディレクトリに置いとけば、コンポーネント登録する時にDelphiが認識してくれます。

ものすごく簡単に流しましたが、判るでしょうか。
まねして作れば割とコンポーネントを作るのは楽なものです。
基本的にはTComponent派生のクラスを作るだけだし、Registerをコピーして使い回せばいいんだし。
まぁ、マニュアルを読んでみてください。コンポーネントを作るのはそんなに難しい話じゃないです。

これで、非同期検索の「IPアドレスと本名表示」用のオブジェクトを作る準備ができました。
それがこれ。
[ソースダウンロード]
今回から、wssmplユニットがコンポーネントを含むようになりました。
wssmpl.pasのコンポーネントのインストールを行ってからコンパイルしてください。

-実行例-
ボーランド(米国)WebサーバーのIPアドレスと本名です。
実行イメージ

次の話
[表紙] [Program Files] [オブジェクト指向異聞] [プログラム未整理知識] [Winsockを使ってみようぜ] [だべり] [What's New] [書いた奴] [リンク]