TWebBrowser での POST

きちんと確かめてなかったんでハマりました。

The post data specified by PostData is passed as a SAFEARRAY Data Type structure.
The VARIANT should be of type VT_ARRAY and point to a SAFEARRAY Data Type.
The SAFEARRAY Data Type should be of element type VT_UI1, dimension one,
and have an element count equal to the number of bytes of post data.

PostData のところは VT_STRING ではなくて Byte の VT_ARRAY じゃないといけないです。あと,Delphi の場合,Navigate の第1引数以外は var OleVariant なので(C++ でいう & 参照体),定数を直接与えられないです。

ということでサンプルコード。

procedure TForm1.Button1Click(Sender: TObject);
var
  str: AnsiString;
  p: PChar;
  vFlag, vTarget, vPostdata, vHeaders: OleVariant;
begin
  str := 'user=hogehoge&password=fugafuga';

  vPostdata := VarArrayCreate([0, Length(str) - 1], varByte);
  p := VarArrayLock(vPostdata);
  try
    Move(PChar(str)^, p^, Length(str));
  finally
    VarArrayUnlock(vPostdata);
  end;

  vFlag     := 2 or 4 or 8; 
  vTarget   := Null;
  vHeaders  := 'Content-Type: application/x-www-form-urlencoded'#13#10;

  WebBrowser1.Navigate('http://example.com/', vFlag, vTarget, vPostdata, vHeaders);
end;

Shaw Communicationsを参考にしました。

ミソは,

  • PostData は VarArrayCreate() で Array Variant を作る
  • よくサンプルで for ループでまわして文字列を配列化してるのがあるけど,パフォーマンスがよくない。
  • だから VarArrayLock でロックして memcpy する(そうしていいよって Delphi のヘルプにも書いてある)
  • Move() は memcpy と引数の順番が違うし,ポインタではなく実体のほうを渡さなくちゃいけないので ^ を使ってる
  • Delphi の文字列は C でいう文字列と違う。PChar(str) というのはキャストしているのではなく,文字列のバイト列へのポインタを取得しているととらえればいいかな。
  • 2 or 4 or 8 というのは navNoHistory*1 | navNoReadFromCache | navNoWriteToCache。これらの定数が shdocvw で定義されてなかったんで。
  • Null というのは nil とは違う。VT_NULL を返す関数。
  • 経験上 Content-Length をヘッダに含めるとうまくいかない。

*1:これを定義するとブラウザの Forward, Back が効かなくなります。ということはヒストリ分ちょっと省メモリになるかな?