末尾へ



20020619

さて、ネットワークでもいじくってみるかな。

実は、DynDNS IP updaterがエラーを出して落ちてしまう。それで、自分で作ってみようという気になった。

Wired-Winsockを使ってみようぜ で暫く、WinsockのDelphiでの使い方をお勉強。いつまでかかるやら。



20020621

さて、やっと週末。ちょっと悟った事は、焦って、どこにAPIがあるかなどは気にしないことにした。Delphiの関数、サブルーチンは、Delphiがもっているし、Win32APIはWindowsがもっている。それにどんな機能があるかは、まあ、本屋で立ち読みでもしよう。作りたいソフトができたら、Webで探せば良い。そんな気分である。

幾つか分かり難いところがあった。

型キャスト

localHostName : String;
  ・・・
gethostname(PChar(localHostName),512)


gethostnameはWinSockのAPIだから、文字型は当然PCharである。Delphi内部の文字はString。で、上の様にString型(であるlocalHostName)をPChar型で包んでgethostnameに与える。返って来る値は、PCharであるが、その皮を剥けばString型の値が現れる。。

型というものは、入れ物だと思えば分かり易い。String型と言わずに赤い箱(形は球としようか)と言おう。同様に、PChar型は青い箱。この箱の中に文字が入る。

Delphiは赤い箱しか開けられない。Windows系は青い箱しか開けられない。

こんな世界かな
  Delphiの世界 String    

−−−−−−−−−−−−−−−−−−−−−

  Windowsの世界 PChar  


このままでは、Delphi世界とWindows世界は貿易ができない。それで、入れ物に袋を被せて相手の色に似せる事にする。

こんな風に
  Delphiの世界 String                       
         
  Delphiの世界 PChar(String)  青い袋を赤い箱に被せる 
−−−−−−−−−−−−−−−−−−−−−−−−−−−−
  Windowsの世界                          

Windows世界はこれでだまされて、青い箱の中の文字列を処理してDelphiに返す。Delphi世界では、青い袋を剥がせば(PChar()を剥がす)、赤い箱(String)が現れるという寸法。

gethostname(PChar(localHostName),512)の意味はそんな所。型キャストとかいうらしい。castって、鋳型の事。


ビッグエンディアンとリツルエンディアン

まあ、何とペダンティックな分かり難い名前なことだ。

endianは、end-ian。で、endは、終わりではなく、端っこ。ianは? Indian, Canadian, Iranianといえば分かるかな。つまり端っこ人というスイフトの造語なのだ。だから、小さな辞書なんか引いても載っていない。

卵って、楕円だけれど、尖っている方の端っこと、比較的丸っこい方の端っこがある。で、尖っている方の端っこがlittle end、丸くて大きい方がbig end小さい端っこ人;リツルエンディアンと、大きい端っこ人;ビッグエンディアンだね。

もう忘れてしまったが、ガリバー旅行記に出てくる小人には2種族あって、卵を尖った端から食べる種族little-endianと、丸い大きい端から食べる種族big-endianがあるそうな。で、上の意味はもうわかりますね。

そこから、数字の大きい桁から取って行く、あるいはネットワークに送り出す方式をbig-endian、逆がlittle-endianと言う。

network byte orderはbig endianなのに、Pentiumは、little endianなものだから面倒でしょうがない。今となっては、big endianなMPUを作っていたモトローラが負けてしまったのが惜しい。プログラミングが楽になったはずだ。というか、こんな事も知らなくて良かったはずだ。何も考えずにできるから。

1234なら、1が千の桁、2が百の桁、3が十の桁、4が1の桁だから、big-endianなら1234の順にネットワークに送り出す。little-endianなら4321だね。

よく、little-endianは3412の順だと書いた本がある。これは間違いではないけれど、endianの概念を説明している時には読者を混乱させるだけだ。正解は、4321です。

では、なぜ、3412のような書き方をするか?これは、byte単位ということとゴッチャに説明しているから(byte単位でネットワークに送出し、かつ34、及び12を16進数の1byte表現とみなせばこれであっている)。コンピュータでは、1byteを単位として扱う。で、ABCDという4byteがあるとしよう。Aが大きい桁、Dが小さい桁の側とする。そうすると、

 big-endian; ABCD
 little-endian; DCBA

問題は、Byteを
16進数 であらわすことにある。たとえば、11110000はF0。 つまり、Aを11110000とすると、この1byteはF0と書く。Bを00001111とすると、これは0FABという並びは、この16進数で表記すればF00F

A  B
F0 0F
これを
big-endianで送り出せば、ABだから、 F00F
little-endianで送り出せば、BAだから、 0FF0であって、F00Fではないのだね。
endianの対象は1byte単位なのだから。




gethostname、gethostbyaddr、ホスト名

こける Wired-Winsockを使ってみようぜ-3.住所と氏名  によると
このIPアドレスをgethostbyaddrを使ってホスト名を調べると、プロバイダが接続時に自分のコンピュータにつけたホスト名が判ります。 通常にgethostnameとgethostbynameだけ使って本名を調べても、gethostnameと同じ物が返ってくるだけです。一旦IPアドレスに変換しないとだめです。

という事である。これは、gethostnameがWindowsの中の世界=レジストリ参照だけで解決されているからだろう。当然、コンピュータ名=NetBIOS名となる。また、gethostbynameでは、多分、名前解決をする順が決まっていて、
 1.まず自分自身かどうかをレジストリで調べる
 2.自分自身でなければ(インターネット上の)DNSサーバに問い合わせにいく

従って、上のようになる。gethostbynameでは、いきなりDNSに問い合わせにいくと、その名前は解決できない場合がある。つまり、お金を出して固定IPアドレスとドメインネームを買い、NIC(NetworkInformationCenter;日本ならJPNIC)に届けを出した覚えのない人達(固定IPアドレスを持たず、ISPからDHCPで毎回IPアドレスをもらっている人、つまりほとんどの個人ユーザ)のPCは当然DNSサーバに登録されていないからDNSで調べても、見つからないのである。そんな事態を防ぐ為に、上のような順に調べるのだろう。

ところで、gethostbyaddrも、同様にまず自分のNIC(こちらはNetworkInterfaceCard;イーサアダプタ等の一般呼称)に振られたIPアドレスをレジストリで調べてもいいが、グローバルIPアドレスは必ずDNSに存在するのだから、gethostbynameのように失敗することはない。失敗したら、それは本当にそんなIPアドレスを持つホストは存在しないという意味である。だから、いきなりDNSサーバを調べるのだろうと思う。

DNSサーバ(DHCP使用時は、最初に調べるのはISPのDNSサーバである)に逆引きで問い合わせに行き、そこからホスト名をもらうから、ISPが与えたホスト名になる。 逆引きは、IPアドレスを逆にして、更に、.in-addr.apraを付けて引くという約束事になっている。192.168.0.1なら、次のようにして引いているが、こんなことは別にプログラマは知らなくても良い;1.0.168.192.in-addr.apra
BINDのような、DNSサーバを建てる人には必要な知識である。



20020622

ここに至るまでに実は、先延ばししておいた分からないことがある。

http://www.asahi-net.or.jp/~nk2w-ishr/winsock2.htm

終了際に使うのが、WSACleanupです。 function WSACleanup: Integer; 返り値が0なら成功、エラーの場合はSOCKET_ERRORです。

0は、integerでしょ。SOCKET_ERRORはどうみてもStringじゃないの。これって可笑しい。 多分、言いたいことは、2つの可能性しかない;
1.SOCKET_ERRORは宣言されていないから、予約語で、integer型になっている

2.返り値が0なら成功、エラーの場合は0以外のintegerで、その意味は、SOCKET_ERRORです。

2.の場合、形式と意味がゴッチャに説明されているので、初心者は途惑ってしまう。どっちなんだろう。

FillChar(Addr,sizeof(Addr),0);

これの説明がないが、Addrという何かしらないが、領域を、0(zero)でclearするのかな。
尚、
Addr: TSockAddr;

Wired-Winsockを使ってみようぜ を参考に、errorチェックも何もほとんどなし、とにかくつながるコードをちょっと書いてというかコピーして試してみたのが以下;
どこまで動いているか、Labelに出そうと思って、あちこちにLabel3.Caption :='gethostbyname ok'などと書いてみたが、同期方式では、スレッドを使わないと、これが実時間で表示されない。受信が終了するまで、何も表示されないので役にたたない。
例によって、インデントは無し。

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, winsock, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
RecvData: string;
ErrorCd: Integer;
end;
var
Form1: TForm1;
WSAData : TWSaData;
implementation
var
WSAStartupResult : Integer ;
initializeErr : String;
localhostname : String;
hostname : String;
phe : PHostEnt;
Socket1: TSocket;
 Addr: TSockAddr;
ip: u_long;
port: WORD;
FState: Integer;
len: integer ;
const
SocketErr= 'Socket Error' ;
localHostNameErr ='Get LocalHostName Failure' ;
HttpPort = 80;
{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
label3.caption:='Start' ;
//**************************
// Error Check
//**************************
if (WSAStartupResult <> 0) or (WSAData.wVersion <> $0101) then
begin
Label1.Caption := InitializeErr ;
exit;
end;
//****************************
// ShowLocalHostName
//****************************
Label3.Caption := 'Socket ok' ;
SetLength(localhostName,512) ;
if gethostname(PChar(localhostname),512) <>0 then
begin
Label1.Caption:=LocalHostNameErr;
exit;
end;
Setlength(localhostName, StrLen(PChar(localhostName)));
Label1.Caption := localhostname;

//****************************
// GetHostByName & GetHostByAddr
//****************************
label3.Caption :='getHostByName開始';
hostname :='www.rtpro.yamaha.co.jp'; //これはチェック用

phe := gethostbyname(PChar(hostname));
label3.Caption := phe^.h_name ; //Show FQDN
// Show IP Address
label3.Caption := hostname+'='+inet_ntoa(PInAddr(phe^.h_addr_list^)^);
//Get IP address
ip:=PInAddr(phe^.h_addr_list^)^.s_addr;

//GetHostByAddr
//hostname :='216.239.33.100'; //これはチェック用
//ip := inet_addr(PChar(hostname));
//phe := gethostbyaddr(@ip,4,AF_INET);
//label1.Caption := hostname+'='+phe^.h_name;
//****************************
// Create Socket
//****************************
// label3.Caption :='CreateSocket開始';
Socket1 := socket(AF_INET,SOCK_STREAM,0); //引数はこれしかないので、お呪い
//error check
if Socket1=INVALID_SOCKET then
begin
Label1.caption:='ソケット作成のエラーです Error
code'+IntToStr(WSAGetLastError);
Exit;
end;
//****************************
// Connect
//****************************
Port := HttpPort; //試験用 ;IPは上でGoogleがset済み
FillChar(Addr,sizeof(Addr),0); //addrを0 clear??
Addr.sin_family:=AF_INET;
Addr.sin_port:=htons(Port);
Addr.sin_addr.s_addr:=ip;
if connect(Socket1,Addr,sizeof(Addr))=SOCKET_ERROR then
begin
label1.caption:='接続のエラーです Error code '+
IntToStr(WSAGetLastError);
closesocket(Socket1);
FState:=0;
Exit;
end;
//****************************
// Receive
//****************************
SetLength(RecvData,8*1024);
len:=Recv(Socket1,RecvData[1],Length(RecvData),0);
if len=SOCKET_ERROR then
begin
if WSAGetLastError<>WSAEINTR then
ErrorCd:=WSAGetLastError;
RecvData:='';
Exit;
end;
SetLength(RecvData,len);
label1.caption:= '受信データ = '+RecvData;
label2.caption:= '受信データサイズ = '+InttoStr(len);

end;

initialization
WSAStartupResult := WSAStartup($0101, WSAData);
finalization
closesocket(Socket1); //事前にpacket送出完了を確認の事
WSACleanup ;

end.




20020623 AM

前回は、一応、

  名前解決→ソケット作成→コネクト→受信

まで書いてみたが、ナントモハヤ、本質的でない部分や変換が多くてグチャグチャな設計で、閉口した。ネットワークプログラミングは、もっと専用の言語を設計した方がいいよ。Javaもその走りだが。

とにかく受信はしたが、これでは、DynDNSのIPアドレス書き換えには程遠い。気が遠くなりそうだ。もっとモジュールを探さないと、なんともならない。

ソケット通信 が、整理されていて、考え易かった。同期型と非同期型のお勉強はここが良い。

見つけた!!
Synchronous; TCP/IP Library for Delphiで、HTTPclientのコードをダウンロードした。

TCP/IPsuiteがある。ここはチェコなので、欧州言語対応何とかのインストール窓が開くが、中身は英語だから、インストールの必要はない。<BR>
現時点では、2002-05-08 (size: 505k) - release no. 28が全コードを含んでいる。500KBくらいだから、これを落としておけば良い。他は目的別にこれをバラしたもの。

http://www.nn.iij4u.or.jp/~showtake/win95.htmは、Win95のupdateに関する記録。

Winsockのバージョン情報など。
具体的なWin95ネットワークのupdateの仕方はここ


ところで、上のSynapseには下記のようなFAQがあった。2002.0612 21:50SendKeys では、こういうことが書いてなかったので、コンポーネントとしてインストールしようとして(これが一見できてしまうからDelphi初心者には困るのである)トラブった。

Synapse\html\index.htmには次の記述がある。

How can I install Synapse components?

Synapse is NOT components. It is only library units with code and classes. It cannot install, it not need install! For using synapse you can copy all neccessary Synapse units to your project directory (or to your library search path) and in your project add USES line with refference to needed Synapse units.


簡単に訳せば、Synapseはコンポーネントではないからインストールはできないし、その必要もない。必要なものをProjectフォルダにコピーするか、Delphiのサーチパスが 通っている所に置いておき、あなたが作ったprojectのUSESに、必要なSynapseのUnit名をかけば良い。

また、Synapse\readme.txtにもこうあり、非常に親切だ。

4.) Usage notes

Simply write BLCKSOCK to USES section in your source code (or any other unit from package, when you need it). To read documentation, simply open INDEX.HTM file (in HTML subdirectory) on your HTML browser.

Synapseは、全て同期方式で書いてあるから、スレッドを作らないと、通信中固まってしまう。ということは、これを使う限り、部分を非同期モジュールを使っても無駄なのだろうな。


Try, Exception,Finally文が最近良く出てくるので、ここ でお勉強。


20020623 PM

早速Synapseをフォルダー毎DelphiのLibフォルダに放り込み、Delphiを起動。

 ツール→環境オプション→ライブラリ→ライブラリパス

に、ソースの位置を書き込んだ。ここには、すでに$(Delphi)¥Lib;$(Delphi)\Bin;...などとパスが書き込まれているので、$(Delphi)¥Lib¥Synapse¥Source¥Libを書き込んだ。

で、 $(Delphi)¥Lib¥Synapse¥Source¥Demo¥Http\Httpdemo.dprをDelphiから読み出して、実行させてみた。こんな画面 がでる。これは使えそうである。DynDNSにアクセスしてIPアドレスを書き換えるにはピッタリだ。


20020623(日)

壁に当たってしまった。ちょっと簡単には解けそうにない。大体、Webで調べるにしてもどんなキーワードで調べたら良いかが思い付かない。

DynDNSへのloginはPOSTメソッドではなかった。loginを押すと、loginウィンドウがポップアップしてくる。これはActiveXかJavaか?ところで、そのpopupしてくるURLに上のHTTPモジュールでアクセスしても、当然、popupして来ない。ActiveXにしてもJavaにしても読み込んだHTMLファイルを解釈して、実行するのだが、Synapseは通信モジュールだけで、ブラウザのインタプリタ機構がないから、実行されず、従ってlogin窓がpopupしない。

もう少し、この周辺の知識がつくまでこの目標は保留。

IPアドレスを始終、デスクトップに表示するものでも作ってみよう。次なる目標は、
 ・バックグラウンドで実行
 ・タスクトレーにアイコンをいれる
くらいかな。これならどこかで見かけた。

これまでのやりかけのコード

unit dyndns;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
HTTPSend, IniFiles, Winsock, StdCtrls, ExtCtrls ;
type
TForm1 = class(TForm)
Button1: TButton;
Header: TMemo;
Contents: TMemo;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
HTTP: THTTPSend;
IniFile: TIniFile;
// DynDNS.ini
ipcheckURL: String ; //自分のIPアドレスを調べるサイト
DynDNSURL:String ;
ID : String ;
Passwd: String ;
hLoginP: HWND ; //Parent Window
hID : HWND; //ID Window handle
i : integer ; //counter
j : integer ; //counter
k : integer ; //counter
NewIP: String ; // 新しく登録するIPアドレス
OldIP: String ; // 以前登録したIPアドレス
st: TMemoryStream;
Key : Byte ;
const
IniErr='ERROR';
IPNotFound ='貴方のIPアドレスが見つかりません。 ' ;
IPNotFound2 ='で探しました。' ;
iniErrURL ='貴方のIPアドレスを提示するURLがdyndns.iniにありません。';
iniErrDynDNS ='DynDNSのURLがdyndns.iniにありません。';
iniErrID ='IDがdyndns.iniにありません。';
iniErrPasswd ='Passwdがdyndns.iniにありません。';
hLoginPerr ='Login窓がみつかりません。';
hIDerr ='ID入力窓がみつかりません。';

begin
//*********************************
// DynDNS.ini fileの読み込み
// IP,
//*********************************
IniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini')
);
//***************************************************
//自分のIPアドレスを教えてくれるURL
// URLは次の形式のIPアドレス文字列 である事が必要
// 'IP Address:xxx.xxx.xxx.xxx' 'IP address:xxx.xxx.xxx.xxx'
// case-sensitiveである。
// ':'の次の空白は0以上,幾つでも良い
//***************************************************
IPcheckURL := IniFile.ReadString('checkIP','IPcheckUrl',iniErrURL);
// IPcheckURL:='http://checkip.dyndns.org/' ;
// IPcheckURL:='http://www.rtpro.yamaha.co.jp/cgi-bin/client-ip/';
DynDNSURL := IniFile.ReadString('login','DynDNSURL',iniErrDynDNS);
ID := IniFile.ReadString('login','ID',IniErrID);
Passwd := IniFile.ReadString('login','Passwd',IniErrPasswd);
IniFile.Free;
if IPcheckURL = iniErrURL then
begin
Label1.Caption := iniErrURL ;
exit;
end;
if DynDNSURL = iniErrDynDNS then
begin
Label1.Caption := iniErrDynDNS ;
exit;
end;
if ID = iniErrID then
begin
Label1.Caption := iniErrID ;
exit;
end;
if Passwd = iniErrPasswd then
begin
Label1.Caption := iniErrPasswd ;
exit;
end;
//*********************************************
// http://checkip.dyndns.org/
// から自己IPアドレス画面取り込み
//*********************************************
// このセッションは、synapse\source\demo\httpの中のUnit1.pasを覗いた
HTTP := THTTPSend.Create;
try
HTTP.HTTPMethod('GET', ipcheckURL);
Header.Lines.Assign(HTTP.Headers);
Contents.Lines.LoadFromStream(HTTP.Document);
finally
HTTP.Free;
end;
//*********************************************
// http://checkip.dyndns.org/ あるいは
// 'http://www.rtpro.yamaha.co.jp/cgi-bin/client-ip/'
// からContentsBufに取り込んだ自己IPアドレスを抽出
// 例; IP Address: 133.123.12.34
// 将来はPosは止めて、case-insensitiveにしたい。
//*********************************************
i := pos('IP Address:',Contents.Text); //Posはcase-sensitive
if i=0
then //文字列'IP Address:'が見つからなかった
begin
i := pos('IP address:',Contents.Text); //Posはcase-sensitive
if i=0
then //文字列'IP address:'が見つからなかった
begin
Label1.Caption := IPNotFound+IPcheckURL+IPNotFound2 ;
Exit;
end;
end;
i :=i+11; //'IP Address:' の':'の次の空白の位置
while Contents.Text[i] = ' ' do i := i+1 ;// skip spce
// IP アドレスを抜き出す
FillChar(NewIP,sizeof(NewIP),0); //zero clear?
j:= i; // iはIPアドレスの先頭。copyの引数に使うので保存
k:= 1;
repeat // get IP addresse
begin
k := k+1 ;
j := j+1 ;
end ;
//LF = #10, CR = #13. UNX: LFのみ。Win:CRLF。16進数を使うとエラー!!
until (Contents.Text[j] = #13) or (Contents.Text[j] = #10) or (k>16);
NewIP:= copy(string(Contents.Text),i,k);
//For debug
ShowMessage(NewIP) ;
//*********************************************
//IP アドレスが正しく、数値であることを確認
//*********************************************
if (inet_addr(PChar(NewIP)) = INADDR_NONE) then
begin // エラー;IPアドレスではない
Label1.Caption := IPNotFound+'数値ではない' ;
exit ;
end;
//*********************************************
// https://members.dyndns.org/nic/login
// にアクセスして、login
// 前回登録の自己IPアドレスを抽出
//*********************************************
HTTP := THTTPSend.Create;
try
HTTP.HTTPMethod('GET', DynDNSURL);
Header.Lines.Assign(HTTP.Headers);
Contents.Lines.LoadFromStream(HTTP.Document);
finally
HTTP.Free;
end;
//***********************************
// Input ID Windowのハンドルを取得
//***********************************
hLoginP := 0;
i :=0;
//ID入力窓の親ウィンドウハンドル取得
repeat
sleep(200) ; // wait for appearing login window.本当に要るの?
hLoginP:=FindWindow(nil,'ネットワーク パスワードの入力');
i := i+1 ;
until (hLoginP<>0) or (i> 100); //100 は20秒分起動を待つ
if hLoginP =0
then
begin //errorlogin window not found. handle not obtained
Label1.Caption := hLoginPerr ;
ShowMessage(hLoginPerr) ;
exit ;
end;
//**************************************
//    ID入力
//**************************************
//loginウィンドウをアクティブにする。
SetForegroundWindow(hLoginP);
hID := 0;
hID:=FindWindow('edit',nil);

if hID =0
then
begin //errorID window not found. handle not obtained
Label1.Caption := hIDerr ;
ShowMessage(hIDerr) ;
exit ;
end;
//キーを送るウィンドウをアクティブにする。
SetForegroundWindow(hID);
//IDを入力
i:= 1;
while i<=Length(ID) do
begin
Key := VkKeyScan( ID[i] );
keybd_event(LoByte(Key), 0, 0, 0);
keybd_event(LoByte(Key), 0, KEYEVENTF_KEYUP, 0);
Sleep(50);
i:= i+1;
end;
//[Shift]キーを押してPasswd入力に移る
keybd_event(VK_SHIFT, 0, 0, 0);
//[SHIFT]キーを離す
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
//Passwdを入力
i:= 1;
while i<=Length(Passwd) do
begin
Key := VkKeyScan( ID[i] );
keybd_event(LoByte(Key), 0, 0, 0);
keybd_event(LoByte(Key), 0, KEYEVENTF_KEYUP, 0);
Sleep(50);   //要るのかネエ?BR> i:= i+1;
end;
//[Enter]キーを押す
keybd_event(VK_RETURN, 0, 0, 0);
//[Enter]キーを離す
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
//*********************************************
// http://www.dyndns.org/ で
// IPアドレスの変化をチェック
//*********************************************


//*********************************************
// http://www.dyndns.org/
// 変化があれば書き込み
//*********************************************
{ //以下はSynapseからのdead copy。未完成
procedure TForm1.Button2Click(Sender: TObject);
var
st: TMemoryStream;
begin
st:=TMemoryStream.Create;
try
HTTPpostURL(Edit2.Text, Edit3.Text + '=' + Edit4.Text, st);
st.Seek(0,soFromBeginning);
Memo2.Lines.LoadFromStream(st);
finally
st.Free;
end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
st: TFileStream;
begin st := TFileStream.Create(Edit7.Text, fmOpenRead or fmShareDenyWrite);
try
HTTPPostFile(Edit5.Text, Edit6.Text, ExtractFilename(Edit7.Text), st,
TStringList(memo2.Lines));
finally
st.Free;
end;
end;
} //未完成
end;
end.




20020625

上のコードは自分のIPアドレスを求めるのに外部のWebにアクセスしている。その種のCGIをもつHPは幾つかある。勿論、ADSLモデムにPCが直結なら、WindowsのAPIで求められるが、間に幾つかローカルルータがあるとその手はつかえない。モデムに直結している最初の入り口のルータに割り振られたIPアドレスをどうやってもとめるかの問題である。まあ、上の手が、ルータ依存にならず一番簡単である。

ルータにはたとえば、192.168.0.1を自分の内側(LAN側)のIPアドレスとしてもっていて、TelnetやHTTPでアクセスを許す物が多い。だから、外のHPにまで出て行かなくても解決できるが、これはルータ依存の方法になるので止めた。

で、思い出したが、居候のルータはHTTPでアクセスすると、DynDNSと同じ「ネットワークパスワードの入力」というウィンドウを開く。これをブラウザ無しで、開かせる方法があれば、昨日の問題は解決するが、それが他のサーバを試験台に何度もアクセスして迷惑をかけるということ無しにできるので、もう少し考えてみよう。

見つけた。
VBユーザーのためのWindows API にこんな記事があった。

 ・ネットワークリソースの接続/切断
 ・ネットワークリソース接続/切断 API
 ・WNetAddConnection3

これを調べればなんとかなりそうな気がする。
こんな記述がある;
CONNECT_INTERACTIVEを指定すると、パスワードを設定されているネットワークリソースに接続する場合、必要に応じてユーザー名/パスワードの入力を求めるダイアログボックスが表示されます(図4、図5)。

ということは、パスワードを求める窓は、ActiveXなんかじゃなく、 Windowsが直に出しているのだ。それは、最初、Webサーバに繋いだ 時に、INTERACTIVE=対話モードで、パスワードが要るよって、Webサーバに言われたIEは、WNetAddConnection3というWin32APIで、問題の窓をだすのだね、きっと。これなら、できそうだ。

synapse\html\docs\help\THTTPSend.HTTPMethod.htmlに次の記述があるので、パスワード入力窓を出せるのだろう。

function HTTPMethod(Const Method, URL: string): Boolean;
Unit
HTTPSend
Description
Connects to host define in URL and access to resource defined in URL by method. If Document is not empty, send it to server as part of HTTP request. Server response is in Document and headers. Connection may be authorised byt username and password in URL.


2002.0629

ちょいと躓いたままなので、気分直しにブラウザを作って、遊んでみた。

ダウンコンバータを使って30インチTVにPCの画面をだすと、解像度が低いので何かと辛い。おまけにIEは、ゴッチャゴチャの作りで、子供やお年寄りにはあのボタンやメニューが邪魔である。そこで、10分位で、懐メロ専用ブラウザを作ってみた。別にネットワークプログラミングは必要ない。凄く大きな塊のモジュールになっているから、それこそ、インターフェイスを作るだけ、つまり、コンポーネントをフォーム=ウィンドウにベタベタ貼り付けるだけで済んでしまった。

下記を参照させて頂いた。
http://delphi.sakura.vg/index2.html

欲しい方はここ にあります。遊びですよ。URLもハードコーディングしてあるし。
zip圧縮なので、解凍して、適当なフォルダに置いて、起動すれば、ブラウザが開きます。それだけ。右の方にCDのアイコンが幾つかならんでいるのがURLs。


2002.0630

昨日作ったブラウザはCDアイコンできめうちのURLに飛んでいく方式なので、URLを手でいれる必要はないが、一応、Editなので入れられる。なのに、Enterでは検索にいかないで、警告音がでるだけでトッテモかっこ悪い。

http://www.borland.co.jp/qanda/delphi/d0003345.html
によると;

TEdit コンポーネント上で Enter キーを押すとビープ音が発生します。
このビープ音を消すにはどうしたらよいでしょうか。

A:TEdit の OnKeyPress イベントで Enter キーの処理を行いパラメータの Key に #0 を代入します。シャープ記号(#)の後に 0 から 255 までの符号なし整数定数を続けると,対応する ASCII 値の文字が表されます。
Enter キーが押されると Key には VK_RETURN が渡されて通常はビープ音が発生しますが, #0 を指定することで Key に null を代入することとなりビープ音を抑制できます。

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if (Key = Chr(VK_RETURN)) then
Key := #0;
end;


ということなので、何とかインスペクタのEdit1のイベントのOnKeyPressの右側をダブルクリックして、
  procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
を書き込んでもらい、Goボタン(Edit窓の右側にある地球と虫メガネアイコン)をクリックした時のコードをここにコピーした;


procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if (Key = Chr(VK_RETURN)) then
begin
Key := #0;
if Edit1.Text ='' then
begin
Edit1.Text :='about:blank' ;
end;
AI.Navigate(Edit1.Text);
end;




今度は、Chrが分からない; http://www.wwlnk.com/boheme/delphi/vbtodel/daf0110.html によれば;

Chrは文字コードを渡すとそれに対応する文字を返す関数です。
Delphiでも同じ名前の関数、Chrを使用します。

■ Chrの使用例
  procedure TForm1.Button1Click(Sender: TObject); 
  var
    ix  : integer;
  begin
    // A〜Kの文字をメモに追加します。
    for ix  :=  65  to  75  do  begin
      Memo1.Lines.Add(Chr(ix));
    end;
  end;

戻ってくる文字の型は String型ではなく Char型なので注意してください。



ということである。コンピュータができて50年を何年も超えているのに、言語/ソフトの世界の遅れは如何ともし難いネ。こういう本来、不要な変換ばかりが、減るどころか増えている。昔、Fortranなどという汚い言語は無くなるのが早ければ早いほど良いと言われたが、Fortranどころの話じゃない。どれもこれも汚さが日増しに増えている。これがMSを始めとして、バグだらけのソフトを生産する原因なのだろう。


2002.07.03

やっと、パスワードが出る構造が分かった。それからWinSockやSynapseなど使わずにWinInetを使うのが、HTTPやFTPなら簡単そうなことも Wininet Programingの部屋で分かった。

IDやパスワードをJavaやCGIではなくWindowsのAPI呼び出しで行っている所まではわかったが、この時、HTTPdから受信しているデータはアクセスエラーの通知だけである。勿論、HTMLでWindowsのAPIが呼び出せてしまっては、やりたい放題、セキュリティも糞もないので、何か間接的にパスワードを要求しているのだろうと、6/25には推定したのだが、そこから先にすすめなかった。大体、SynapseのHTTPsendで接続してもパスワード要求窓は開かない。エラーメッセージを受信するだけである。ブラウザが窓を出しているのはわかるが、何を契機にして出しているのかが分からなかった。特別なメッセージがありそうもないのである。

Wininet Programingの部屋によれば、このアクセスエラーの通知を受け取ると、ブラウザがID/パスワード要求窓を開いて、それを取得した後、再度HTTPdに接続しているのだ。

IDとパスワードの必要なページへのアクセスを行います。
  認証の必要なページはステータスとして HTTP_STATUS_DENIED が返ってきます
  その後で、IDとパスワードを送信して再度リクエストを送ります。


つまり拒否されたら、ID/パスワードを送ってみようというセコイ方式なのだ。

今週末はこれでやってみよう。



2002.07.30

あ〜あ、一月近くなにもできなかった。担当している雑誌に大穴をあけそうになって、いや、穴ならいいが、欠巻にしそうになって、こんなところで遊んでいる暇はなかったのだTT。

やっとのことで印刷所に送り込んだのだけれど、その副作用で一月分の仕事が溜まってしまい、やんやの催促。もう鬱状態だ。で、軽い書き物で気を紛らわしている。とてもプログラミングなんか重い事をやる気分じゃない。さて、何時再開できるか。


私のホームページへ | SiliconValley-Cupertinoのページへ | メイン | 今すぐ登録