ラベル Windows Programming の投稿を表示しています。 すべての投稿を表示
ラベル Windows Programming の投稿を表示しています。 すべての投稿を表示

2022年6月15日

現在実行している環境(プロセッサアーキテクチャ)を調べる

Windowsの実行環境としては、現時点(2022年06月)でx86(32bit)版、x64(64bit)版、ARM版があります。一方でDelphiがサポートするターゲットプラットフォームにはWindows 32ビット(x86)、Windows 64ビット(x64)があります。組み合わせとして、x86のプログラムはx86、x64、ARMのいずれでも、またx64のプログラムはx64、ARM(Windows 11のみ)で動作します。逆に動作しない組み合わせはx64のプログラムとx86、ARM(Windows 10)ということになります(Delphiではいまのところ作れませんが、ARMのプログラムはx86/x64では動作せず、ARM版でのみ動作します)。これはWindowsのWOW64(エミュレータ)やDynamic Binary Translator(JIT)によるもので、後方互換性を極めて重視するMicrosoftらしいよくできた仕組みです。これによりWindows上で動作するプログラムは実行環境のことをあまり気にしなくてもよいのですが、まれに(ターゲットプラットフォームではなく)実行環境によって動作を変えたい、ということがあります。
実行環境を調べるにはいくつかの方法がありますが、IsWow64Process2() を使わずにWowA64を検出する · GitHubによると、Windows 10 Version 1511以降であればWin32APIのIsWow64Process2を、それ以前(Windows XP/Vista/7/8/8.1/10 Version 1507(RTM))であればWin32APIのGetNativeSystemInfoを使うのがよいようです(Windows 2000ではGetSystemInfo)。それでは実装してみましょう。
type
{$SCOPEDENUMS ON}
  TProcessorArchitecture = (x86,   // Intel x86
                            x64,   // AMD64/Intel 64
                            ARM);  // ARM

  TIsWow64Process2Func = function (hProcess: THandle; var ProcessMachine: USHORT; var NativeMachine: USHORT): BOOL; stdcall;
  TGetSystemInfoFunc = procedure (var lpSystemInfo: TSystemInfo); stdcall;

const
  IMAGE_FILE_MACHINE_ARM64 = $AA64;

function GetProcessorArchitecture: TProcessorArchitecture;
var
  IsWow64Process2Func: TIsWow64Process2Func;
  ProcessMachine: USHORT;
  NativeMachine: USHORT;
  GetSystemInfoFunc: TGetSystemInfoFunc;
  SI: TSystemInfo;
begin

  { Use IsWow64Process2 on Windows 10 Version 1511 or later }
  @IsWow64Process2Func := GetProcAddress(GetModuleHandle(kernel32),'IsWow64Process2');
  if Assigned(IsWow64Process2Func) = True then
  begin
    if IsWow64Process2Func(GetCurrentProcess,ProcessMachine,NativeMachine) = True then
    begin
      case NativeMachine of
        IMAGE_FILE_MACHINE_I386:
        begin
          Result := TProcessorArchitecture.x86;
          Exit;
        end;

        IMAGE_FILE_MACHINE_AMD64:
        begin
          Result := TProcessorArchitecture.x64;
          Exit;
        end;

        IMAGE_FILE_MACHINE_ARM64:
        begin
          Result := TProcessorArchitecture.ARM;
          Exit;
        end;
      end;
    end;
  end;

  { Use GetNativeSystemInfo (Windows XP or later) or GetSystemInfo (Windows 2000) }
  FillChar(SI,SizeOf(SI),0);
  @GetSystemInfoFunc := GetProcAddress(GetModuleHandle(kernel32),'GetNativeSystemInfo');
  if Assigned(GetSystemInfoFunc) = True then
  begin
    GetSystemInfoFunc(SI);
  end
  else
  begin
    GetSystemInfo(SI);
  end;

  if SI.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64 then
  begin
    Result := TProcessorArchitecture.x64;
  end
  else
  begin
    Result := TProcessorArchitecture.x86;
  end;

end;
IsWow64Process2、GetNativeSystemInfo、GetSystemInfoの順にフォールバックして情報を取得するようになっています。

2019年12月20日

WindowsのNTFSでハードリンクを扱う

このアーティクルはDelphi Advent Calendar 2019の20日目の記事です(1日ぶり6回目)。

Windows上でファイル/ディレクトリに対してリンク(複数のエントリを用意する)する方法にはシンボリックリンク、ジャンクション、ハードリンクがありますが、ここではDelphiからハードリンクを扱います。

ハードリンクはNTFS上のファイル本体に対するディレクトリエントリを複数用意する(あるいはファイル本体に複数のパス名をつける)、というもので、一般のユーザ権限で作れ、NTFS以外にSMB3.0でもサポート(ReFSは不可)されていますが、ディレクトリを扱うことができず、同一ボリューム上にしかリンクを作ることができません。また最大のリンク数が1023に制限されています。

Windowsのシンボリックリンクとジャンクションとハードリンクの違い:Tech TIPS - @IT

普通に(CreateFile関数で)作成したファイルはリンク数が1になっており、ハードリンクを作成する毎にリンク数が増え、逆にハードリンクをDeleteFile関数で削除する毎にリンク数は減っていき、リンク数が0になるとそのファイルの実体も削除されます。

ここではハードリンクの作成と、指定されたファイルのハードリンクの数と一覧の取得をDelphiから行います。以下のコードは(System.)IOUtilsユニットのTPathレコード型や無名メソッドを使っているためにDelphi 2010以降の対応になっていますが、それ以前のバージョンであっても適当に修正すれば動くはずです。

interface

{$IF RTLVersion < 21.0}
{$MESSAGE ERROR 'Delphi 2010 or later is required.'}
{$IFEND}

uses
{$IF RTLVersion < 23.0}
  Windows, SysUtils, Classes, IOUtils;
{$ELSE}
  Winapi.Windows,
  System.SysUtils, System.Classes, System.IOUtils;
{$IFEND}

type
  { THardLink }
  THardLink = record
  private
    class procedure DoGetFileList(const Filename: String; EnumProc: TProc); static;
  public
    class procedure Create(const LinkFile: String; const SourceFile: String); static;
    class function GetFileList(const Filename: String): TArray; overload; static;
    class procedure GetFileList(const Filename: String; Strings: TStrings); overload; static;
    class function GetNumberOfLinks(const Filename: String): Integer; static;
  end;

implementation

{ HANDLE FindFirstFileNameW(LPCWSTR lpFileName, DWORD dwFlags, LPDWORD StringLength, PWSTR LinkName); }
function FindFirstFileNameW(const lpFileName: PWideChar; dwFlags: DWORD; var StringLength: DWORD; LinkName: PWideChar): THandle; stdcall; external kernel32;
{$EXTERNALSYM FindFirstFileNameW}

{ BOOL FindNextFileNameW(HANDLE hFindStream, LPDWORD StringLength, PWSTR LinkName); }
function FindNextFileNameW(hFindStream: THandle; var StringLength: DWORD; LinkName: PWideChar): BOOL; stdcall; external kernel32;
{$EXTERNALSYM FindNextFileNameW}

class procedure THardLink.Create(const LinkFile: String; const SourceFile: String);
begin
  if CreateHardLink(PChar(LinkFile),PChar(SourceFile),nil) = False then
  begin
    RaiseLastOSError;
  end;
end;

class function THardLink.GetFileList(const Filename: String): TArray;
var
  Files: TArray;
begin
  SetLength(Files,0);

  DoGetFileList(Filename,
    procedure (Filename: String)
    begin
{$IF RTLVersion >= 28.0}
      Files := Files + [Filename];
{$ELSE}
      SetLength(Files,Length(Files) + 1);
      Files[Length(Files) - 1] := Filename;
{$IFEND}
    end);

  Result := Files;
end;

class procedure THardLink.GetFileList(const Filename: String; Strings: TStrings);
begin
  Strings.Clear;

  DoGetFileList(Filename,
    procedure (Filename: String)
    begin
      Strings.Add(Filename);
    end);
end;

class function THardLink.GetNumberOfLinks(const Filename: String): Integer;
var
  hFile: THandle;
  FileInformation: TByHandleFileInformation;
begin
  hFile := CreateFile(PChar(Filename),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
  if hFile = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end;

  try
    if GetFileInformationByHandle(hFile,FileInformation) = False then
    begin
      RaiseLastOSError;
    end;
    Result := FileInformation.nNumberOfLinks;

  finally
    CloseHandle(hFile);
  end;
end;

class procedure THardLink.DoGetFileList(const Filename: String; EnumProc: TProc);
var
  hFindStream: THandle;
  Len: DWORD;
  Buffer: String;
  Root: String;
begin
  Root := ExcludeTrailingPathDelimiter(TPath.GetPathRoot(Filename));

  { Retrieve buffer size }
  Len := 0;
  FindFirstFilenameW(PWideChar(Filename),0,Len,nil);
  if GetLastError <> ERROR_MORE_DATA then
  begin
    RaiseLastOSError;
  end;
  SetLength(Buffer,Len);

  { Get first filename without drive letter }
  hFindStream := FindFirstFilenameW(PWideChar(Filename),0,Len,PWideChar(Buffer));
  if hFindStream = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end;

  try
    while True do
    begin
      { Adjust buffer size }
      SetLength(Buffer,Len - 1);

      { Callback }
      EnumProc(Root + Buffer);

      { Retrieve buffer size }
      Len := 0;
      FindNextFileNameW(hFindStream,Len,nil);
      case GetLastError of
        ERROR_HANDLE_EOF:
        begin
          Break;
        end;

        ERROR_MORE_DATA:
        begin
        end;

        else
        begin
          RaiseLastOSError;
        end;
      end;

      { Get next filename without drive letter }
      SetLength(Buffer,Len);
      if FindNextFileNameW(hFindStream,Len,PWideChar(Buffer)) = False then
      begin
        RaiseLastOSError;
      end;
    end;

  finally
    { Close }
    {$IF RTLVersion >= 23.0}Winapi.{$IFEND}Windows.FindClose(hFindStream);
  end;
end;

ハードリンクはCreateHardLink関数で作成し、リンク数はGetFileInformationByHandle関数で取得したBY_HANDLE_FILE_INFORMATION構造体のnNumberOfLinksで知ることができます。またハードリンクの一覧はFindFirstFileName関数/FindNextFileName関数/FindClose関数で取得できます。このときFindFirstFilename関数/FindNextFileName関数を一旦LinkName=nilで呼び出して必要なサイズを取得し、ファイル名の格納に必要な領域を確保してからもう一度FindFirstFilename関数/FindNextFileName関数を呼び出しています。

→WindowsのNTFSでハードリンクを扱う(Gist)

2019年12月19日

Windowsのユーザ名とSIDを相互に変換する

このアーティクルはDelphi Advent Calendar 2019の19日目の記事です(2年ぶり5回目)。またDelphi Programming Tipsカテゴリの記念すべき(かどうかは微妙)100本目の記事になります。

Windows上のユーザはそれぞれ固有のSID(Security Identifier/セキュリティ識別子)で管理されています。

オブジェクトを識別するSIDとは?:Tech TIPS - @IT

ユーザ名とSIDを相互に変換するにはWin32APIのLookupAccountName関数とLookupAccountSid関数を使用します。ところがこれらの関数でSIDは文字列ではなくSID構造体で扱う必要があります。ということで文字列表現のSIDとSID構造体を相互に変換する必要がありますが、これを行うのがConvertSidToStringSid関数とConvertStringSidToSid関数になります。

それではまずユーザ名をSIDに変換するほうから。
uses
{$IF RTLVersion < 23.0}
  Windows, SysUtils;
{$ELSE}
  Winapi.Windows, System.SysUtils;
{$IFEND}

{$IF RTLVersion < 28.0}
{ Win32API ConvertSidToStringSid }
{$IFNDEF Unicode}
function ConvertSidToStringSid(Sid: PSID; var StringSid: LPSTR): BOOL; stdcall; external advapi32 name 'ConvertSidToStringSidA';
{$ELSE}
function ConvertSidToStringSid(Sid: PSID; var StringSid: LPWSTR): BOOL; stdcall; external advapi32 name 'ConvertSidToStringSidW';
{$ENDIF}
{$EXTERNALSYM ConvertSidToStringSid}
{$IFEND}

{$IF RTLVersion < 19.0}
{ Win32API LookupAccountName }
{$IFNDEF Unicode}
function LookupAccountName(lpSystemName, lpAccountName: LPCSTR;
  Sid: PSID; var cbSid: DWORD; ReferencedDomainName: LPSTR;
  var cbReferencedDomainName: DWORD; var peUse: SID_NAME_USE): BOOL; stdcall; external advapi32 name 'LookupAccountNameA';
{$ELSE}
function LookupAccountName(lpSystemName, lpAccountName: LPCWSTR;
  Sid: PSID; var cbSid: DWORD; ReferencedDomainName: LPWSTR;
  var cbReferencedDomainName: DWORD; var peUse: SID_NAME_USE): BOOL; stdcall; external advapi32 name 'LookupAccountNameW';
{$ENDIF}
{$EXTERNALSYM LookupAccountName}
{$IFEND}

function UsernameToSid(const AUsername: String): String;
var
  PSID: Pointer;
  SidSize: DWORD;
  Domain: String;
  DomainLen: DWORD;
  SidName: SID_NAME_USE;
  SidStr: PChar;
begin
  PSID := nil;
  SidStr := nil;
  try
    SidSize := 0;
    DomainLen := 0;
    LookupAccountName(nil,PChar(AUsername),nil,SidSize,nil,DomainLen,SidName);
    if GetLastError <> ERROR_INSUFFICIENT_BUFFER then
    begin
      RaiseLastOSError;
    end;

    PSID := Pointer(LocalAlloc(LPTR,SidSize));
    SetLength(Domain,DomainLen);
    if LookupAccountName(nil,PChar(AUsername),PSID,SidSize,PChar(Domain),DomainLen,SidName) = False then
    begin
      RaiseLastOSError;
    end;

    ConvertSidToStringSid(PSID,SidStr);
    SetString(Result,SidStr,StrLen(SidStr));

  finally
    if PSID <> nil then
    begin
{$IF RTLVersion >= 32.0}
      if LocalFree(PSID) <> nil then
{$ELSE}
{$IFNDEF WIN64}
      if LocalFree(DWORD(PSID)) <> 0 then
{$ELSE}
      if LocalFree(UInt64(PSID)) <> 0 then
{$ENDIF}
{$IFEND}
      begin
        RaiseLastOSError;
      end;
    end;

    if SidStr <> nil then
    begin
{$IF RTLVersion >= 32.0}
      if LocalFree(SidStr) <> nil then
{$ELSE}
{$IFNDEF WIN64}
      if LocalFree(DWORD(SidStr)) <> 0 then
{$ELSE}
      if LocalFree(UInt64(SidStr)) <> 0 then
{$ENDIF}
{$IFEND}
      begin
        RaiseLastOSError;
      end;
    end;
  end;
end;
まずLookupAccountName関数でユーザ名に対応するSIDをSID構造体に取得し、これをConvertSidToStringSid関数で文字列に変換します。このときLookupAccountName関数を一旦SID=nil、ReferencedDomainName=nilで呼び出して必要なサイズを取得し、SIDはLocalAlloc関数で、ReferencedDomainNameは(文字列なので)SetLengthで領域を確保して、もう一度LookupAccountName関数を呼ぶようにしているのと、LocalAlloc関数、ConvertSidToStringSid関数で確保された領域はLocalFree関数で解放しなければならない、というところに気をつける必要があります(DelphiのバージョンによってLocalFree関数の宣言に差異があるので$IF RTLVersionと$IFNDEF WIN64で分岐しています)。

次にSIDをユーザ名に変換します。
{$IF RTLVersion < 28.0}
{ Win32API ConvertStringSidToSid }
{$IFNDEF Unicode}
function ConvertStringSidToSid(StringSid: LPCSTR; var Sid: PSID): BOOL; stdcall; external advapi32 name 'ConvertStringSidToSidA';
{$ELSE}
function ConvertStringSidToSid(StringSid: LPCWSTR; var Sid: PSID): BOOL; stdcall; external advapi32 name 'ConvertStringSidToSidW';
{$ENDIF}
{$EXTERNALSYM ConvertStringSidToSid}
{$IFEND}

{$IF RTLVersion < 19.0}
{ Win32API LookupAccountSid }
{$IFNDEF Unicode}
function LookupAccountSid(lpSystemName: LPCSTR; Sid: PSID;
  Name: LPSTR; var cbName: DWORD; ReferencedDomainName: LPSTR;
  var cbReferencedDomainName: DWORD; var peUse: SID_NAME_USE): BOOL; stdcall; external advapi32 name 'LookupAccountSidA';
{$ELSE}
function LookupAccountSid(lpSystemName: LPCWSTR; Sid: PSID;
  Name: LPWSTR; var cbName: DWORD; ReferencedDomainName: LPWSTR;
  var cbReferencedDomainName: DWORD; var peUse: SID_NAME_USE): BOOL; stdcall; external advapi32 name 'LookupAccountSidW';
{$ENDIF}
{$EXTERNALSYM LookupAccountSid}
{$IFEND}

function SidToUsername(const ASID: String): String;
var
  PSID: Pointer;
  UserNameLen: DWORD;
  Domain: String;
  DomainLen: DWORD;
  SidName: SID_NAME_USE;
begin
  PSID := nil;
  try
    if ConvertStringSidToSid(PChar(ASID),PSID) = False then
    begin
      RaiseLastOSError;
    end;

    UserNameLen := 0;
    DomainLen := 0;
    LookupAccountSid(nil,PSID,nil,UserNameLen,nil,DomainLen,SidName);
    if GetLastError <> ERROR_INSUFFICIENT_BUFFER then
    begin
      RaiseLastOSError;
    end;

    SetLength(Result,UserNameLen);
    SetLength(Domain,DomainLen);
    if LookupAccountSid(nil,PSID,PChar(Result),UserNameLen,PChar(Domain),DomainLen,SidName) = False then
    begin
      RaiseLastOSError;
    end;

    SetLength(Result,StrLen(PChar(Result)));

  finally
    if PSID <> nil then
    begin
{$IF RTLVersion >= 32.0}
      if LocalFree(PSID) <> nil then
{$ELSE}
{$IFNDEF WIN64}
      if LocalFree(DWORD(PSID)) <> 0 then
{$ELSE}
      if LocalFree(UInt64(PSID)) <> 0 then
{$ENDIF}
{$IFEND}
      begin
        RaiseLastOSError;
      end;
    end;
  end;
end;
こちらはまずConvertStringSidToSid関数でSIDの文字列をSID構造体に変換し、LookupAccountSid関数でユーザ名に変換します。こちらもLookupAccountSid関数をName=nil、ReferencedDomainName=nilで呼び出して必要なサイズを取得し、SetLengthで領域を確保してからもう一度LookupAccountSid関数を呼び出しています。またConvertStringSidToSid関数で確保したSID構造体はLocalFree関数で解放します。

使用しているWin32APIのうち、LookupAccountSid関数/LookupAccountName関数はDelphi 2009で、ConvertStringSidToSid関数/ConvertSidToStringSid関数はDelphi XE7で(Win32API.)Windowsユニットに関数宣言が追加されたため、それ以前のバージョンでは明示的に定義が必要です。

→Windows上のユーザ名とSIDを相互変換(Gist)

2011年6月9日

Windows 7のピン止め機能を無効にする

Windows 7の新機能の一つにタスクバーへのピン止め(pinning、日本マイクロソフトの表現では"固定表示")があります。この機能はタスクバーと従来のクイックリンクを統合したようなもので、ユーザの明示的な操作によりタスクバー上のプログラムをピン止めし、非起動状態と起動状態の区別を考慮することなく扱うことができる、というものです(プログラム側からピン止めを設定することはできない)。しかしプログラムによってはこの機能を使ってほしくないこともあります。そこでプログラム側でピン止め機能を無効化してみます。

MSDNのApplication User Model IDs (AppUserModelIDs) (Windows)のExclusion Lists for Taskbar Pinning and Recent/Frequent ListsによればAppUserModelIDを設定する前にSystem.AppUserModel.PreventPinningプロパティを設定することでピン止めが無効化されます。Windowsプロパティの設定はSHGetPropertyStoreForWindowで行います。ところがこのWindowsプロパティに関係する定義はDelphi 2010で追加されたPropSysユニット、PropKeyユニットには存在するものの、Delphi 2009およびそれ以前のバージョンでは独自に定義する必要があります。またプロパティキーPKEY_AppUserModel_PreventPinningはDelphi 2010でも定義が存在しないため、これも独自に定義します。

uses
  Windows, SysUtils, ShellAPI, ActiveX
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF RTLVersion >= 21.00}
  , PropSys, PropKey
{$IFEND}
{$ENDIF}
;

{$IFDEF CONDITIONALEXPRESSIONS}
{$IF RTLVersion < 21.00}
const
  SID_IPropertyStore = '{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}';

  IID_IPropertyStore: TGUID = SID_IPropertyStore;
  {$EXTERNALSYM IID_IPropertyStore}

type
  { interface IPropertyStore }
  IPropertyStore = interface(IUnknown)
  [SID_IPropertyStore]
    function GetCount(out cProps: DWORD): HRESULT; stdcall;
    function GetAt(iProp: DWORD; out pkey: TPropertyKey): HRESULT; stdcall;
    function GetValue(const key: TPropertyKey; out pv: TPropVariant): HRESULT; stdcall;
    function SetValue(const key: TPropertyKey; const propvar: TPropVariant): HRESULT; stdcall;
    function Commit: HRESULT; stdcall;
  end;
  {$EXTERNALSYM IPropertyStore}

type
  TSHGetPropertyStoreForWindow = function (hwnd: HWND; const riid: TGUID;
                                           var ppv: Pointer): HResult; stdcall;
{$IFEND}
{$ENDIF}

const
  PKEY_AppUserModel_PreventPinning : TPropertyKey = (
    fmtid : '{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}'; pid : 9);
  {$EXTERNALSYM PKEY_AppUserModel_PreventPinning}


function MarkWindowAsUnpinnable(handle: HWND): HRESULT;
var
  pps: IPropertyStore;
  v: TPropVariant;
{$IFDEF CONDITIONALEXPRESSIONS}
{$IF RTLVersion < 21.00}
  hModule: THandle;
  SHGetPropertyStoreForWindow: TSHGetPropertyStoreForWindow;
{$IFEND}
{$ENDIF}
begin

  Result := 0;

  if CheckWin32Version(6,1) = False then
  begin
    Exit;
  end;

{$IFDEF CONDITIONALEXPRESSIONS}
{$IF RTLVersion < 21.00}
  hModule := LoadLibrary(shell32);
  if hModule = 0 then
  begin
    Exit;
  end;

  try
    @SHGetPropertyStoreForWindow := GetProcAddress(hModule,'SHGetPropertyStoreForWindow');
    if Assigned(SHGetPropertyStoreForWindow) = False then
    begin
      Exit;
    end;
{$IFEND}
{$ENDIF}

    Result := SHGetPropertyStoreForWindow(handle,IID_IPropertyStore,Pointer(pps));
    if Succeeded(Result) = True then
    begin
      v.vt := VT_BOOL;
      v.boolVal := True;
      Result := pps.SetValue(PKEY_AppUserModel_PreventPinning,v);
    end;
    pps := nil;

{$IFDEF CONDITIONALEXPRESSIONS}
{$IF RTLVersion < 21.00}
  finally
    FreeLibrary(hModule);
  end;
{$IFEND}
{$ENDIF}

end;
MarkWindowAsUnpinnableはメインフォームのOnCreateイベントハンドラで
procedure TForm1.FormCreate(Sender: TObject);
begin

  MarkWindowAsUnpinnable(Handle);

end;
のように呼び出します。

元ねたはRaymond ChenさんのHow do I prevent users from pinning my program to the taskbar? - The Old New Thing - Site Home - MSDN Blogs。

2011年5月19日

Windowsの互換モード上でのLCMapStringの不具合

ちょっと前になりますが、au2010さんのところで気になる話を見つけました。

Windows7でDelphi7のプログラムを動かす時の注意点 - au2010の日記

実際にLCMapString (ja)の動作を確認してみると、Windows 7で互換モードを設定(プログラムのショートカットの"互換性"タブで"互換モード"にチェックオン)すると、LCMapStringのcchDestを0にしたときだけ戻値がA版(LCMapStringA)であっても(W版と同様の)文字数で返ってくる、という不具合があります(cchDestが非0ならば戻値は仕様通り)。いろいろ調べてみましたがプログラム側から互換モードの指定状況を知る方法はなく、結果が既知の変換動作を行わせて、その戻値で判定するしかないようです。ということで半角→全角変換と全角→半角変換のコードを修正しました。

2011年2月25日

Windowsにおける呼出規約の歴史

Raymond ChenさんのThe Old New ThingにWindowsにおける呼出規約(Calling Convention)とその歴史的経緯に関するアーティクルがありました。

The history of calling conventions, part 1 - The Old New Thing - Site Home - MSDN Blogs
Part 1では16bit環境での規約(C/Pascal/Fortran/Fastcall)について説明しています。16bit環境ではどの規約でもBP、SI、DIの内容は保持される必要があり、戻値はAX(16bit)またはDX:AX(32bit)に格納します。

The history of calling conventions, part 2 - The Old New Thing - Site Home - MSDN Blogs
Part 2では過去に存在した32bitのx86以外のCPU(Alpha AXP/MIPS R4000/PowerPC)の規約について説明しています。これらのCPUでは呼出規約が1つずつしかありません。またRISC系CPUでレジスタが多く存在するため、スタックをなるべく使わないようになっています。

The history of calling conventions, part 3 - The Old New Thing - Site Home - MSDN Blogs
Part 3ではx86(32bit)の規約(C/__stdcall/__fastcall/thiscall)について説明しています(Microsoftのものだけですが)。どの規約でもEDI、ESI、EBP、EBXの内容は保持される必要があり、EDX:EAXレジスタペアに戻値を格納します。なおC++のメンバ関数ではthisポインタが暗黙の第1パラメータとなることに注意が必要です。またWin32APIは基本的に__stdcallです。(参考: Results of Calling Example)

The history of calling conventions, part 4: ia64 - The Old New Thing - Site Home - MSDN Blogs
Part 4ではia64(Intel Itanium)の規約について説明しています。ia64には呼出規約は1つしかありません。ia64には128のレジスタがあり、グローバルに使用する32(r0-r31)を除いたローカル領域の96(r32-r127)について、関数はレジスタをいくつローカル/パラメータで使用するのかを宣言するようになっており、可能であればレジスタ間のシフト(実際にはレジスタリネーミングでしょう)で、もし必要であれば専用のレジスタスタックを使用するようになっています(解釈が違ったらすいません)。戻値はグローバル領域のレジスタ経由で呼び出し元に返され、リターンアドレスも通常はローカル領域のレジスタ上に置かれます。興味深いのはスタック上の最初の16バイトは誰がいつどのように使ってもいい(=関数を呼び出したら内容が破壊されているかもしれない)領域となっていることと、ia64上の関数ポインタは関数のエントリポイントではなく関数記述構造体(a structure that describes the function)を指しており、構造体の前半8バイトがエントリポイント、次の8バイトが"gpレジスタ"の値を格納するようになっているということでしょうか(関数ポインタがエントリポイントではなくある種の構造体を指すというのはRISC系CPUと共通しています)。

The history of calling conventions, part 5: amd64 - The Old New Thing - Site Home - MSDN Blogs
Part 5ではx64(AMD64)の規約について説明しています。
てきとうな要約:
  • x64ではx86のレジスタが64bitに拡張されており(RAX、RBX、...)、さらにR8-R15の8つのレジスタが追加されています。
  • x64についても呼出規約は1つしかありません。
  • 最初の4つのパラメータはRCX、RDX、R8、R9に格納され、それ以降はスタックに配置されますが、スタック上には最初の4つのパラメータの分の領域も確保されます。
  • パラメータが64bit未満の場合、上位ビットを0にするのではなくごみ("garbage")のままとなり、64bitよりも大きいパラメータはその格納アドレスが渡されます。
  • 戻値はRAXに格納されますが、64bitよりも大きい場合は第1パラメータとして戻値を格納する領域のアドレスが渡されます。
  • RAX、RCX、RDX、R8、R9、R10、R11以外のレジスタの内容は保持される必要があります。
  • スタックのクリーンアップは呼出元の責任です。
  • スタックは16バイトアライメントで維持されます。

Delphi(x86)の呼出規約については

Procedures and Functions (Delphi) - RAD Studio
プロシージャと関数 - RAD Studio

に説明があります。またx64については日本語だとAkihiro NotesさんのMicrosoft x64 呼び出し規約の説明がわかりやすいですね。

2011年2月23日

x64プログラミングの参考資料

次期版のDelphi "Pulsar"では従来の32bit版Windows(x86)に加えて64bit版Windows(x64)とMacOS X(x86)のサポートが追加される予定です。Windows x64に関しては既にMicrosoftのVisual Studio (Visual C++)がサポートしていますが、Microsoftによるx64プログラミングの解説がMSDNにありました。

x64 の入門書: 64 ビット Windows システムのプログラミングを開始するときに必要な知識 -- MSDN Magazine, 2006 年 5 月
Visual C++ による 64 ビット プログラミング
64 ビット Windows プログラミング ガイド (Windows)

x86とx64の大きな違いは
  • データサイズ(32/64bit)
  • 呼出規約(calling convention)
  • 例外処理(SEH)
でしょうか(細かい話ではPE32+ヘッダやprintfの書式指定などもありますが)。これらの記事は"Pulsar"でも十分に役立つと思います。

2011年1月17日

Visual Studio 2010の新しい"Help3"システム

Visual Studioでは2002以降で(Delphiでも使用している)Document ExplorerによるMicrosoft Help 2(.hxs)形式のヘルプシステムを採用していましたが、やはり不評だったようで、VS2010では新たに"Help3"と呼ばれるヘルプシステムを開発しました。この"Help3"作成の経緯などをMicrosoftのLibrary EXperience (LEX)チームのJeff Braatenさんが明らかにしています。

The Story of Help in Visual Studio 2010
The Story of Help in Visual Studio 2010 (Part 2)
The Story of Help in Visual Studio 2010 (Part 3)

"Help3"はヘルプシステムのパフォーマンス改善、オフライン/オンラインの切替、MSDNとの共存、ローカライズ、適切な内容のヘルプの表示、といった問題を解決するべく開発が行われました。ただ"Help3"は例によって"早すぎた"ようで、Visual Studio 2010 SP1でHelp Viewerも更新される予定です(既にベータ版が利用可能になっています)。

上記のアーティクルでも考察されていますが、現在Windows上で使用できるヘルプシステムは
の事実上4種類です。今後Delphiのヘルプシステムはどうなっていくのでしょう?

2011/01/18追記: "Help3"についてはこんなページもありました。

XLsoft エクセルソフト : Helpware Group - FAR HTML ヘルプ作成ツール - 日本語ホーム
MS Help Viewer 1.0

旧形式からのマイグレーションツールやビュアー、対応オーサリングツールもあるようです。

2010年12月22日

Windowsのテストに便利なツールとその使用法

Windows 7/Server 2008 R2上でテストを行うためのツール類とその使用方法を紹介するドキュメントをMicrosoftが公開しています。

Windows のテストに便利なツールとその使用法

紹介されているツールとその使用方法(要約):
  • システムイメージの作成と復元: Windows Automated Installation Kit (WAIK)
  • デバイスのプラグアンドプレイ(PnP)関連のテスト: Plug and Play Driver test (Pnpdtest.exe) (WDKに付属)
  • システムのスリープ・レジュームの移行を自動的に繰り返し実行: Power Management Test Tool (Pwrtest.exe) (WDKに付属)
  • アプリケーションの動作を検証: Application Verifier
  • ネットワークパケットを監視・分析: Network Monitor
  • Windowsポータブルデバイス(WPD)の動作をモニタ: WPD Monitor (WPDMon.exe) (WDK)
  • 問題の再現手順を簡単に記録: 問題ステップ記録ツール (Problem Steps Recorder) (Windows標準)
  • マネージアプリケーションの例外とコールスタックを取得: WinDBG (Windows SDK)
  • カーネルモードドライバ内のエラーを検出: Driver Verifier (Windows標準)
  • ブルースクリーンの問題を調査: Debugging Tools for Windows (Windows SDK)

紹介されているツールのダウンロードリンク:

2010年11月16日

ダミーのDwmapi.dllを作成する

前回のアーティクルでも触れましたが、Visual Studioで作成したプログラムがWindows Vista以降で"Known DLLs"となったDwmapi.dllをWindows 2000/XPでもLoadLibraryしてしまいバイナリプランティングを引き起こしてしまう件(およびDelphiで作成したプログラムにこの問題が存在しない件)について、これを検証するためのダミーのDwmapi.dllを作成してみました(当然Windows 2000/XP用です)。

まずはプロジェクトファイルです。DLLを新規作成し、DWMAPIという名前にします。
library DWMAPI;

uses
  Windows,
  SysUtils,
  Classes,
  _DWMAPI in '_DWMAPI.pas';

exports
  DwmDefWindowProc,
  DwmEnableBlurBehindWindow,
  DwmEnableComposition,
  DwmEnableMMCSS,
  DwmExtendFrameIntoClientArea,
  DwmGetColorizationColor,
  DwmGetCompositionTimingInfo,
  DwmGetWindowAttribute,
  DwmIsCompositionEnabled,
  DwmModifyPreviousDxFrameDuration,
  DwmQueryThumbnailSourceSize,
  DwmRegisterThumbnail,
  DwmSetDxFrameDuration,
  DwmSetPresentParameters,
  DwmSetWindowAttribute,
  DwmUnregisterThumbnail,
  DwmUpdateThumbnailProperties;

{$R *.res}

begin
end.
さらに新規作成でユニットを追加し、_DWMAPI.pasとします。
unit _DWMAPI;

interface

uses
  Types;

type
  DWORD = Types.DWORD;
  {$EXTERNALSYM DWORD}
  BOOL = LongBool;
  {$EXTERNALSYM BOOL}
  UINT = LongWord;
  {$EXTERNALSYM UINT}

  HRGN = type LongWord;
  {$EXTERNALSYM HRGN}

  LONGLONG = Int64;
  {$EXTERNALSYM LONGLONG}

  ULONGLONG = UInt64;
  {$EXTERNALSYM ULONGLONG}
  ULARGE_INTEGER = record
    case Integer of
    0: (
        LowPart: DWORD;
        HighPart: DWORD);
    1: (
        QuadPart: LONGLONG);
  end;
  {$EXTERNALSYM ULARGE_INTEGER}
  PULargeInteger = ^TULargeInteger;
  TULargeInteger = ULARGE_INTEGER;

  HWND = type LongWord;
  {$EXTERNALSYM HWND}

  WPARAM = Longint;
  {$EXTERNALSYM WPARAM}
  LPARAM = Longint;
  {$EXTERNALSYM LPARAM}
  LRESULT = Longint;
  {$EXTERNALSYM LRESULT}

  {$EXTERNALSYM PDWM_BLURBEHIND}
  PDWM_BLURBEHIND = ^DWM_BLURBEHIND;
  {$EXTERNALSYM DWM_BLURBEHIND}
  DWM_BLURBEHIND = packed record
    dwFlags: DWORD;
    fEnable: BOOL;
    hRgnBlur: HRGN;
    fTransitionOnMaximized: BOOL;
  end;
  _DWM_BLURBEHIND = DWM_BLURBEHIND;
  TDWMBlurBehind = DWM_BLURBEHIND;
  PDWMBlurBehind = ^TDWMBlurBehind;

  _MARGINS = record
    cxLeftWidth: Integer;
    cxRightWidth: Integer;
    cyTopHeight: Integer;
    cyBottomHeight: Integer;
  end;
  {$EXTERNALSYM _MARGINS}
  MARGINS = _MARGINS;
  {$EXTERNALSYM MARGINS}
  PMARGINS = ^MARGINS;
  {$EXTERNALSYM PMARGINS}
  TMargins = MARGINS;

  {$EXTERNALSYM PDWM_THUMBNAIL_PROPERTIES}
  PDWM_THUMBNAIL_PROPERTIES = ^DWM_THUMBNAIL_PROPERTIES;
  {$EXTERNALSYM DWM_THUMBNAIL_PROPERTIES}
  DWM_THUMBNAIL_PROPERTIES = packed record
    dwFlags: DWORD;
    rcDestination: TRect;
    rcSource: TRect;
    opacity: Byte;
    fVisible: BOOL;
    fSourceClientAreaOnly: BOOL;
  end;
  _DWM_THUMBNAIL_PROPERTIES = DWM_THUMBNAIL_PROPERTIES;
  TDWMThumbnailProperties = DWM_THUMBNAIL_PROPERTIES;
  PDWMThumbnailProperties = ^TDWMThumbnailProperties;

  {$EXTERNALSYM DWM_FRAME_COUNT}
  DWM_FRAME_COUNT = ULONGLONG;
  {$EXTERNALSYM QPC_TIME}
  QPC_TIME = ULONGLONG;

  {$EXTERNALSYM UNSIGNED_RATIO}
  UNSIGNED_RATIO = packed record
    uiNumerator: Cardinal;
    uiDenominator: Cardinal;
  end;
  _UNSIGNED_RATIO = UNSIGNED_RATIO;
  TUnsignedRatio = UNSIGNED_RATIO;
  PUnsignedRatio = ^TUnsignedRatio;

  {$EXTERNALSYM DWM_TIMING_INFO}
  DWM_TIMING_INFO = packed record
    cbSize: Cardinal;
    rateRefresh: UNSIGNED_RATIO;
    qpcRefreshPeriod: QPC_TIME;
    rateCompose: UNSIGNED_RATIO;
    qpcVBlank: QPC_TIME;
    cRefresh: DWM_FRAME_COUNT;
    cDXRefresh: UINT;
    qpcCompose: QPC_TIME;
    cFrame: DWM_FRAME_COUNT;
    cDXPresent: UINT;
    cRefreshFrame: DWM_FRAME_COUNT;
    cFrameSubmitted: DWM_FRAME_COUNT;
    cDXPresentSubmitted: UINT;
    cFrameConfirmed: DWM_FRAME_COUNT;
    cDXPresentConfirmed: UINT;
    cRefreshConfirmed: DWM_FRAME_COUNT;
    cDXRefreshConfirmed: UINT;
    cFramesLate: DWM_FRAME_COUNT;
    cFramesOutstanding: UINT;
    cFrameDisplayed: DWM_FRAME_COUNT;
    qpcFrameDisplayed: QPC_TIME;
    cRefreshFrameDisplayed: DWM_FRAME_COUNT;
    cFrameComplete: DWM_FRAME_COUNT;
    qpcFrameComplete: QPC_TIME;
    cFramePending: DWM_FRAME_COUNT;
    qpcFramePending: QPC_TIME;
    cFramesDisplayed: DWM_FRAME_COUNT;
    cFramesComplete: DWM_FRAME_COUNT;
    cFramesPending: DWM_FRAME_COUNT;
    cFramesAvailable: DWM_FRAME_COUNT;
    cFramesDropped: DWM_FRAME_COUNT;
    cFramesMissed: DWM_FRAME_COUNT;
    cRefreshNextDisplayed: DWM_FRAME_COUNT;
    cRefreshNextPresented: DWM_FRAME_COUNT;
    cRefreshesDisplayed: DWM_FRAME_COUNT;
    cRefreshesPresented: DWM_FRAME_COUNT;
    cRefreshStarted: DWM_FRAME_COUNT;
    cPixelsReceived: ULONGLONG;
    cPixelsDrawn: ULONGLONG;
    cBuffersEmpty: DWM_FRAME_COUNT;
  end;
  _DWM_TIMING_INFO = DWM_TIMING_INFO;
  TDWMTimingInfo = DWM_TIMING_INFO;
  PDWMTimingInfo = ^TDWMTimingInfo;

  {$EXTERNALSYM HTHUMBNAIL}
  HTHUMBNAIL = THandle;
  {$EXTERNALSYM PHTHUMBNAIL}
  PHTHUMBNAIL = ^HTHUMBNAIL;

  {$EXTERNALSYM DWM_PRESENT_PARAMETERS}
  DWM_PRESENT_PARAMETERS = packed record
    cbSize: Cardinal;
    fQueue: BOOL;
    cRefreshStart: DWM_FRAME_COUNT;
    cBuffer: UINT;
    fUseSourceRate: BOOL;
    rateSource: UNSIGNED_RATIO;
    cRefreshesPerFrame: UINT;
    eSampling: UINT;
  end;
  _DWM_PRESENT_PARAMETERS = DWM_PRESENT_PARAMETERS;
  TDWMPresentParameters = DWM_PRESENT_PARAMETERS;
  PDWMPresentParameters = ^TDWMPresentParameters;

function DwmDefWindowProc(hWnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM; var plResult: LRESULT): BOOL; stdcall;
function DwmEnableBlurBehindWindow(hWnd: HWND; const pBlurBehind: TDWMBlurBehind): HResult; stdcall;
function DwmEnableComposition(uCompositionAction: UINT): HResult; stdcall;
function DwmEnableMMCSS(fEnableMMCSS: BOOL): HResult; stdcall;
function DwmExtendFrameIntoClientArea(hWnd: HWND; const pMarInset: TMargins): HResult; stdcall;
function DwmGetColorizationColor(out pcrColorization: DWORD; out pfOpaqueBlend: BOOL): HResult; stdcall;
function DwmGetCompositionTimingInfo(hwnd: HWND; out pTimingInfo: TDWMTimingInfo): HResult; stdcall;
function DwmGetWindowAttribute(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HResult; stdcall;
function DwmIsCompositionEnabled(out pfEnabled: BOOL): HResult; stdcall;
function DwmModifyPreviousDxFrameDuration(hwnd: HWND; cRefreshes: Integer; fRelative: BOOL): HResult; stdcall;
function DwmQueryThumbnailSourceSize(hThumbnail: HTHUMBNAIL; pSize: PSIZE): HResult; stdcall;
function DwmRegisterThumbnail(hwndDestination: HWND; hwndSource: HWND; out phThumbnailId: HTHUMBNAIL): HResult; stdcall;
function DwmSetDxFrameDuration(hwnd: HWND; cRefreshes: Integer): HResult; stdcall;
function DwmSetPresentParameters(hwnd: HWND; var pPresentParams: TDWMPresentParameters): HResult; stdcall;
function DwmSetWindowAttribute(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HResult; stdcall;
function DwmUnregisterThumbnail(hThumbnailId: HTHUMBNAIL): HResult; stdcall;
function DwmUpdateThumbnailProperties(hThumbnailId: HTHUMBNAIL; const ptnProperties: TDWMThumbnailProperties): HResult; stdcall;

implementation

function DwmDefWindowProc(hWnd: HWND; msg: UINT; wParam: WPARAM; lParam: LPARAM; var plResult: LRESULT): BOOL;
begin
  Result := False;
end;

function DwmEnableBlurBehindWindow(hWnd: HWND; const pBlurBehind: TDWMBlurBehind): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmEnableComposition(uCompositionAction: UINT): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmEnableMMCSS(fEnableMMCSS: BOOL): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmExtendFrameIntoClientArea(hWnd: HWND; const pMarInset: TMargins): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmGetColorizationColor(out pcrColorization: DWORD; out pfOpaqueBlend: BOOL): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmGetCompositionTimingInfo(hwnd: HWND; out pTimingInfo: TDWMTimingInfo): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmGetWindowAttribute(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmIsCompositionEnabled(out pfEnabled: BOOL): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmModifyPreviousDxFrameDuration(hwnd: HWND; cRefreshes: Integer; fRelative: BOOL): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmQueryThumbnailSourceSize(hThumbnail: HTHUMBNAIL; pSize: PSIZE): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmRegisterThumbnail(hwndDestination: HWND; hwndSource: HWND; out phThumbnailId: HTHUMBNAIL): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmSetDxFrameDuration(hwnd: HWND; cRefreshes: Integer): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmSetPresentParameters(hwnd: HWND; var pPresentParams: TDWMPresentParameters): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmSetWindowAttribute(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmUnregisterThumbnail(hThumbnailId: HTHUMBNAIL): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

function DwmUpdateThumbnailProperties(hThumbnailId: HTHUMBNAIL; const ptnProperties: TDWMThumbnailProperties): HResult; stdcall;
begin
  Result := E_NOTIMPL;
end;

end.
(Dwmapi.dll関係の各種定義はDwmApi.pasなどから借りてきました)。これでビルドしたDwmapi.dllをWindows 2000/XPでSystem32あたりに配置しておきます。この状態から問題のありそうなプログラムを実行してDependency WalkerやWindows SysInternalsのProcess Explorerなどを使ってDwmapi.dllのロード状況を確認することができます。確かにDelphiで作成したプログラムは問題ないようですね。念のためにVCLのソースで確認してみると、DWMのデスクトップコンポジションを使用できるかどうかを調べるDwmApiユニットのDwmCompositionEnabled関数(ヘルプにはエントリがありませんが)の実装が
function DwmCompositionEnabled: Boolean;
var
  LEnabled: BOOL;
begin
  Result := (Win32MajorVersion >= 6) and (DwmIsCompositionEnabled(LEnabled) = S_OK) and LEnabled;
end;
(Delphi 20007のDwmApi.pasの795行目から)となっており、Windows Vista以降かどうかの確認を正しく行っていることがわかります。

元ねたはVS2010 でコンパイルされた全ての単体 MFC アプリケーションに脆弱性が存在 - スラッシュドット・ジャパン、VS2010でコンパイルされたすべてのMFCアプリに脆弱性ってのは過小報告? - Windows 2000 Blog。

2010年11月15日

バイナリプランティングの防止

IPAからも注意喚起が行われていますが、最近バイナリプランティング("Binary planting"、あるいは"DLL planting"、"DLL preloading"とも表現されます)という攻撃手法が問題になっています。これは(いわゆる"Known DLLs"を除く)絶対パス指定ではないDLLを検索するパスに"カレントディレクトリ"が含まれていて、状況によってはその優先順位が高いために攻撃者の用意した不正なDLLが実行プログラムにバインドされてしまう(通常はDLLをロードして初期化するだけでDLLMainが実行されてしまいますから、この時点で攻撃成立です)、というものです(これがが狭義の"DLL planting")。また類似の状況として、絶対パス指定ではない実行ファイルをCreateProcess/CreateProcessAsUser/CreateProcessWithLogonW/CreateProcessWithTokenW/ShellExecute/ShellExecuteExなどで起動することでも同様の問題が生じます。さらに問題を複雑なものにする要因として、Windows Vista/7ではKnown DLLsに含まれるDwmapi.dllがWindows 2000/XPには存在しないにもかかわらず一部のフレームワークがWindowsのバージョンを考慮せずにDwmapi.dllをロードしようとするために、カレントディレクトリに攻撃用のDwmapi.dllを配置することでこれがバインドされてしまい攻撃が成立してしまう、というものがあります(幸いにもVCLや.NET Frameworkは該当しませんが、MFC(Visual Studio 2005/2008/2010)は該当するようです)。上記のいずれの状況でも攻撃用のDLL/EXEはカレントディレクトリに配置するのが攻撃成立の条件になります(例えばSystem32にそんなものを置かれるようではどんな攻撃も可能ですから)ので、カレントディレクトリが外部に設定されてプログラムが起動するような場合、つまりファイルをダブルクリックして関連付けでプログラムが起動するような場合が最も危険である、ということになります(ショートカットでもカレントディレクトリは設定できますが)。

バイナリプランティングをプログラム側から防ぐには、
  • リンクするDLLや起動する実行ファイルは可能な限り完全修飾パス名を使用する。
  • SetDllDirectoryで""(空文字列)を指定してDLLの検索パスからカレントディレクトリを削除する(Windows XP以降)。
  • DLL/実行ファイルを検索するのにSearchPath (ja)はなるべく使用しない。使用するときはSetSearchPathModeでBASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODEを設定する(Windows Vista以降)。
  • Windowsの特定のバージョン以降で"Known DLLs"に追加されたDLLをLoadLibraryするときはWindowsのバージョンチェックを行うか、完全修飾パス名を使用する。
といった対策をとる必要があります。

ということでSetDllDirectoryを使用してDLLの検索パスからカレントディレクトリを削除するサンプルです。なるべく早い時点で設定するのが望ましいので、プロジェクトファイルの先頭で行います。またSetDllDirectoryはWindows XP SP1以降にしか存在しないので、エントリの存在を確認して呼び出すようにしています。
program Project1;

uses
  Windows,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

type
  TSetDllDirectoryFunc = function (lpPathName: PChar): BOOL; stdcall;

const
{$IFDEF Unicode}
  CSetDllDirectory = 'SetDllDirectoryW';
{$ELSE}
  CSetDllDirectory = 'SetDllDirectoryA';
{$ENDIF}

var
  S: String;
  SetDllDirectory: TSetDllDirectoryFunc;
begin

  @SetDllDirectory := GetProcAddress(GetModuleHandle(kernel32),
  CSetDllDirectory);
  if Assigned(SetDllDirectory) = True then
  begin
    S := '#0';
    SetDllDirectory(PChar(S));
  end;

  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
なおコンソールアプリケーションでグラスエフェクトを有効にするのサンプルコードもDwmapi.dllがWindows 2000/XPには存在しないことを利用したバイナリプランティングの影響を受けるため、修正してあります。

元ねたは以下の通り。

情報処理推進機構(IPA)マイクロソフトITpro(要登録)CodeZineNyaRuRuの日記

2010年9月28日

LockWindowUpdateとWM_SETREDRAW

Raymond ChenさんのThe Old New ThingにLockWindowUpdate (ja)(とWM_SETREDRAW)に関する興味深い一連のアーティクルがありました。
てきとうな要約:
  • LockWindowUpdateはシステム(デスクトップ)全体で1つのウィンドウしかロックできない。
  • LockWindowUpdateはドラッグ操作(アイテムのドラッグアンドドロップやウィンドウのドラッグ)でウィンドウの描画更新を止めるときに使う(本来はそのために存在している)。
  • LockWindowUpdateで描画更新を止めているウィンドウに追加的に描画を行う必要があるときはGetDCEx (ja)のflagsにDCX_LOCKWINDOWUPDATEを含めて作成したDCに描画する。
  • それ以外の状況ではLockWindowUpdateではなくWM_SETREDRAWを使用するべきである。
  • LockWindowUpdateはWindows 3.1(Win16)のリソースが限定されていた時代に作られたもので、いまどきはDirectXオーバレイ、リージョン化ウィンドウ、レイヤードウィンドウ、アルファブレンド、デスクトップコンポジションといったものがあるのだから、これらでエフェクトをかけるほうが多機能だし望ましい。

The Old New Thingは書籍(日本語訳はWindowsプログラミングの極意)にもなっていますが、MSDNのリファレンスには載っていない色々な"理由"を解説してくれる素晴らしいblogです(英語なので厳しいですが…)。

2010年7月27日

keyed events

以前クリティカルセクションの仕様変更の影響について触れましたが、この件に関連して、CriticalSectionに代わる非公開機能の"keyed events"に関する興味深い記事を見つけました。

Windows keyed events, critical sections, and new Vista synchronization features(Joe Duffy's Weblog)

とってもいいかげんな要約: クリティカルセクションはWindows 2000以前の実装ではSMP上でのパフォーマンスのボトルネックになってしまっていた。これを改善するためにWindows 2000で仕様を変更したが、今度は信頼性を損なうことになってしまった。これを解決するためにWindows XPで新たに非公開の"keyed events"が作られ、Windows Vistaではその実装が改善された。ハンドルとノンページメモリを圧迫しない新しい同期機能としてユーザモードのコードからも利用可能になることを期待している。

もう少し詳しい話がWindows Internals, Fifth Edition (amazon)にあるそうですが、日本語訳はいつ出るんでしょうか…。

元ねたは江添さんの本の虫: keyed-eventsとNyaRuRuさんのTwitter上の2010/07/24-25あたりの発言。

2009年9月1日

Windowsユーザエクスペリエンスガイドライン

MSDNライブラリでVisual Style(Windows Vista/7)に対応した"Windows ユーザー エクスペリエンス ガイドライン"(UXガイド)が公開されています。PDF版もあります(775ページもありますね)。

Windows ユーザー エクスペリエンス ガイドライン

元ねたはCrystal Dew R&D LabsさんのWindows ユーザー エクスペリエンス ガイドライン。

2009年5月26日

InitializeCriticalSectionEx

Team Japanの高橋さんの記事によると、Windows Vista(NT 6.0)以降ではInitializeCriticalSectionによる初期化ではリソースリークが発生するとのこと。

Team Japan » InitializeCriticalSectionEx

まぁそもそもマルチCPUではInitializeCriticalSectionAndSpinCountでスピンカウントの指定をしないとパフォーマンスに影響が出るらしいし、新しいOSではInitializeCriticalSectionExを呼び出しなさない、というのは判るのだが、もう少し互換性のある拡張はできないのかと。スピンカウントもどのくらいの値が適当なのかよくわからないし。

ちょうどいま流し読み中のWindowsデバッグの極意(Mario Hewardt、Daniel Pravat著/長尾高弘訳/アスキー・メディアワークス/ISBN978-4-04-867608-3)の"第10章 同期"でクリティカルセクションに触れています。以下引っ掛かる点を引用。
RTL_CRITICAL_SECTION.DebugInfo
クリティカルセクションに関する追加情報を格納する構造体で、これを格納するメモリはシステムにより確保される。(p.502)

RTL_CRITICAL_SECTION_DEBUG
RTL_CRITICAL_SECTION_DEBUGは主としてデバッグ用の情報を格納しているように見えるが、初期バージョンのWindowsでは、これがなければクリティカルセクションが使える状態だと見なされなかった。実際、オペレーティングシステムが初期化中にこの構造体のメモリを確保できなければ、APIは失敗していたのである。Windows Server 2003 SP1以降、このデバッグ情報がなくても、クリティカルセクションは機能するようになった。(p.504)

EnterCriticalSection
Windows 2000のEnterCriticalSection APIについては注意が必要だ。メモリの残量が少なくなったときにこれを呼び出すと、メモリ不足例外が投げられることがあるのだ。クリティカルセクションはイベントを使っており、EnterCriticalSection APIではこのイベントが初期化される。システムのメモリの残量が少なくなっていると、そのために例外が起きる。Windows 2000のもとでクリティカルセクションを信頼できる形で実行したければ、クリティカルセクションの初期化時にイベントを確保し、その後は例外を投げないInitializeCriticalSectionAndSpinCount APIを使うようにするとよい。(p.505)

追記: 高橋さん、フォローありがとうございます。

2009年5月22日

アプリケーション開発者向けMicrosoft Windows 7対応アプリケーションの互換性

MSDNのWindows 7 互換性情報ページで"アプリケーション開発者向けMicrosoft Windows 7対応アプリケーションの互換性"というドキュメントがダウンロードできるようになっています。このドキュメントではWindows Vista/Windows 7向のアプリケーションで注意しなければならない点について解説されています(Vistaのときのお粗末な文書に比べると結構詳細でわかりやすくなっています)。

2008年10月3日

レジストリのデータをエクスポートする

Windowsのレジストリに書き込んだ設定をファイルにエクスポートするには

REGEDIT.EXE /e "<exportfile>" "<keyname>"
<exportfile> ... 出力ファイル名(拡張子.REG)
<keyname> ... 出力する最上位のキー名(HKEY_CURRENT_USER\...)

を実行します。Windows Vistaではレジストリエディタ(REGEDIT.EXE)がUACで管理者権限を要求するため、管理者として実行する必要があります。