高見知英の技術ログ

技術関係のログをQiitaから移行してきました。プログラミングのほか、使っているアプリの細かい仕様についてなど書いていきます。

PascalPad作ってみた

このブログはLazarus(FreePascal)アドベントカレンダー12日目の記事です。

qiita.com

ちょこっとしたプログラムの動作検証を行なうときにいちいちコードを書き込める場所を探すのが面倒なので、PascalPadというツールを作ってみました。

github.com

とりあえず機能的にはとりあえず使える段階なので、リリースを作っています。Windowsの実行バイナリも添付しておりますので良ければどうぞ。

github.com

Pascal Script

Lazarusには、初期状態でPascal Scriptというコンポーネントがインストールされています。Inno Setupの内部で使われているらしい*1

wiki.lazarus.freepascal.org

これを使うとPascalコードを読み込んでそのまま実行 なんてことが可能です。Script といいつつCompileメソッドを使ってコンパイルを行なったあと、Executeメソッドで実行できる というものですが、別に両方のメソッドを一つのメソッド内で呼び出せばスクリプト言語のような感覚で利用が可能です。

また、プラグインを追加して内部で使える関数を増やしたり、OnCompileイベントでTPSScript.AddMethodメソッドやTPSScript.AddFunctionメソッドで関数を追加するなど、結構使い道はありそうです。

まあ多種多様なプログラム言語に気軽に触れられるようになった今、なぜわざわざPascalをユーザーに使わせるのか という問題もありますが、たとえばプログラムの内部処理をPascalで補うとか、ゲームなどの細かい挙動をスクリプトで制御するとか、使い道はありそう。

*1:そういえばPascalスクリプトと言えばPPAなんてものもあったな・・・ 今はどうなっているのだろう?

Lazarusで拡張メソッド?

このブログはLazarus(FreePascal)アドベントカレンダー11日目の記事です。

qiita.com

Lazarusには(というかPascalには)、他の言語でいう拡張メソッド的な書き方を実現する仕組みがあります。

それがヘルパークラスです。

wiki.freepascal.org

えっ、そんな機能昔使ってたDelphiにはなかった・・・!と思ってたらDelphiにも(編集履歴を見る限り2015年には?)ある機能みたい。

docwiki.embarcadero.com

このあたりでとても気軽に使えるのが、TStringHelper。

www.freepascal.org

最近の言語であれば比較的自然な、文字列リテラルのあとに.を書いてメソッドを呼び出す なんてことが可能になります。

'%sのバージョン情報'.Format([Application.ExeName]);

みたいな感じで。

いくつか自分のところで使っている感じとしては、シチュエーションにもよりますがとても書きやすいので、どんどん活用していきましょう。

LazarusのGUIデザイナーでコンポーネントをコピーすると…

このブログはLazarus(FreePascal)アドベントカレンダー10日目の記事です。

qiita.com

今日は小ネタ・・・ですが、意外と重要な小ネタ。

LazarusでGUIデザイナー上よりコンポーネントをコピーしようとすると、lfmファイルに書き込まれているのとおなじテキスト形式で、GUIコンポーネントの情報がクリップボードに書き込まれます。

object FMonitor: TMemo
  Left = 0
  Height = 97
  Top = 0
  Width = 320
  Align = alTop
  Anchors = [akTop, akLeft, akRight, akBottom]
  BorderStyle = bsNone
  Color = clBtnFace
  Lines.Strings = (
    'FMonitor'
  )
  PopupMenu = FPopupMenu
  ReadOnly = True
  TabOrder = 0
end

なお、デフォルト値のまま変更していない値は表示されません。

プロジェクトインスペクタ上だと案外プロパティの状態が一覧しづらく、うっかり重要な情報を見落としてしまう ということが多々あります。

そんなときにとりあえずクリップボードコンポーネントをコピーしてみると、案外見落としていた問題に気付く なんてことがあるかもしれません。

Lazarusアプリのバージョン情報を取得する

このブログはLazarus(FreePascal)アドベントカレンダー9日目の記事です。

qiita.com

Delphiでアプリケーションのバージョン情報を取得するには、FileInfoユニットにあるTVersionInfoクラスを用います。

procedure TAboutDialog.FormCreate(Sender: TObject);
var
  VersionInfo: TVersionInfo;
begin
  VersionInfo := TVersionInfo.Create;
  try
    VersionInfo.Load(HINSTANCE);
    Self.VersionString.Caption:=Format('Version %d.%d.%d.%d', 
      [VersionInfo.FixedInfo.FileVersion[0],
        VersionInfo.FixedInfo.FileVersion[1],
        VersionInfo.FixedInfo.FileVersion[2],
        VersionInfo.FixedInfo.FileVersion[3]]);
  finally
    VersionInfo.Free;
  end;
end;

もちろん事前にプロジェクトのオプションより、バージョン番号を設定しておく必要があります。

f:id:TakamiChie:20211206101714p:plain
プロジェクトのオプション

TVersionInfo.VersionInfo.FixedInfo.FileVersionプロパティにバージョン番号が入っています。配列の要素は頭からメジャーバージョン、マイナーバージョン、リビジョン、ビルド番号となっています。

Lazarusにはコンパイルの度にビルド番号をインクリメントするというオプションがあります。これをチェックしておくと実行メニューの構築を実行したときに1インクリメントされます(コンパイルや実行ではダメなようです)。

個人的にはMicrosoft風に日付に関連する値にしてみたいところですが、このへんはツールを作るなりなんなりして対応するしかなさそうですね。

Lazarusのfor文

このブログはLazarus(FreePascal)アドベントカレンダー8日目の記事です。

qiita.com

8日目はアッサリ目にfor文の話し。

Lazarusでのfor文はfor [カウンタ] := [開始値] to [終了値] do ...です。他の言語のように増分を指定するStep値の指定はできません。常に開始値~終了値まで1ずつカウントがインクリメントされていきます。

ただし、-1は可能です。for [カウンタ] := [開始値] downto [終了値] do ...という構文となります。

wiki.freepascal.org

あれDelphiってこんな仕様だっけ・・・ と思ったら、Delphiもそんな仕様だった。

ただし、最近のPascalにはちゃんとfor each文的な構文も用意されています。for [カウンタ変数] in [配列等イテレータブルなオブジェクト] do文です。これを使えば配列を走査したりStep値を設定したfor文のようなことも可能ではあります。

なお、Lazarusは変数の宣言が必須な言語ですし、他の言語と違って変数宣言部と実装部が分離された言語なので、カウンタ変数の宣言はメソッドの先頭で行なう必要があります。ちょっとややこしいですがそこがPascalなので慣れるしかない。

procedure Method;
var
  i: Integer;
begin
  for i := 0 to 5 do ...
end;

という感じになります。

Lazarusでコモンダイアログを表示する

このブログはLazarus(FreePascal)アドベントカレンダー7日目の記事です。

qiita.com

7日目はLazarusでファイルを開く・保存ダイアログやメッセージボックスを表示する方法を。

コモンダイアログを表示する

Lazarusは、Delphiと同様、LCLに標準ダイアログを表示することができるクラスが揃っています。いちおうコンポーネント一覧にありますが、フォームに貼り付けて使うのも面倒ですし、普通にクラスを初期化して使うと楽です。

procedure TMainForm.ButtonClick(Sender: TObject);
var
  OFD: TOpenDialog;
begin
  OFD := TOpenDialog.Create(Self);
  try
    OFD.Filter:='EXE File|*.exe';
    if OFD.Execute then Path.Text:=OFD.FileName;
  finally
    OFD.Free;
  end;
end;

なお、「これってなんのダイアログだっけ・・・?」と思ったら、コンポーネントをフォームに貼り付けてダブルクリックすると、ダイアログのテストができます。これはたしかDelphiもおなじでしたね。

TCalcuratorDialog?

なお、コンポーネントパレットを見るとTCalculatorDialogなどという見慣れない名前のダイアログがいくつか見つかります。

f:id:TakamiChie:20211203200731p:plain
TCalculatorDialog

ソースコードを見ている感じWindows APIを呼んでどうにかしている様子はないため、ダイアログを作る際の参考になるかもしれません。

単純なメッセージボックスを表示する

また、単純なメッセージボックス、確認ダイアログなどは、DialogsユニットのMessageDlgまたはQuestionDlg関数を使います。

{ QuestionDlg }
Result:= QuestionDlg('caption', 'message', mtConfirmation, [mrYes, mrNo], 0);
{ MessageDlg }
MessageDlg('caption', 'message', mtInformation, [mbOK], 0);

第三引数Buttonsが要求する値が、QuestionDlgarray of const(mr○○の定数配列)、MessageDlgTMsgDlgButtons(mb○○の定数配列)と微妙に違うので注意。

単純なメッセージボックスを「作る」

なお、ダイアログをただちに表示するわけでなく、TFormオブジェクトとして取得し、いつでも表示できるようにする という方法もあります。

CreateMessageDialogというメソッドを使用します。

lazarus-ccr.sourceforge.io

これを使えば上記MessageDlgと同等のダイアログをオブジェクトとして取得することができます。たとえばおなじダイアログを何度も表示したり、フォントや表示位置を調整したいときには便利です。

Lazarusプログラムからショートカットを作る

このブログはLazarus(FreePascal)アドベントカレンダー6日目の記事です。

qiita.com

プログラムのショートカットファイル(*.Ink)を作るには、ほかの言語と同様にCOMオブジェクトのShellLinkオブジェクトを使用します。

詳細については公式Wikiを参照。

wiki.lazarus.freepascal.org

ここのミソは二つ

  • windows, shlobj, ActiveX, ComObj;の四つをusesに加えること。
  • IPersistFile.Save()の引数にはWideString型の変数を使うこと*1

なにやらReleaseしてないのが本能的に気になりますが、Releaseメソッドを呼ぼうとするとViolationErrorが発生しますのでとりあえずこのままで。

なお、このコードではWindows APISHGetSpecialFolderLocationを使っていますが、現在のLazarusではLazFileUtilsSHGetFolderPathUTF8というメソッドがあるのでそれを使ってもOK(引数のCSIDL値についてはshlobjユニットに定義されています)。

function Register4StartupClick(): Boolean;
var
  path: WideString;
  ISlink: IShellLink;
  IPFile: IPersistFile;
begin
  path := WideString(SHGetFolderPathUTF8(CSIDL_STARTUP) + 'AppName.lnk');
  if FileExists(path) then
  begin
    Result:= DeleteFile(path);
  end
  else
  begin
    ISlink:=CreateComObject(CLSID_ShellLink) as IShellLink;
    IPFile:= ISlink as IPersistFile;
    ISlink.SetPath(PChar(Application.ExeName));
    ISlink.SetWorkingDirectory(PChar(ExtractFileDir(Application.ExeName)));
    Result:= Succeeded(IPFile.Save(PWChar(path), False));
  end;
end;

スタートアップフォルダに自アプリのショートカットを作成したり削除したりします。

*1:String型ではダメです。どうせPWCharにキャストしてるから問題ないだろーと思ったらそんなことなかった