(2015/12/01~)
2015/12/02

ア・ラ・カ・ル・ト

さて、今年も Delphi Advent Calendar 2015 が始まりました!

これは Delphi Advent Calendar 2 日目の記事です...でもね、ネタがないんですよ。そこで今回は幾つかの小ネタをまとめて記事にする事にしました。

でるお (仮)

ドヤァ!

いきなりの出オチですが "でるお (仮)" です。以前はパワポに貼り付けられるように emf 形式のファイルを同梱していましたが、今回はそれに加えて SVG ファイル (プレーン SVG) も同梱してみました。

今時のブラウザで SVG が表示できない事はなく、ベクター形式なので拡大/縮小してもキレイなままなので潰しが利くと思います。試しに、ブラウザで〔Ctrl〕+〔マウスホイール〕をグリグリしてみてください。

でるお(仮) の画像はパワポで emf 形式のファイルを作成し、Inkscape を使って SVG ファイルに変換しました。

Download: deruo_20151201.zip

Visual Studio Code + Omni Pascal

先日紹介した Visual Studio Code ですが、Delphi のインストールパスやソースパスを指定しても、コード補完が利かなかったり、パラメータヒントが出なかったりしたので何故だろうと悩んでいましたが、理由が判明しました

結論を書くと、

これを守ればちゃんと補完が利くようです。開いたファイルが UTF-8 で文字化けしてしまう場合には、

  1. ステータスバーの文字エンコーディングをクリック
  2. "Reopen with Encoding" を選択
  3. "Japanese (Shift JIS)" を選択

これで OK です。Visual Studio Code は随時更新されているようなので、たまには [Help | Check for Updates...] で更新を確認してみてください。

Delphi と Delphi 製アプリケーションの動作環境

さて、思うところあって WikipediaDelphi ページを書き換えました。実は関連する C++ BuilderRAD Studio もちょっとだけ書き換えたのですけれど (^^;A

...それはいいのですが、箇条書きだと Delphi のどのバージョンでどの OS がサポートされてるのかわかりにくいです。ソースは DocWiki なのですが、これがまた探しづらい。

判りにくいのはマルチプラットフォームになった XE2 以降なので、XE2 以降の Delphi のバージョンとサポートしている OS のバージョンを一覧にしてみました。

  Windows (開発環境) OS X (PAServer) OS X (アプリケーション) iOS (アプリケーション) Android (アプリケーション)
Delphi XE2 Windows 7
Windows Server 2008
Windows Vista
Windows Server 2003 (SP1)
Windows XP (SP3)
OS X 10.7 (Lion)
OS X 10.6 (Snow Leopard)
OS X 10.7 (Lion)
OS X 10.6 (Snow Leopard)
iOS 5.x
iOS 4.2
 
Delphi XE3 Windows 8
Windows 7 (SP1)
Windows Vista (SP2)
Windows Server 2008
OS X 10.8 (Mountain Lion)
OS X 10.7 (Lion)
OS X 10.6 (Snow Leopard)
OS X 10.8 (Mountain Lion)
OS X 10.7 (Lion)
OS X 10.6 (Snow Leopard)
   
Delphi XE4 Windows 8
Windows 7 (SP1)
Windows Vista (SP2)
Windows Server 2008
OS X 10.8 (Mountain Lion)
OS X 10.7 (Lion)
OS X 10.8 (Mountain Lion)
OS X 10.7 (Lion)
iOS 6
iOS 5.1
 
Delphi XE5 Windows 8
Windows 7 (SP1)
Windows Vista (SP2)
Windows Server 2008
OS X 10.9 (Mavericks)
OS X 10.8 (Mountain Lion)
OS X 10.9 (Mavericks)
OS X 10.8 (Mountain Lion)
OS X 10.7 (Lion)
iOS 7
iOS 6
Android 4.4 (KitKat)
Android 4.1.x、4.2.x、4.3.x (Jelly Bean)
Android 4.0.3、4.0.4 (Ice Cream Sandwich)
Android 2.3.x (Gingerbread)
Delphi XE6 Windows 8, 8.1
Windows 7 (SP1)
Windows Vista (SP2)
Windows Server 2008
OS X 10.9 (Mavericks)
OS X 10.8 (Mountain Lion)
OS X 10.9 (Mavericks)
OS X 10.8 (Mountain Lion)
OS X 10.7 (Lion)
iOS 7
iOS 6
Android 4.4 (KitKat)
Android 4.1.x、4.2.x、4.3.x (Jelly Bean)
Android 4.0.3、4.0.4 (Ice Cream Sandwich)
Android 2.3.x (Gingerbread)
Delphi XE7 Windows 8, 8.1
Windows 7 (SP1)
OS X 10.10 (Yosemite)
OS X 10.9 (Mavericks)
OS X 10.8 (Mountain Lion)
OS X 10.10 (Yosemite)
OS X 10.9 (Mavericks)
OS X 10.8 (Mountain Lion)
iOS 8
iOS 7
Android 4.4 (KitKat)
Android 4.1.x、4.2.x、4.3.x (Jelly Bean)
Android 4.0.3、4.0.4 (Ice Cream Sandwich)
Android 2.3.x (Gingerbread)
Delphi XE8 Windows 10
Windows 8, 8.1
Windows 7 (SP1)
OS X 10.10 (Yosemite)
OS X 10.9 (Mavericks)
OS X 10.10 (Yosemite)
OS X 10.9 (Mavericks)
iOS 8
iOS 7
Android 5 (Lolipop)
Android 4.4 (KitKat)
Android 4.1.x、4.2.x、4.3.x (Jelly Bean)
Android 4.0.3、4.0.4 (Ice Cream Sandwich)
Delphi 10 Seattle Windows 10
Windows 8, 8.1
Windows 7 (SP1)
OS X 10.11 (El Capitan)
OS X 10.10 (Yosemite)
OS X 10.9 (Mavericks)
OS X 10.11 (El Capitan)
OS X 10.10 (Yosemite)
OS X 10.9 (Mavericks)
iOS 9
iOS 8
iOS 7
Android 5 (Lolipop)
Android 4.4 (KitKat)
Android 4.1.x、4.2.x、4.3.x (Jelly Bean)
Android 4.0.3、4.0.4 (Ice Cream Sandwich)

関連して、以下のトピックも更新してあります。

余談ですが、私は C++Builder に詳しくないので (Delphi も言うほど詳しくはないですけどね)、どなたか C++Builder のページを適宜書き換えていただけると幸いです m(_ _)m

Delphi と BlueStacks と Android-x86

BlueStacks という Windows で動作する Android エミュレータがありまして、ちょっと細工すれば Delphi 製 Android アプリが動作します。XE5 / Appmethod 1.13 では細工なしに動作していたのですけどね。

しかしながらこの BlueStacks、独自のガワが被せてあり、常駐もするしで開発用の Android エミュレータとして使うには少々使い勝手がよくありません。そこでオススメするのが当サイトでも紹介している Android-x86 です。Android-x86 は任意の VM に Linux としてインストールする事で動作します。

以前は Android-x86 上で Delphi 製 Android アプリは動作しなかったのですが...

以下の手順で Android-x86 上でも Delphi 製 Android アプリが動作するようになります (10 Seattle 用)。

具体的には、

  1. FMX.Platform.Android.pas をプロジェクトフォルダにコピーする
  2. FMX.Platform.Android.pas を以下のように書き換える
    function TWindowManager.RetrieveContentRect: TRect;
    var
      Activity: JActivity;
      NativeWin: JWindow;
      DecorView: JView;
      ContentRectVisible, //add
      ContentRect: JRect;
    begin
      Activity := TAndroidHelper.Activity;
      if Activity nil then
      begin
        NativeWin := Activity.getWindow;
        if NativeWin nil then
        begin
          FStatusBarHeight := FNewContentRect.top;
          ContentRect := TJRect.Create;
          DecorView := NativeWin.getDecorView;
          DecorView.getDrawingRect(ContentRect);
    
          // add start
          CallInUIThread(
            procedure 
            begin
              if (not PlatformAndroid.GetFullScreen(nil)) and 
                (SharedActivity.getWindow.getAttributes.flags and TJWindowManager_LayoutParams.JavaClass.FLAG_FULLSCREEN <> 
                  TJWindowManager_LayoutParams.JavaClass.FLAG_FULLSCREEN) then 
              begin
                ContentRectVisible := TJRect.Create;
                DecorView.getWindowVisibleDisplayFrame(ContentRectVisible);
                if (ContentRect.top < 1or (ContentRectVisible.top < FStatusBarHeight) then 
                begin
                  ContentRect.top := ContentRectVisible.top;
                  FNewContentRect.top := ContentRectVisible.top;
                  FStatusBarHeight := FNewContentRect.top;
                end;
              end;
            end
           );
          // add end
       
          Result := TRect.Create(Round(FNewContentRect.left / FScale), Round(FNewContentRect.top / FScale),
       Round(ContentRect.right / FScale), Round(ContentRect.bottom / FScale));
      end;
     end;
    end;
  3. [プロジェクト | 配置] で、ローカル名が libnative-activity.so になってるもののチェックを外してビルドします。

    今回は x86 となっているものだけを外せばいいのですが、Amazon ストアへ登録するアプリの場合には armeabi / mips のものも外す必要があるようですし、libnative-activity.so は対応していないデバイスで "Application does not support this device" を表示するためだけに用意されたスタブですので、全部外していいと思います。

VM の Android-x86 がデバイスとして認識されない場合には、VM のネットワーク設定を見直し、adb connect (IP アドレス) をやってみましょう。詳細についてはこちらをどうぞ

Android-x86"Generic Android-x86" として認識されます。

今回はちょっと VM の用意が間に合わなかった (ウチの回線は貧弱なのです) ので、Aspire One 実機にインストールした Android-x86 で試していますが...

ご覧の通りですよ! \(^o^)/

Android-x86 を VM で起動してデバッグ環境として使うのもアリですが、押し入れに眠っているネットブックを引っぱり出してきて Android-x86 をインストールしてアプリを作ってみるのも楽しいかもしれませんね。2015 年のロードマップでは、Atom (Intel) な Android に対応するためのコンパイラの登場も示唆されていますし、今後がますます楽しみです。

See Also:

では今回はここまでとさせていただきますね。まとまりのない乱雑なトピックで申し訳ありませんでした m(_ _)m


2015/12/07

ファイルをコピーする (Delphi Programming)

これは Delphi Advent Calendar 2015 7 日目の記事です。

長らく「Delphi にはファイルコピーの機能がない」と言われてきました。今までどうやってファイルコピーをしていたのかと言うと、Windows API の CopyFile() を使うなり、AssignFile() を使うなり、TFileStream を使うなり、Shell API の SHFileOperation() を使っていました。

ですが、Delphi 2010 からは IOUtils の TFile クラスにある Copy() メソッドを使ってファイルコピーを行えるようになっています。

  TFile.Copy(SrcFile, DstFile, True);   

とても簡単ですね。では、今回はここまでとさせていただ...く訳ないでしょう? (^^;A

続・ファイルをコピーする (Delphi Programming)

ローカルコピーは IOUtils.TFile.Copy() を使えばいいのですが、それ以外では?

何も第28回デベロッパーキャンプのおさらいをしようってんじゃありません。"それ以外" というのはこういう意味です。

[uFileGet.pas]
{*******************************************************}
{                File Get Utility Unit                  }
{          (C) 2015 Hideaki Tominaga (DEKO)             }
{*******************************************************}

{ ---------------------------------------------------------------------------- }
// USAGE:
//
// ・FILE (Local)
//   FileGet(fgpFile, '', 'C:\PICTURE\SAMPLE.JPG', 'C:\TEST\SAMPLE.JPG');
//
// ・FILE (Network)
//   FileGet(fgpFile, 'FILE-SERVER', '\PICTURE\SAMPLE.JPG', 'C:\TEST\SAMPLE.JPG');
//
// ・FILE (Network with Login)
//   FileGet(fgpFile, 'FILE-SERVER', '\PICTURE\SAMPLE.JPG', 'C:\TEST\SAMPLE.JPG', 'USER', 'PASS');
//
// ・HTTP
//   FileGet(fgpHTTP, 'www.sample.com', '/picture/sample.jpg', 'C:\TEST\SAMPLE.JPG');
//
// ・HTTPS
//   FileGet(fgpHTTPS, 'www.sample.com', '/picture/sample.jpg', 'C:\TEST\SAMPLE.JPG');
//
// ・HTTP (with Authentication)
//   FileGet(fgpHTTP, 'www.sample.com', '/picture/sample.jpg', 'C:\TEST\SAMPLE.JPG', 'USER', 'PASS');
//
// ・HTTP (with Port)
//   FileGet(fgpHTTP, 'www.sample.com', '/picture/sample.jpg', 'C:\TEST\SAMPLE.JPG', '', '', 8080);
//
// ・FTP
//   FileGet(fgpFTP, 'ftp.sample.com', '/picture/sample.jpg', 'C:\TEST\SAMPLE.JPG', 'USER', 'PASS');
//
// ・FTP (with Passive)
//   FileGet(fgpFTP, 'ftp.sample.com', '/picture/sample.jpg', 'C:\TEST\SAMPLE.JPG', 'USER', 'PASS', 0, True);
//
{ ---------------------------------------------------------------------------- }

unit uFileGet;

interface

{$IFDEF CONDITIONALEXPRESSIONS}
  {$IF CompilerVersion >= 25.0}
    {$LEGACYIFEND ON}
  {$IFEND}
  {$IF CompilerVersion >= 21.0}
    {$DEFINE D2010OVER}
  {$IFEND}
{$ENDIF}

uses
  SysUtils, Classes,
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF}
  {$IFDEF D2010OVER}
  IOUtils,
  {$ENDIF}
  IdBaseComponent, IdComponent, IdTCPConnection, IdFTPCommon,
  IdTCPClient, IdExplicitTLSClientServerBase, IdHTTP, IdFTP,
  IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL, IdSSLOpenSSL,
  IdAuthentication, IdAuthenticationDigest;

type
  TFleGetProtocol = (fgpFile, fgpHTTP, fgpHTTPS, fgpFTP);

  { TFileGet }
  TFileGet = class
  private
    FHost: string;
    FPort: WORD;
    FPass: string;
    FUser: string;
    FSecure: Boolean;
    FPassive: Boolean;
  public
    constructor Create; virtual;
    function Get(Src, Dst: string): Boolean; virtual; abstract;
    property Host: string read FHost write FHost;
    property User: string read FUser write FUser;
    property Pass: string read FPass write FPass;
    property Port: WORD read FPort write FPort;
    property Passive: Boolean read FPassive write FPassive;
    property Secure: Boolean read FSecure write FSecure;
  end;

  { TFTPFileGet }
  TFTPFileGet = class(TFileGet)
  public
    function Get(Src, Dst: string): Boolean; override;
  end;

  { THTTPFileGet }
  THTTPFileGet = class(TFileGet)
  private
    FNeedReload: Boolean;
    procedure IdHTTP_Authorization(Sender: TObject;
      Authentication: TIdAuthentication; var Handled: Boolean);
  public
    function Get(Src, Dst: string): Boolean; override;
  end;

  { TSMBFileGet }
  TSMBFileGet = class(TFileGet)
  public
    function Get(Src, Dst: string): Boolean; override;
  end;

  // FileGet()
  function FileGet(aProtocol: TFleGetProtocol; aHost, aSrc, aDst: string;
    aUser: string = ''; aPass: string = ''; aPort: WORD = 0; aPassive: Boolean = False): Boolean;

implementation

procedure MakeDirectory(aDir: string);
begin
  // ディレクトリが存在しなければ作成
  if not DirectoryExists(ExtractFilePath(aDir)) then
    try
      ForceDirectories(ExtractFilePath(aDir));
    except
    end;
end;

{ TFileGet }

constructor TFileGet.Create;
begin
  FHost := '';
  FPass := '';
  FUser := '';
  FPort := 0;
  FSecure := False;
  FPassive := False;
end;

{ TFTPFileGet }

function TFTPFileGet.Get(Src, Dst: string): Boolean;
// http://mrxray.on.coocan.jp/Delphi/plSamples/773_Indy_FTPGetPut.htm
var
  IdFTP: TIdFTP;
  FS: TFileStream;
begin
  // FTP で接続
  Result := False;
  MakeDirectory(Dst);
  IdFTP := TIdFTP.Create(nil);
  try
    IdFTP.Host         := FHost;
    if FPort = 0 then
      IdFTP.Port := 21
    else
      IdFTP.Port := FPort;
    IdFTP.Passive      := FPassive;
    IdFTP.Username     := FUser;
    IdFTP.Password     := FPass;
    IdFTP.TransferType := ftBinary;
    FS := TFileStream.Create(Dst, fmCreate);
    try
      IdFTP.Connect;
      IdFTP.Get(Src, FS);
      IdFTP.Disconnect;
      Result := True;
    except
    end;
    FS.Free;
  finally
    IdFTP.Free;
  end;
end;

{ THTTPFileGet }

function THTTPFileGet.Get(Src, Dst: string): Boolean;
// http://mrxray.on.coocan.jp/Delphi/plSamples/772_Indy_HTTPGet.htm
var
  lStream: TMemoryStream;
  lUrl: string;
  lProtocol: string;
  lPort: string;
  IdHTTP: TIdHTTP;
begin
  // HTTP で接続
  Result := False;
  MakeDirectory(Dst);
  IdHTTP := TIdHTTP.Create(nil);
  lStream := TMemoryStream.Create;
  try
    FNeedReload := False;
    IdHTTP.OnAuthorization := IdHTTP_Authorization;
    if FSecure then
      begin
        lProtocol := 'https';
        IdHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create;
      end
    else
      begin
        lProtocol := 'http';
      end;
    if FPort = 0 then
      lPort := ''
    else
      lPort := Format(':%d', [FPort]);
    lUrl := Format('%s://%s%s%s', [lProtocol, FHost, lPort, Src]);
    try
      IdHTTP.Head(lUrl);
      Result := True;
    except
    end;
    if not Result then
      Exit;
    Result := False;
    if (FUser <> ''and (IdHTTP.Request.Authentication = nilthen
      begin
        // BASIC 認証
        IdHTTP.Request.Clear;
        IdHTTP.Request.BasicAuthentication := True;
        IdHTTP.Request.Username := FUser;
        IdHTTP.Request.Password := FPass;
      end;
    try
      IdHTTP.Get(lUrl, lStream);
      Result := True;
    except
    end;
    lStream.Position := 0;
    lStream.SaveToFile(Dst);
  finally
    lStream.Free;
    IdHTTP.Free;
  end;
end;

procedure THTTPFileGet.IdHTTP_Authorization(Sender: TObject;
  Authentication: TIdAuthentication; var Handled: Boolean);
begin
  // Digest 認証
  Authentication.Username := Self.FUser;
  Authentication.Password := Self.FPass;
  if Authentication is TIdDigestAuthentication then
    begin
      TIdDigestAuthentication((Sender as TIdHTTP).Request.Authentication).Uri := (Sender as TIdHTTP).Request.URL;
      TIdDigestAuthentication(Authentication).Method := 'GET';
    end;
  Handled := True;
  FNeedReload := True;
end;

{ TSMBFileGet }

function TSMBFileGet.Get(Src, Dst: string): Boolean;
// http://owlsperspective.blogspot.jp/2008/07/wnetaddconnection2.html
var
  NewConnection: Boolean;
  lSrcPath, lSrcDir: string;
  lResult: Integer;
  {$IFDEF MSWINDOWS}
  { DoLogOn BEGIN }
  function DoLogOn(var RemotePath: stringconst UserName: string;
    const Password: string): Integer;
  var
    NR: TNetResource;
  begin
    RemotePath := ExcludeTrailingPathDelimiter(RemotePath);
    with NR do
      begin
        dwType       := RESOURCETYPE_ANY;
        lpLocalName  := nil;
        lpRemoteName := PChar(RemotePath);
        lpProvider   := nil;
      end;
    Result := WNetAddConnection2(NR, PChar(Password), PChar(UserName), 0);
  end;
  { DoLogOn END }
  { DoLogOff BEGIN }
  function DoLogOff(const RemotePath: string): Integer;
  var
    PathName: string;
  begin
    PathName := RemotePath;
    Result := WNetCancelConnection2(PChar(PathName), 0, False);
  end;
  { DoLogOff END }
  {$ENDIF}
begin
  result := False;
  NewConnection := False;
  MakeDirectory(Dst);
  if FHost <> '' then
    begin
      lSrcPath := Format('\\?\UNC\%s%s', [FHost, Src]);            // UNC パス (ファイル、拡張 UNC パス)
      lSrcDir  := ExtractFilePath(Format('\\%s%s', [FHost, Src])); // UNC パス (ディレクトリ)
    end
  else
    begin
      lSrcPath := Src;
      lSrcDir  := Src;
    end;
  {$IFDEF MSWINDOWS}
  // ネットワーク資源にユーザー名/パスワードを指定して接続
  if FUser <> '' then
    begin
      lResult := DoLogOn(lSrcDir, FUser, FPass);
      if (lResult <> ERROR_ALREADY_ASSIGNED) and (lResult <> ERROR_SESSION_CREDENTIAL_CONFLICT) then
        begin
          // 接続されていなければ接続
          lResult := DoLogOn(lSrcDir, FUser, FPass);
          if lResult <> NO_ERROR then
            Exit;
          NewConnection := True;
        end;
    end
  else
  {$ENDIF}
    begin
      // ファイル存在確認
      {$IFDEF D2010OVER}
      if not TFile.Exists(lSrcPath) then
        Exit;
      {$ELSE}
      if not FileExists(lSrcPath) then
        Exit;
      {$ENDIF}
    end;
  // ファイルコピー
  {$IFDEF D2010OVER}
  TFile.Copy(lSrcPath, Dst, True);
  {$ELSE}
  CopyFile(PChar(lSrcPath), PChar(Dst), False);
  {$ENDIF}
  {$IFDEF MSWINDOWS}
  // 新規で接続されていたら切断
  if NewConnection then
    DoLogOff(lSrcDir);
  {$ENDIF}
  result := True;
end;

function FileGet(aProtocol: TFleGetProtocol; aHost, aSrc, aDst: string;
  aUser: string; aPass: string; aPort: WORD; aPassive: Boolean): Boolean;
var
  FG: TFileGet;
begin
  case aProtocol of
    fgpHTTP, fgpHTTPS:
      FG := THTTPFileGet.Create;
    fgpFTP:
      FG := TFTPFileGet.Create;
  else
    FG := TSMBFileGet.Create;
  end;
  try
    FG.Host    := aHost;
    FG.Port    := aPort;
    FG.User    := aUser;
    FG.Pass    := aPass;
    FG.Secure  := (aProtocol = fgpHTTPS);
    FG.Passive := aPassive;
    result := FG.Get(aSrc, aDst);
  finally
    FG.Free;
  end;
end;

end.

コメント行でバレバレですが、このユニットを使うと、FileGet() という単一の関数だけで、

いずれからでもコピー可能です (もちろん個別にクラスを使ってもいいのですが)。中で使ってるロジックは Mr.XRAY さんトコのとふーさんトコのコードをパクってあります (w

これを使えば大抵のサーバ (オンプレミスを含む) からファイルを持って来れますね。

ZIP ファイルを解凍する (Delphi Programming)

ZIP ファイルの解凍方法はこのサイトに Tips があります。

XE2 以降では System.Zip.TZipFile を使えばいいのですが、それ以前だと何らかのライブラリが必要となります。Tips では TurboPower Abbrevia は 2010 以降としてありますが、最近確認してみた所、Delphi 6 以降のすべてのパッケージが揃っているようです。

そうなると、この辺もまとめてみたいですよね、こんな感じで。

[uZipUtils.pas]
{*******************************************************}
{                   Zip Utility Unit                    }
{          (C) 2015 Hideaki Tominaga (DEKO)             }
{*******************************************************}

unit uZipUtils;

{$IFDEF CONDITIONALEXPRESSIONS}
  {$IF CompilerVersion >= 25.0}
    {$LEGACYIFEND ON}
  {$IFEND}
  {$IF CompilerVersion >= 21.0}
    {$DEFINE D2010OVER}
  {$IFEND}
  {$IF CompilerVersion >= 23.0}
    {$DEFINE DXE2OVER}
  {$IFEND}
{$ENDIF}

interface

uses
  {$IFDEF D2010OVER}
  IOUtils,
  {$ENDIF}
  {$IFDEF DXE2OVER}
  Zip,
  {$ELSE}
  AbZipper, AbBase, AbBrowse, AbZBrows, AbUnzper, AbArcTyp,
  {$ENDIF}
  SysUtils;

  procedure DecompressZip(aZipFile: string; aDst: string);
  {$IFNDEF D2010OVER}
  procedure Directory_Delete(const Dir: string);
  {$ENDIF}

implementation

{$IFNDEF D2010OVER}
procedure Directory_Delete(const Dir: string);
var
  sDir: string;
  Rec: TSearchRec;
begin
  sDir := IncludeTrailingPathDelimiter(Dir);
  if FindFirst(sDir + '*.*', faAnyFile, Rec) = 0 then
    try
      repeat
        if (Rec.Attr and faDirectory) = faDirectory then
          begin
            if (Rec.Name <> '.'and (Rec.Name <> '..'then
              Directory_Delete(sDir + Rec.Name);
          end
        else
          DeleteFile(sDir + Rec.Name);
      until FindNext(Rec) <> 0;
    finally
      FindClose(Rec);
    end;
  RemoveDir(sDir);
end;
{$ENDIF}

procedure DecompressZip(aZipFile: string; aDst: string);
var
  lOutput: string;
{$IFNDEF DXE2OVER}
  Unzipper: TAbUnZipper;
{$ENDIF}
begin
{$IFDEF DXE2OVER}
// Use System.Zip
  if not TFile.Exists(aZipFile) then
    Exit;
  lOutput := IncludeTrailingPathDelimiter(aDst);
  TZipFile.ExtractZipFile(aZipFile, lOutput);
{$ELSE}
// Use TurboPower Abbrevia
  if not FileExists(aZipFile) then
    Exit;
  UnZipper := TAbUnZipper.Create(nil);
  try
    lOutput := ExcludeTrailingPathDelimiter(aDst);
    UnZipper.BaseDirectory := lOutput;
    UnZipper.ExtractOptions := [eoCreateDirs, eoRestorePath];
    UnZipper.FileName := aZipFile;
    UnZipper.ExtractFiles('*.*');
  finally
    UnZipper.Free;
  end
{$ENDIF}
end;

end.

Delphi 6~10 Seattle まで、DecompressZip() という同一の関数で解凍できます。XE 以前の環境には TurboPower Abbrevia をインストールする必要がありますけどね。

「勘のいい子は嫌いだよ!」

上記二つのコード (ユニット) に、第28回デベロッパーキャンプ でご紹介した uIOUtilsEx を組み合わせれば、何ができるでしょう?

そう、アプリケーションの (自動) アップデータを実装できます。今回の Advent Calendar の記事は、アップデータの実装を行う記事となります。

ここからが本題

アプリケーションのアップデータを実装するには2つの方法があります。

前者は最初期のリリースからアップデータを配布しておく必要があります。途中でアップデータを配布するのは面倒です。何故ならば、ショートカットがアプリケーションを指しており、これを変更する手間が必要になるからです。 長所としては、アプリケーションの更新タイミングがシビアではないという事です。アップデートファイルを元々のアプリケーションフォルダに直接展開できます。

後者は後付けが可能です。但し、自身を再起動させる必要があるため、ファイルコピーのタイミングがちょっとシビアです。下手をするとファイルが使用中である可能性があります。

ぶっちゃけた話、自動アップデートをやってくれるクラスが公開されているのですが、ネットワーク関連で Indy ではなく Synapse を使っていたり、古い Abbrevia を使っていたり、説明とコードの記述が異なっていたりしますので、自分で作った方が早いと思います。

折角なので、当サイトで公開している IBConsole 日本語版+α にアップデート機能を組み込んでみました。

アップデートチェックが行われた時の動作は以下の通りです。

  1. サーバーに置いてある更新情報ファイル (Ini ファイル形式) をローカルにコピーして読む (取得できなかったらエラー)。
    [Updater]
    Version=63
    File=ibc_unicode_rel63_win32_ja.zip
  2. バージョンとファイル名 (ZIP) を取得
  3. 更新情報ファイルに書かれていたバージョンと現行バージョンを比較 (最新バージョンだったら抜ける)
  4. サーバーから更新ファイル (ZIP) を取得 (取得できなかったらエラー)
  5. 更新ファイルをテンポラリに展開 (ZIP 解凍)
  6. 更新ファイルに含まれている update.exe (ファイルコピー&アプリケーション再起動用) を起動。この時、IBConsole のフルパス名を渡す
    // 変数 UpdaterFile には update.exe のフルパス名が入っている
    ShellExecute(0'open', PChar(UpdaterFile), PChar(Format('"%s"', [ParamStr(0)])), '', SW_SHOWNORMAL);
  7. IBConsole を終了
  8. update.exe でテンポラリから IBConsole のインストールフォルダへファイルを上書き
  9. コマンドラインに渡されたフルパス名を使って IBConsole を起動
  10. update.exe を終了

この方法だと毎回、更新ファイルに含まれている update.exe を起動するので、配布ファイルの構成が変わったり何か問題が起きても update.exe にパッチ的な何かを仕込む事ができます。 アプリケーション固有のコードが書いてあるため、アップデートチェックや update.exe のソースコードは公開しませんが、やっている事は上記の通りなので参考にして実装するのは難しくないと思います。

既にアーカイブも用意してあります。Unicode 版なら rel.62、ANSI 版なら rel.98 を DL してアップデートチェックを行うと それぞれ rel.63 と rel.99 に更新されます (このバージョンはアップデートチェック用のバージョンで、機能に差異はありません)。

uFileGet.pasuZipUtils.pas は VCL 関連ユニットを uses していないので、FireMonkey でも動作するハズです。業務アプリ等にこういったアップデータを仕込めば Active Directory でない (使えない) 環境でも、配布が楽になりますね!

Delphi Advent Calendar 2015 用の記事に対するご意見ご感想などありましたら、Delphi Forum までどうぞ。では今回はこの辺で!


 BACK   古いのを読む   新しいの読む