2023年3月28日

Firebirdロードマップ(2023/03)

Firebird 5.0 Beta 1がリリースされましたが、Firebirdのロードマップも更新されています。

Firebird: Roadmap

適当な要約
  • Firebird 2.0 - 2012年に開発終了。最新版は2012年04月の2.0.7
  • Firebird 2.1 - 2014年に開発終了。最新版は2014年12月の2.1.7
  • Firebird 2.5 - 2019年に開発終了。最新版は2019年06月の2.5.9
  • Firebird 3.0 - 安定版。最新版は2022年06月の3.0.10で、半年ごとの更新、次回(3.0.11)は2023年第1四半期内を予定。
  • Firebird 4.0 - 安定版。最新版は2022年08月の4.0.2で、四半期ごとの更新、次回(4.0.3)は2023年第1四半期内を予定。
  • Firebird 5.0 - 2021年05月に開発開始、2023年03月に最初のベータ版(5.0.0 Beta 1)をリリース。

2023年2月28日

RAD Studio/Delphi/C++Builder 11 Alexandria Release 3 (11.3)

RAD Studio/Delphi/C++Builder 11 AlexandriaのRelease 3 (11.3)がリリースされています。インストールには有効なUpdate Subscriptionが必要です。Registered Products Portalからダウンロードできます。

RAD Studio, Delphi, C++Builder 11.3 ISO
RAD Studio, Delphi, C++Builder 11.3 Web Install
RAD Studio, Delphi, C++Builder 11.3 ISO (inline)
RAD Studio, Delphi, C++Builder 11.3 Web Install (Inline)
RAD Studio, Delphi, C++Builder 11.3 ISO (2024 rebuild)
RAD Studio, Delphi, C++Builder 11.3 Web Install (2024 rebuild)

11 Alexandria - Release 3
New features and customer reported issues fixed in RAD Studio 11.3
List of publicly reported bugs fixed in 11.3

RAD Studio 11.3 Alexandria リリースのお知らせ (en)
RAD Studio 11.3へアップデートを行う際のご注意
11.3の新機能: TControlListコントロールで複数アイテムが選択可能に! (en)

RAD Studio 11.3 新機能ウェビナー(日本語吹替版)Part 1 - YouTube
RAD Studio 11.3 新機能ウェビナー(日本語吹替版)Part 2 - YouTube
RAD Studio 11.3 新機能ウェビナー(日本語吹替版)Part 3 - YouTube

2023/04/27追記: インストーラがPatch 1を含むものに差し替えられました。

新しいRAD Studio 11.3 Inline Buildのご案内 (en)

2024/02/21追記: GetItサーバの更新に伴ってインストーラが更新されました。
GetItアップデート: RAD Studio 11のGetItオンラインインストール、DelphiおよびC++Builder Community Editionが利用可能に! (en)

2023年2月27日

[書籍]データベース速攻入門

紀伊國屋書店新宿本店

データベース速攻入門 (Amazon)/堀内康夫、徳尾秀敬、とみたまさひろ、株式会社リブセンス、村田太、成田優隆、福本誠、大津桜子、藤本正比古、廣山豊、吉村守、杉江伸祐、杉山ジョージ、中村昌登著/技術評論社/ISBN 978-4-297-13362-7/2,420円

を購入。

[書籍]プログラマー脳

紀伊國屋書店新宿本店The Programmer's Brain (Amazon US)の翻訳である

プログラマー脳 (Amazon)/Felienne Hermans著/水野貴明訳/水野いずみ監訳/秀和システム/ISBN 9784798068534/3,300円

を購入。

2023年2月13日

[書籍]C#クックブック

紀伊國屋書店新宿本店C# Cookbook (Amazon US)の翻訳である

C#クックブック (Amazon)/ Joe Mayo著/鈴木幸敏訳/オライリー・ジャパン/ISBN 978-4-8144-0018-8/3,960円

を購入。

2023年2月1日

[書籍]Good Code, Bad Code

紀伊國屋書店新宿本店Good Code, Bad Code (Amazon US)の翻訳である

Good Code, Bad Code (Amazon)/Tom Long著/秋勇紀高田新山訳/山本大祐監訳/秀和システム/ISBN 9784798068169/3,960円

を購入。

2023/02開催のセミナー

2023年1月29日

[書籍][ebook]プログラマーのためのCPU入門

ラムダノート

プログラマーのためのCPU入門 (Amazon)/Takenobu Tani著/ラムダノート/ISBN 978-4-908686-16-0/3,740円(紙書籍+電子書籍(PDF))、3,520円(電子書籍(PDF)のみ)

を購入して、書籍版が配送されてきました。

2022年12月22日

ユーザを偽装してSYSTEMサービスからネットワークリソースにアクセスする

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

以前WNetAddConnection2を使って他のPCのネットワーク共有にアクセスする方法について書きましたが、これをサービスから実行するとWNetAddConnection2が時々エラー1312(ERROR_NO_SUCH_LOGON_SESSION)になる、という現象が発生します。調べてみたところ、サービスの場合はSystemではなくNetworkServiceで動作していないとこのような状態になるようです(常にエラーになるわけではなく、成功したりエラーになったりという感じ)。もちろんサービスをNetworkServiceで実行すればネットワークアクセスでエラーになることはないのですが、ローカルコンピュータに対するアクセスがUsersグループ相当に制限されてしまいます(サービスで使用する(Local)System/LocalService/NetworkServiceアカウントについてはWindowsテクニカルドキュメント(旧MSDN)のService User AccountsLocalService AccountNetworkService AccountLocalSystem Accountや@ITのWindowsのサービスで使用される「System」「Local Service」「Network Service」アカウントとは?:Tech TIPSを参照)。

そこでSystemアカウントで動作するサービスから実行することを前提に、ネットワークリソースにアクセスするときだけNetworkServiceアカウントに偽装するようにしてみます。
まずLogonUserでNetworkServiceとしてログインし、返されたトークンハンドルでImpersonateLoggedOnUserを呼び出すことでユーザを偽装します。
const
  LOGON32_LOGON_NEW_CREDENTIALS   = 9;
  {$EXTERNALSYM LOGON32_LOGON_NEW_CREDENTIALS}
var
  LogonUserUser: String;
  LogonUserDomain: String;
  LogonUserLogonType: DWORD;
  LogonUserLogonProvider: DWORD;
  hToken: THandle;
begin
  { Logon as 'NetworkService' user }
  LogonUserUser := 'NetworkService';
  LogonUserDomain := 'NT AUTHORITY';
  LogonUserLogonType := LOGON32_LOGON_NEW_CREDENTIALS;
  LogonUserLogonProvider := LOGON32_PROVIDER_DEFAULT;
  if LogonUser(PChar(LogonUserUser),PChar(LogonUserDomain),nil,
               LogonUserLogonType,LogonUserLogonProvider,hToken) = False then
  begin
    RaiseLastOSError;
  end;

  { Impersonate }
  if ImpersonateLoggedOnUser(hToken) = False then
  begin
    RaiseLastOSError;
  end;
これでこの後WNetAddConnection2で接続を試みるときにSystemアカウントではなくNetworkServiceアカウントであるかのように扱われます。

切断するときはまずWNetCancelConnection2を呼び出した後で、RevertToSelfで偽装を解除し、CloseHandleでトークンハンドルをクローズすることでログオフします。
  { Revert impersonation }
  if RevertToSelf() = False then
  begin
    RaiseLastOSError;
  end;

  { Loggoff }
  CloseHandle(hToken);
  hToken := INVALID_HANDLE_VALUE;
なおプロセス上で複数のスレッドが動作している場合(特にサービス)など、同一の共有リソース('\\<computername>\<sharename>')に対してWNetAddConnection2をネストして呼び出すとERROR_ALREADY_ASSIGNEDでエラーになります。これを防ぐには同一の共有リソースに対してWNetAddConnection2/WNetCancelConnection2が1回ずつ呼び出されるように、接続している共有リソースをプロセス全体で適切に管理する必要もあります。

2022年12月5日

2022年12月2日

InterBase 2020 Update 4

InterBase 2020 Update 4がリリースされています。バージョンは14.4.0.804となっています。

InterBase 2020U4 リリースノート - InterBase (en)
InterBase 2020 Update 4 の新機能 - InterBase (en)
Resolved Defects - InterBase (en)

Embarcadero InterBase 2020 Update 4 のリリース (en)

レコード型のフィールドのオフセットを取得する

このアーティクルはDelphi Advent Calendar 2022の2日目の記事です(3年ぶり7回目)。

Delphiのレコード型(Cの構造体に相当)は、スタックに配置することができるということ以外にも、特定のメモリレイアウトを定義することができることから、固定長ファイルや指定されたメモリ上のデータを解釈するために使われることがあります。このような場合に、定義したレコード型の特定のフィールドのオフセットが(主にデバッグ用に)欲しくなったりするのですが、Delphiには(SizeOfはあるのに)C/C++のoffsetofマクロ(stddef.h)のようなものが存在しません(RSP-39559)。そこで軽く検索してみたところStack Overflowにそのものずばりな投稿がありました。

delphi - Get Position of a struct var AKA Offset of record field - Stack Overflow
Delphi: Offset of record field - Stack Overflow

ではちょっと試してみましょう。まずレコード型とそのポインタ型を定義します。
type
  TFoo1 = record
    Bar: Boolean;
    Baz: Double;
    Qux: array [0..8] of Byte;
    Quux: Integer;
  end;
  PFoo1 = ^TFoo1;
これで
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Add(Format('TFoo1: SizeOf=%d',[SizeOf(TFoo1)]));
  Memo1.Lines.Add(Format('TFoo1.Bar: offset=%d',[NativeUInt(@(PFoo1(nil)^.Bar))]));
  Memo1.Lines.Add(Format('TFoo1.Baz: offset=%d',[NativeUInt(@(PFoo1(nil)^.Baz))]));
  Memo1.Lines.Add(Format('TFoo1.Qux: offset=%d',[NativeUInt(@(PFoo1(nil)^.Qux))]));
  Memo1.Lines.Add(Format('TFoo1.Quux: offset=%d',[NativeUInt(@(PFoo1(nil)^.Quux))]));
end;
のように、nilをレコード型へのポインタにキャスト→フィールドを参照→そのアドレスを取得→整数に変換とすることで、そのフィールドのオフセット値を取得できます(アドレスと同じサイズの符号なし整数はNativeUInt型)。では実行してみましょう。

TFoo1: SizeOf=32
TFoo1.Bar: offset=0
TFoo1.Baz: offset=8
TFoo1.Qux: offset=16
TFoo1.Quux: offset=28
Delphiのレコード型のデフォルトのアライメントマスクに従って配置されていることがわかります(TFoo1.QuuxのオフセットがDelphiのデフォルトのフィールドのアライメント(構造体アライメント)の8バイトに従った32ではなく、型(Integer)のサイズである4バイトで28になっていることに注意)。
しかし最初に書いたように、特定の(外部で規定された)メモリレイアウトに簡単にアクセスできるようにするためにレコード型を使用する、という目的を考えると、レコード型にはpackedを指定して、明示的にパディングするような場合が多いと思われます。ではレコード型をpacked recordにしてみましょう。
type
  TFoo2 = packed record
    Bar: Boolean;
    Baz: Double;
    Qux: array [0..8] of Byte;
    Quux: Integer;
  end;
  PFoo2 = ^TFoo2;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Add(Format('TFoo2: SizeOf=%d',[SizeOf(TFoo2)]));
  Memo1.Lines.Add(Format('TFoo2.Bar: offset=%d',[NativeUInt(@(PFoo2(nil)^.Bar))]));
  Memo1.Lines.Add(Format('TFoo2.Baz: offset=%d',[NativeUInt(@(PFoo2(nil)^.Baz))]));
  Memo1.Lines.Add(Format('TFoo2.Qux: offset=%d',[NativeUInt(@(PFoo2(nil)^.Qux))]));
  Memo1.Lines.Add(Format('TFoo2.Quux: offset=%d',[NativeUInt(@(PFoo2(nil)^.Quux))]));
end;
実行してみます。

TFoo2: SizeOf=22
TFoo2.Bar: offset=0
TFoo2.Baz: offset=1
TFoo2.Qux: offset=9
TFoo2.Quux: offset=18
意図通り、すべてのフィールドがパディングされることなく並んでいることがわかります。

逆アセンブルを見ると、コンパイラは各メンバのオフセットを知っててnil(=0)に加算しているので、最初からこれをもらう方法があれば…とは思います。また上記のStack Overflowの2番目の投稿にはRTTIを使った方法も紹介されていますが、RTTIのテーブルでループを回しながら文字列の比較をする必要があるため、それに比べればnilを使った方法のほうが優れていると考えられます。

ところで、packedを指定せずデフォルトのアライメントマスクに従って配置された場合、順序型はそのサイズでアライメントされる、との記述があります。つまり
type
  TFoo3 = record
    Bar: array [0..8] of Byte;
    Baz: Boolean;
    Qux: array [0..8] of Byte;
  end;
  PFoo3 = ^TFoo3;
のように1バイトアライメントされている配列のフィールドの次に1バイトの順序型のフィールドがあると、デフォルトのアライメントの8バイトに従ったパディングが行われることなく、連続した配置になる、ということになります(逆も同じ)。
procedure TForm1.Button3Click(Sender: TObject);
begin
  Memo1.Lines.Add(Format('TFoo3: SizeOf=%d',[SizeOf(TFoo3)]));
  Memo1.Lines.Add(Format('TFoo3.Bar: offset=%d',[NativeUInt(@(PFoo3(nil)^.Bar))]));
  Memo1.Lines.Add(Format('TFoo3.Baz: offset=%d',[NativeUInt(@(PFoo3(nil)^.Baz))]));
  Memo1.Lines.Add(Format('TFoo3.Qux: offset=%d',[NativeUInt(@(PFoo3(nil)^.Qux))]));
end;
実行してみると、

TFoo3: SizeOf=19
TFoo3.Bar: offset=0
TFoo3.Baz: offset=9
TFoo3.Qux: offset=10
と、確かにその通りになっています。またDelphi 2007までは型仕様が共通のフィールド(A, B: Extended;のように複数のフィールドが","で並べられて同じ型が指定されているフィールド)は暗黙にpackedとなる、という仕様も存在していました(いま知りました)。

このようなことから、レコード型を使用するときは、packedを指定せず完全にコンパイラにお任せにして特定のメモリレイアウトを必要としないようにするか、packedを指定してパディングも明示的に配置して特定のメモリレイアウトになるようにコーディングするか、どちらかにするべきだと考えられます(この結論そのものは当たり前すぎるものですが)。

なおpackedを指定してメモリレイアウトを完全に自分で制御する場合など、定義したレコード型が正しいサイズになっているかどうかは
{$IF SizeOf(TFoo2) <> 22}
{$MESSAGE ERROR 'SizeOf(TFoo2) is not 22 bytes.'}
{$IFEND}
のようにコンパイル時にチェックすることができます(Delphi 11.2 Alexandriaだとこれを書いた瞬間にLSPで判定が行われるため、{$MESSAGE}の行がエラー表示になるか淡色表示になるかでわかりますが)。