2019年12月24日

[書籍][eBook]Mastering Delphi Programming

Packt Publishingで注文した

Mastering Delphi Programming: A Complete Reference Guide (Amazon US, Amazon JP)/Primož Gabrijelčič著/Packt Publishing/ISBN 9781838989118/44.99USD(printed+ebook)

が配送されてきました(今回の配送もDHLで、インドのチェンナイからの発送でした)。2019/12/16に注文して9日目の到着、44.99USD=5,042JPY(1USD=112.07JPY)でした。

2019年12月21日

RAD Studio/Delphi/C++Builder 10.3.3 Rio Android Services Patch

RAD Studio/Delphi/C++Builder 10.3.3 RioのAndroid Services Patchがリリースされています。AAB(Android App Bundle)としてパッケージ化されたアプリケーションでネイティブライブラリをロードできずにAndroidサービスが実行できない問題を修正します。

30905 RAD Studio 10.3.3 Android Services Patch

RAD Studio 10.3.3 Androidサービスパッチ (en)

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)

2019年12月14日

RAD Studio/Delphi/C++Builder 10.3.3 Rio Android Debugger Patch

RAD Studio/Delphi/C++Builder 10.3.3 RioのAndroid Debugger Patchがリリースされています。これはAndroid上でのデバッグに関係するいくつかの問題を修正します。
30904 RAD Studio 10.3.3 Android Debugger Patch

Delphi 10.3.3 Android デバッグパッチ (en)

RAD Studio/Delphi/C++Builder 10.3.3 Rio IDE and VCL Patch

RAD Studio/Delphi/C++Builder 10.3.3 RioのIDE and VCL Patchがリリースされています。これはコード補完ウィンドウのスクロールバーの表示が残ってしまう問題(RSP-26731)とTActionManager/TPopupActionBarコンポーネントを使用しているとコンパイルに失敗する問題(RSP-27035)を修正します。

30903 RAD Studio 10.3.3 IDE and VCL Patch

RAD Studio 10.3.3 IDE/VCLパッチ、ローカリゼーションパッチ (en)

2019年12月9日

RAD Studio/Delphi/C++Builder 10.3.3 Rio localization patch

RAD Studio/Delphi/C++Builder 10.3.3 Rioのlocalization patchがリリースされています。これはローカライズ(DE/FR/JA)の問題を解決するものです。

30901 RAD Studio 10.3.3 localization patch (DE/FR)
30902 RAD Studio 10.3.3 localization patch (JA)

RAD Studio 10.3.3 IDE/VCLパッチ、ローカリゼーションパッチ (en)

2019年12月6日

RAD Studio/Delphi/C++Builder 10.3.3 Rio iOS Linking Patch

RAD Studio/Delphi/C++Builder 10.3.3 RioのiOS Linking Patchがリリースされています。これはiOSアプリケーションをDebugビルドでリンクしようとするとエラーになる問題(RSP-23698)を解決するものです。

30900 RAD Studio 10.3.3 iOS Linking Patch

2019年12月1日

2019/12開催のセミナー