ホーム >プログラム >Delphi 6 ローテクTips >Windows APIメモ

Windows APIのメモ。


CommandLineToArgW

CommandLineToArgW APIは二重引用符で区切られた引数の場合、引数の最後の文字が'\'であるとおかしな動作になってしまいます。
具体的には'\'が消えて'"'が残ります。

"C:\Windows\"C:\Windows"

おそらく'\'はC言語などでは特殊な扱いになっているので'\'が次の'"'をエスケープしてしまい引数の区切りとしてではなく引数内の文字だという扱いになっているのだと思います。
その結果二重引用符で囲んだ引数のあつかいが期待通りにならなくなってしまいます。

"Documents and Settings\" "Program Files\" "WINDOWS\"

は、

'Documents and Settings\'
'Program Files\'
'WINDOWS\'

とはならず

'Documents and Settings" Program'
'Files"'
'WINDOWS"'

となります。

エクスプローラーからフォルダをドロップした場合は終わりの'\'はつかないのでそれほど問題にならないと思いますが、ショートカットファイルの引数を自前で書く場合や半角スペース区切りのデータを分割するのにこのAPIを利用する場合などには気をつける必要があります。

GetAsyncKeyState
function GetAsyncKeyState(vKey: Integer): SHORT; stdcall;
キーが押されているかどうかを判定するには、
function gfnbKeyState(iKey: Integer): Boolean;
begin
  //マウスの左右ボタンを入れ替えている場合に対処
  if (GetSystemMetrics(SM_SWAPBUTTON) <> 0) then begin
    if (iKey = VK_LBUTTON) then begin
      iKey := VK_RBUTTON;
    end else if (iKey = VK_RBUTTON) then begin
      iKey := VK_LBUTTON;
    end;
  end;

  Result := BOOL(Hi(GetAsyncKeyState(iKey)));
end;
とします。
入力フォーカスがないときでもタイマーを使ってこの関数を呼べばフックを使わなくてもキー入力を監視できます。
お手軽です。

上の例では最下位ビットの値は調べていませんが、きちんと最下位ビットの状態も調べて判定に加えるとよりよい判定ができるのではないかと思いました。
ということで実際にやってみた結果、マウスとキーボードで挙動が違うことが分かりました。
どちらもずっと押しっぱなしでやってみたところ Lo(li_Key) の値は、
マウスボタンの場合、押し始めの一回だけ1であとはずっと0が返ります。
キーボードのキーの場合、押し始めの一回が1であとはちょっとの間0が続き、その後ずっと1が返ります。

キーボードからの入力でキーを押しっぱなしにすると一回入力の後タイムラグがあり、その後ダダダダダと入力が続きます。
多分そのタイムラグ(ディレイタイム)なのであろうと思われます。
ということでキー入力のシミュレートをするにはこの最下位ビットを判定に加えるのが良いのでしょう。
function gfnbKeyState(iKey: Integer): Boolean;
var
  li_Code: SHORT;
begin
  //マウスの左右ボタンを入れ替えている場合に対処
  if (GetSystemMetrics(SM_SWAPBUTTON) <> 0) then begin
    if (iKey = VK_LBUTTON) then begin
      iKey := VK_RBUTTON;
    end else if (iKey = VK_RBUTTON) then begin
      iKey := VK_LBUTTON;
    end;
  end;

  li_Code := GetAsyncKeyState(iKey);
  Result  := BOOL(Hi(li_Code)) and BOOL(Lo(li_Code));
end;
キー入力のシミュレートが目的ではなく、単にキーやマウスボタンが押されているかを調べたい場合は最上位ビットだけの判定の方が良いようです。
最下位ビットの判定も加えると、キーボードの場合ディレイタイムの間は取りこぼしがありますしマウスボタンの場合最初の一回だけしか押されていると判定されないので使いづらいと思われるので。
GetPixel
function GetPixel(DC: HDC; X, Y: Integer): COLORREF; stdcall;
X, Yはクライアント座標。 
var
  lpt_Pos:  TPoint;
  lh_WND:   HWND;
  lh_DC:    HDC;
  li_Color: COLORREF;
begin
  GetCursorPos(lpt_Pos);
  lh_WND := WindowFromPoint(lpt_Pos);
  lh_DC  := GetDC(lh_WND);
  Windows.ScreenToClient(lh_WND, lpt_Pos);
  li_Color := GetPixel(lh_DC, lpt_Pos.X, lpt_Pos.Y);

  if (lh_Color <> CLR_INVALID) then begin
    Beep;
    Label1.Color := li_Color;
  end;

  ReleaseDC(lh_WND, lh_DC);
クライアント領域外の色は取得できません。
例えば上の例では、タイトルバーやメニューバー、フォームのフレームなどはクライアント領域外なのでNG 。
lstrcpyn lstrcpynW
function lstrcpyn(lpString1, lpString2: PChar; iMaxLength: Integer): PChar; stdcall;
function lstrcpynW(lpString1, lpString2: PWideChar; iMaxLength: Integer): PWideChar; stdcall;
iMaxLengthはプラットフォーム SDKによると
iMaxLength
lpString2 パラメータが指す文字列から、lpString1 パラメータが指すバッファへコピーするべき文字の数を TCHAR 単位で指定します。終端の NULL 文字分も含めてください
となっています。
終端のNULL文字も含めてとなっているので、
  lstrcpyn(lp_Buff, PAnsiChar(ls_Str), Length(ls_Str) +1);
のようにコピーする文字数 +1(終端のNULL分)としなければなりません。

単純に、
  lstrcpyn(lp_Buff, PAnsiChar(ls_Str), Length(ls_Str));
のようにしてしまうと最後の一文字が欠けます。
SystemParamtersInfo
function SystemParametersInfo(uiAction, uiParam: UINT; pvParam: Pointer; fWinIni: UINT): BOOL; stdcall;
引数fWinIniは,システムパラメータの設定をユーザープロファイルとして更新するかどうか,また更新する場合はすべてのトップレベルウィンドウへ送信して,変更が生じたことを通知するかどうかを指定します.どちらも行わない場合は0を指定します.
SystemParamtersInfo関数のfWinIni引数の値
SPIF_UPDATEINIFILE システム全体のパラメータに関する新しい設定を,ユーザープロファイルに書き込む.
SPIF_SENDCHANGE ユーザープロファイルを更新した後,WM_SETTIMGCHANGEメッセージをブロードキャストする.
SPIF_SENDWININICHANGE SPIF_SENDCHANGと同じ.
ユーザープロファイルを更新して変更を通知するにはSPIF_SENDCHANGEだけでいけそうに思えたのですが、SPIF_UPDATEINIFILE or SPIF_SENDCHANGEとしなければなりません。
SPIF_SENDCHANGEだけではSPIF_UPDATEINIFILEだけを指定した場合と同じ結果になるようです。
TextOutW ExtTextOutW DrawTextW
procedure TForm1.Button1Click(Sender: TObject);
var
  lrc_Rect: TRect;
  ls_Text: WideString;
begin
  ls_Text := '';
  ls_Text := ls_Text + WideChar($BBF8) + WideChar($C548);  //미안

  Image1.Canvas.Font.Assign(Edit1.Font);
  Image2.Canvas.Font.Assign(Edit1.Font);
  Image3.Canvas.Font.Assign(Edit1.Font);

  Image1.Canvas.FillRect(Image1.ClientRect);
  TextOutW(Image1.Canvas.Handle, 0, 0, PWideChar(ls_Text), Length(ls_Text));

  Image2.Canvas.FillRect(Image2.ClientRect);
  lrc_Rect := Image2.ClientRect;
  DrawTextW(Image2.Canvas.Handle, PWideChar(ls_Text), -1, lrc_Rect, DT_NOPREFIX);

  Image3.Canvas.FillRect(Image3.ClientRect);
  ExtTextOutW(Image3.Canvas.Handle, 0, 0, 0, @lrc_Rect, PWideChar(ls_Text), Length(ls_Text), nil);
end;
ハングルだけの文字列を出力した場合TextOutWとExtTextOutWは文字化けします。
DrawTextWはちゃんと表示します。
ただしFontのNameが'MS UI Gothic'の場合はどれもちゃんと表示されます。
反対に'MS Pゴシック'の場合はどれも文字化けします。
さらにフォントのCharsetプロパティに以下の四つを指定するとどれもちゃんと表示されます。

CHINESEBIG5_CHARSET
GB2312_CHARSET
HANGEUL_CHARSET
THAI_CHARSET

またハングルにひらがなや漢字などが混じるとどれもちゃんと表示されます。
半角アルファベットや半角数字・記号などが混じっただけでは文字化けは直りません。
#13などの制御文字が混じるとちゃんと表示されます。

ややこしいです。
なので文字列の出力にはDrawTextW推奨。