Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

自作プラグイン更新のお知らせ。

今更で申し訳ないですが><

macOSでDockメニューに独自項目を追加する。

サンプルコード

uses
  ...,
  System.Messaging,
  Macapi.ObjCRuntime, Macapi.ObjectiveC, Macapi.AppKit, Macapi.Helpers,
  FMX.Helpers.Mac;

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMessageManager.DefaultManager.SubscribeToMessage(TApplicationDockMenuMessage,
    procedure(const Sender: TObject; const M: TMessageBase)
    var
      menu: NSMenu;
      item: NSMenuItem;
    begin
      menu := TNSMenu.Create;
      TNSAutoreleasePool.OCClass.addObject((menu as ILocalObject).GetObjectID);

      item := menu.addItemWithTitle(StrToNSStr('AAA'), sel_getUid('onMenuClicked:'), StrToNSStr(''));
      item.setTag(1);
      item := menu.addItemWithTitle(StrToNSStr('BBB'), sel_getUid('onMenuClicked:'), StrToNSStr(''));
      item.setTag(2);

      TApplicationDockMenuMessage(M).Value.ReturnValue := menu;
    end);

  TMessageManager.DefaultManager.SubscribeToMessage(TApplicationMenuClickedMessage,
    procedure(const Sender: TObject; const M: TMessageBase)
    var
      item: NSMenuItem;
    begin
      item := TApplicationMenuClickedMessage(M).Value.Sender as NSMenuItem;
      case item.tag of
        1: ... // AAA
        2: ... // BBB
      end;
    end);
end;

class or class = ???

1200万パワーだ!

昨日の記事の応用編。なんと演算子オーバーロードの代替表記を使えば、通常では組み合わせることのできない演算子オーバーロードジェネリクスを同時に使えます。

サンプルコード

https://gist.github.com/lynatan/673e574faa8343fa01d7a91e75065c54

type
  TObjectHelper = class helper for TObject
  public
    class function &&op_LogicalOr<T: class>(A, B: T): T; static;
  end;

class function TObjectHelper.&&op_LogicalOr<T>(A, B: T): T;
begin
  if A <> nil then
    Result := A
  else
    Result := B;
end;

procedure Test;
var
  sl1, sl2, sl3: TStringList;
begin
  sl1 := nil;
  sl2 := TStringList.Create;
  sl3 := sl1 or sl2; // -> sl3 = sl2
end;

Use operator overloading for classes with non-ARC compiler

はじめに

演算子オーバーロードDelphi 2006で導入された機能で、自分が定義したレコードに+や-といった演算子の動作を実装できます。この機能はARC対応コンパイラであればレコードだけでなくクラスでも使えるんですが、非ARCコンパイラ、つまりx86向けのようなコンパイラでは使うことができませんでした。
ところが何気なくコードを書いていたところ、ちょっと違う書き方をするだけで演算子オーバーロードがクラスでも使えることを発見しました。また、同じ記述方法でクラスヘルパーやレコードヘルパーで演算子オーバーロードを後付けするという従来では完全に不可能だったことまでできるようになり、コーディングの幅が広がる可能性があります。

使い方

https://gist.github.com/lynatan/886ed984d230ac1b42dd89ed42ab2214

type
  TStringListEx = class(TStringList)
  public
    // class operator In(const A: string; B: TStringListEx): Boolean;
    class function &&op_In(const A: string; B: TStringListEx): Boolean; static;
  end;

  TPointHelper = record helper for TPoint // Possible in class helper!
  public
    // class operator Equal(const A: TPoint; const B: string) : Boolean;
    class function &&op_Equality(const A: TPoint; const B: string): Boolean; static;
  end;

class function TStringListEx.&&op_In(const A: string; B: TStringListEx): Boolean;
begin
  Result := B.IndexOf(A) >= 0;
end;

class function TPointHelper.&&op_Equality(const A: TPoint; const B: string): Boolean;
begin
  Result := Format('%d,%d', [A.X, A.Y]) = B;
end;

var
  sl: TStringListEx;
  pt: TPoint;
  ret: Boolean;
begin
  sl := TStringListEx.Create;
  sl.Add('AAA');
  sl.Add('BBB');
  ret := 'AAA' in sl;
  Writeln(BoolToStr(ret, True)); // -> 'True'

  pt := Point(123, 456);
  ret := pt = '123,456';
  Writeln(BoolToStr(ret, True)); // -> 'True'
  ...
end;

対応表

class operator class function
Implicit &&op_Implicit
Explicit &&op_Explicit
Negative &&op_UnaryNegation
Positive &&op_UnaryPlus
Inc &&op_Increment
Dec &&op_Decrement
LogicalNot &&op_LogicalNot
Trunc &&op_Trunc
Round &&op_Round
In &&op_In
Equal &&op_Equality
NotEqual &&op_Inequality
GreaterThan &&op_GreaterThan
GreaterThanOrEqual &&op_GreaterThanOrEqual
LessThan &&op_LessThan
LessThanOrEqual &&op_LessThanOrEqual
Add &&op_Addition
Subtract &&op_Subtraction
Multiply &&op_Multiply
Divide &&op_Division
IntDivide &&op_IntDivide
Modulus &&op_Modulus
LeftShift &&op_LeftShift
RightShift &&op_RightShift
LogicalAnd &&op_LogicalAnd
LogicalOr &&op_LogicalOr
LogicalXor &&op_ExclusiveOr
BitwiseAnd &&op_BitwiseAnd
BitwiseOr &&op_BitwiseOr
BitwiseXor &&op_BitwiseXOR
Include &&op_Include
Exclude &&op_Exclude

ところで

今ドキュメントを見ると最初から演算子オーバーロードはレコード向けに導入されたような記述がほとんどなんですが、Delphi 2006では普通にクラス向けに演算子オーバーロードが使えませんでしたっけ?次に買った2010ではしれっと無かったことになってて不思議だったんですが、結構大きな機能を削除されたわりにネット上で反応が全くと言っていいほど見られないので、僕の記憶がおかしいのかな…と思ってました。どっちなんだろう。記憶違いでした/(^o^)\

Starterでフォームデザイナオプションが表示されないバグの修正プラグイン作った。

VM上で検証してたら原因が分かったので直しました。

ダウンロード

https://github.com/lynatan/StarterFix
「Clone or Download -> Download ZIP」でダウンロードできます。
Delphinusにも対応しているので、インストールしている方はそちらから導入した方が簡単です。

Delphinusパッケージマネージャの紹介。

パッケージマネージャとはなんぞや

Delphiは数多くのパッケージの集合で成り立っています。パッケージには製品本体に元々付属しているもの以外に企業や個人が作った追加パッケージがあり、この追加パッケージを簡単に導入できるようにするのがパッケージマネージャです。

Delphinusとはなんぞや

Delphinusはパッケージマネージャのひとつで、Embarcadero公式のGetItパッケージマネージャと違い、申請不要で誰でも自作のパッケージを公開することができます。というのも、Delphinusは自前のサーバを持たず、ファイルの管理は全てGitHubに任せています。そのGitHubの検索APIを使い、特定の条件に合致したプロジェクトをパッケージとしてリストアップし、インストールできるようになっています。そのため、GitHubソースコードを公開すれば誰でもパッケージを公開することができるというわけです。
ちなみに対応バージョンはXE以降となっています。

Delphinusのインストール方法

Gitが統合されているバージョンの場合(XE7以降)


Delphiを起動し、「ファイル→バージョン管理リポジトリから開く」を選択、バージョン管理システムとしてGitを選び、ソース欄にDelphinusのプロジェクトページのURLを、保存先には任意のフォルダを選んでください。
OKを押すとダウンロードが開始し、それが終わると開くプロジェクトを選択する画面が出るので、リストの中からDelphiXE6フォルダ内のDelphinus.dprojを選択してOKを押します。
プロジェクトを開いたらプロジェクトマネージャの「Delphinus.bpl」上で右クリックし、「インストール」を選択してください。「ツール→Delphinus」というメニューが追加されていればインストール成功です。

Gitが統合されていないバージョンの場合(XE6以前)

Delphinusのプロジェクトページにアクセスし、緑色の「Clone or download」というボタンを押して「Download ZIP」を選択し、ソースコード一式をダウンロードします。
次に、ダウンロードしたファイルを任意のフォルダに展開し、Delphiを起動してXE-XE5はDelphiXEフォルダ内の、XE6以降はDelphiXE6フォルダ内のDelphinus.dprojを開いてください。
プロジェクトを開いたらプロジェクトマネージャの「Delphinus.bpl」上で右クリックし、「インストール」を選択してください。「ツール→Delphinus」というメニューが追加されていればインストール成功です。

Delphinusでのパッケージのインストール

「ツール→Delphinus」メニューを選択すると、Delphinus Packagemanagerが起動します。左上の緑の更新ボタンを押すとパッケージ一覧が表示されるので、好きなパッケージを選択し、インストールボタン(下向き矢印の付いたアイコン)を押すことでパッケージがインストールできます。

アクセストークンの設定

これは任意の設定項目ですが、歯車アイコンを押すことでアクセストークンが設定できます。これはDelphinusがバックエンドとしてGitHub APIを利用して検索していることから、APIの利用制限を緩和するために設定するもので、無くても利用自体は可能です。
アクセストークンはGitHubにサインインし、メニューのSettingsからPersonal access tokensを選び、Generate new tokenを押すことで生成できます。付与する権限は最小限でいいとのことなので、特にチェックは付けないまま生成してOKです。トークンの文字列が入手できたら、歯車アイコンを押して表示されたエディットに貼り付け、Testボタンを押して成功すれば登録されます。

Delphinusでのパッケージの公開

Delphinusでは誰でも自作のパッケージを公開できます。パッケージはGitHub上でパブリックリポジトリとして公開されていて、かつoriginalであるもの(forkではないもの)である必要があります。
パッケージの登録に必要な手順はPublishing your Project for Delphinusにまとめられていますが、大きく言えば「Delphinus.Info.jsonリポジトリのルートに置く」「Delphinus.Install.jsonリポジトリのルートに置く」「readmeに『Delphinus-Support』という文字列を加える」の3点です。
詳しい説明は省きますが、Delphinusに登録されているパッケージは全てGitHub上に公開されているので、これらの設定は各パッケージのソースコードが参考になります。

DelphiとFreePascalの最適化比較。

はじめに

先日a-1-4がa-5に最適化されないDelphiコンパイラの残念さを知り、ついでに巷で言われている「FreePascalは遅い」は本当なのかが気になったので軽く調べてみた。

検証用コード

Wikipediaより拝借したコードをPascalに書き直した以下のコードを使用、それぞれのコンパイラでどの程度最適化がかかるか調べる。ともにx86ターゲットで、Delphiは10.1 BerlinのO+、FreePascalは3.0.0の-O4で検証した。

function GetValue: Integer; inline;
var
  a, b, c: Integer;
begin
  a := 30;
  b := 9 - a div 5;
  c := b * 4;
  if c > 10 then
    c := c - 10;
  Result := c * (60 div a);
end;

var
  a: Integer;
begin
  a := GetValue;
  Random(a); // aが無効化されないように
end;

ちなみに変数を展開していけば分かりますが、最終的に4になります。

FreePascalの場合

mov eax,$0000001e
mov eax,$00000003
mov eax,$0000000c
mov eax,$00000002
mov eax,$00000004

最終的に4が導き出されてはいるものの、途中の不要な定数が残ってしまっているのが残念。

Delphiの場合

mov ebx,$0000001e
mov eax,ebx
mov ecx,$00000005
cdq 
idiv ecx
push eax
mov eax,$00000009
pop edx
sub eax,edx
mov ecx,eax
add ecx,ecx
add ecx,ecx
cmp ecx,$0a
jle @@1
sub ecx,$0a
@@1:
mov eax,$0000003c
cdq 
idiv ebx
imul ecx

ザ・ウンコ。FreePascalの足元にも及ばないまさかの最適化ゼロ。あまりにも酷いので、最適化オンにしてもデバッグ実行しちゃうとオフになるバグでもあるのでは?と思い、Releaseビルドした上で別のデバッガで逆アセンブルかけてみましたが結果は同じでした…。ちなみに「x64ターゲットだとマシ」という話もあったんで念のためx64でも試したところ、ほぼ同じコードが生成されて膝から崩れ落ちましたw

ちなみに

VC++の場合、

mov eax,$00000004

まで最適化されます。さすがですね…。