Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2017年01月14日] [更新 2017年04月15日] Delphi サンプルプログラム集
   
882_テキストをバイナリデータ形式で保存
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
一部 Delphi XE2 以降用
882_TextSaveBinary.zip [4,422 KB] 2017年04月15日版(EXE同梱)







テキストをバイナリデータ形式で保存


文字列も,ディスクに保存されている状態やメモリ上ではバイナリデータです.これは文字コード (コードポイント) の値や改行文字等の制御文字のバイナリ値です.それらの値に対応する文字が画面に表示されるわけです.

コンピュータのデータは全て 0, 1 のバイナリのデータです.つまり,画像であろうと文字列であろうと保存すればそれはバイナリのデータです.保存しなくても,メモリ上でもバイナリデータです.したがって,本ページのタイトルが適切でないことは否定できません.
バイナリとは,2 つの要素,状態で構成されているという意味です.バイは 2,トリプルのトリは 3 の意味です.

コミュニティの場で表題のような質問がありました.本ページは,何故そのような質問をするに至ったかを推測して,その解決策になるかも知れないと思われる内容を記事にしたものです.



[備考 1]
テキストとして表示できるデータを保存しているファイルを「テキストファイル」あるいは「テキスト形式のファイル」と言います.それ以外のファイルは,テキストファイルと区別が必要な場合に「バイナリファイル」あるいは「バイナリ形式のファイル」と言っています.画像ファイルもバイナリファイルです.
テキスト形式やバイナリ形式という形式の定義があるわけではありません.


[備考 2]
バイナリエディタというソフトを使用すると,テキストファイルに保存してあるバイナリデータを 16 進数で表示できます.バイナリエディタは,保存している文字をバイナリに変換して表示するわけではありません.
一方,テキストファイルをテキストエディタで開くと,それらのバイナリデータを「文字」に変換して表示しす.このバイナリデータは文字のコード (コードポイント) の値です.

現在一般的に使用されているコンピュータのデータは全てバイナリデータです.
コンピュータを利用する立場としては,「テキストエディタはテキストを保存したり読み出して表示,編集できます」という表現に間違いはありません.





01_TBinaryWriter を使用したバイナリデータ形式の保存


文字列,つまり,テキストをバイナリ形式で保存する1つの方法です.
TBinaryWriter で文字列をバイナリデータに変換して保存し,TBinaryReader を使用してバイナリデータを文字列として取得するサンプルです.



図1
Button1 クリック
  • テキストをバイナリで保存
図2
Button2 クリック

  • 保存したファイルから読み出して表示

リスト1
TBinaryWriter, TBinaryReader を使用して,テキストをバイナリデータとして保存
ここでのバイナリデータとは,通常の「文字」としては表示できないデータの意味
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;

  //文字列をセット
  Memo1.Lines.Add(#$4F60#$597D + ' !!');
  Memo1.Lines.Add(#$D842#$DFB7 + ' (サロゲートペアな文字)');
  Memo1.Lines.Add('テキストをバイナリテータで保存');

  FFilePath := ExpandFileName('..\000.bin');
end;

//=============================================================================
//  文字列(テキスト)をバイナリデータとしてファイルに保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFileStream : TFileStream;
  LBinWriter  : TBinaryWriter;
  LText       : string;
begin
  //ファイルストリームとTBinaryWriterを生成
  LFileStream := TFileStream.Create(FFilePath, fmOpenWrite or fmCreate);
  LBinWriter  := TBinaryWriter.Create(LFileStream, TEncoding.Unicode, False);
  try
    LText := Memo1.Lines.Text;

    //文字列をバイナリデータとして書き込む
    LBinWriter.Write(LText);
    LBinWriter.Close;
  finally
    FreeAndNil(LBinWriter);
    FreeAndNil(LFileStream);
  end;
end;

//=============================================================================
//  バイナリデータを文字列とてファイルから読み出す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFileStream : TFileStream;
  LBinReader  : TBinaryReader;
  LText       : string;
begin
  Memo1.Lines.Clear;

  //ファイルストリームとTBinaryReaderを生成
  LFileStream := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
  LBinReader  := TBinaryReader.Create(LFileStream, TEncoding.Unicode, False);
  try
    //バイナリデータを文字列として読み込む
    LText := LBinReader.ReadString;
    LBinReader.Close;
  finally
    FreeAndNil(LBinReader);
    FreeAndNil(LFileStream);
  end;
  //目的の文字列に代入
  Memo1.Lines.Text := LText;
end;

end.


上のコードで保存したファイルは,メモ帳では文字列として正常に表示できません.また,以下のように,通常のテキストファイル表示用のコードを実行しても正常に表示できません.

TBinaryWriter.Write は,文字列を指定したコードページに変換したバイト配列の先頭にメタ情報を付加しています.したがって,エンコードを適切に設定すれば,テキストの内容とテキストエディタの仕様にもよっては,テキストを表示できる可能性はあります.



図3
Button1 クリックの結果
  • Button2 クリックも同じ結果
図4
Button3 クリックの結果

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile(FFilePath);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LFileStream : TFileStream;
begin
  Memo1.Lines.Clear;
  LFileStream := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
  try
    Memo1.Lines.LoadFromStream(LFileStream);
  finally
    FreeAndNil(LFileStream);
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  LFileStream : TFileStream;
begin
  Memo1.Lines.Clear;
  LFileStream := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
  try
    Memo1.Lines.LoadFromStream(LFileStream, TEncoding.Unicode);
  finally
    FreeAndNil(LFileStream);
  end;
end;




02_ファイルのデータをバイナリ値 ( 16 進数 ) で表示 - バイナリダンプ


前項で保存したファイルのデータをバイナリの値で表示するサンプルです.
バイナリの値というのは 2 進数の 0, 1 のいずれかの値ですが,2 進数の値では冗長になってしまうので,実際には 16 進数で表示するのが一般的です.
このように,16 進数のバイナリの値で表示することを,バイナリダンプということがあります.

テキストエディタと呼ばれるメモ帳やテキスト表示用のコントロール等は,このようなバイナリの値を文字のコードと解釈して文字列を表示します.画像を表示するコントロールは,このようなデータを,画像の画素の色等のデータと解釈し,その処理の結果として画像を表示します.



図5
実行結果

リスト2
ファイルのデータをバイナリ値 (16 進数) で表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Font.Charset := ANSI_CHARSET;
  Memo1.Font.Name    := 'Courier New';
  Memo1.Font.Size    := 9;

  FFilePath := ExpandFileName('..\000.bin');
  OpenDialog1.InitialDir := ExtractFileDir(FFilePath);
end;

//=============================================================================
//  ファイルのデータをバイナリ値で表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStream   : TBytesStream;
  LStrList  : TStringList;
  LLineText : string;
  i         : Integer;
begin
  Memo1.Lines.Clear;
  OpenDialog1.FileName := ExtractFileName(FFilePath);
  if not OpenDialog1.Execute then exit;

  FFilePath := OpenDialog1.FileName;
  OpenDialog1.InitialDir := ExtractFileDir(FFilePath);

  LStream  := TBytesStream.Create;
  LStrList := TStringList.Create;
  try
    //ファイルの内容をストリームに読み出す
    LStream.LoadFromFile(FFilePath);

    //バイト配列の各要素(バイナリデータ)の値を16進数の文字に変換
    LLineText := IntToHex(LStream.Bytes[0], 2) + ' ';
    for i := 1 to LStream.Size - 1 do begin
      if (i mod 16) = 0 then begin
        LStrList.Add(LLineText);
        LLineText := '';
      end else
      if (i mod 8) = 0 then begin
        LLineText := LLineText + ' ';
      end;
      LLineText := LLineText + IntToHex(LStream.Bytes[i], 2) + ' ';

      Application.ProcessMessages;
    end;
    if LLineText <> '' then LStrList.Add(LLineText);

    Memo1.Text := LStrList.Text;
  finally
    FreeAndNil(LStrList);
    FreeAndNil(LStream);
  end;
end;

end.




03_データの暗号化と復号化 ( 簡易版 ) - TBytesStream


保存したテキストファイルを,テキストエディタ等で表示しても判読できないようにする目的であれば,暗号化して保存してしまう方法も考えられます.
データの暗号化と復号化には CryptAPI の CryptProtectData 関数,CryptUnprotectData 関数を使用する方法がありますが,バイナリデータに対して XOR 操作することにより,意味のない値に変換してしまう簡単な方法もあります.以下のコードは,その XOR 操作によるサンプルです.



リスト3
バイナリデータの XOR 操作による暗号化と復号化
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
    FXorValue : Byte;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;

  //文字列をセット
  Memo1.Lines.Add(#$4F60#$597D + ' !!');
  Memo1.Lines.Add(#$D842#$DFB7 + ' (サロゲートペアな文字)');
  Memo1.Lines.Add('テキストを暗号化して保存');

  FFilePath := ExtractFilePath(Application.ExeName) +'000.bin';
  FXorValue := 23;
end;

//-----------------------------------------------------------------------------
//  簡易暗号化復号化のルーチン
//  各バイト値に対してxorしているので,もう一度xorすれば元のバイト値に戻る
//  xorする値は任意(バイト値の範囲内)
//-----------------------------------------------------------------------------
procedure XorBytesStream(AStream: TBytesStream; AByte: Byte);
var
  i : integer;
begin
  for i := 0 to AStream.Size - 1 do begin
    AStream.Bytes[i] := AStream.Bytes[i] xor AByte;
  end;
end;

//=============================================================================
//  TMemoに表示しているテキストを暗号化してファイルに保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStream : TBytesStream;
begin
  LStream := TBytesStream.Create;
  try
    Memo1.Lines.SaveToStream(LStream, TEncoding.Unicode);
    XorBytesStream(LStream, FXorValue);
    LStream.SaveToFile(FFilePath);
  finally
    FreeAndNil(LStream);
  end;
end;

//=============================================================================
//  ファイルから読み出した暗号化したテキストを復号化して表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStream : TBytesStream;
begin
  Memo1.Lines.Clear;

  LStream := TBytesStream.Create;
  try
    LStream.LoadFromFile(FFilePath);
    XorBytesStream(LStream, FXorValue);
    Memo1.Lines.LoadFromStream(LStream, TEncoding.Unicode);
  finally
    FreeAndNil(LStream);
  end;
end;

end.


上のコードで暗号化したファイルをメモ帳で表示すると下図のようになります.



図6
暗号化したファイルをメモ帳で表示




04_データの暗号化と復号化 ( 簡易版 ) - TMemoryStream と TBytes


TBytesStream が実装されていない Delphi では TMemoryStream, TBytes を使用して,例えば以下のようにすれば動作します.
このサンプルコードは,Windows 7 U64(SP1) + Delphi 6(UP2) Pro で動作確認しました.非ユニコード版である Delphi 6 では「你好」と「𠮷」は表示できません.



リスト4
バイナリデータの XOR 操作による暗号化と復号化
Delphi 6 以降で使用可能なコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
    FXorValue : Byte;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;

  //文字列をセット
  Memo1.Lines.Add(#$4F60#$597D + ' !!');
  Memo1.Lines.Add(#$D842#$DFB7 + ' (サロゲートペアな文字)');
  Memo1.Lines.Add('テキストを暗号化して保存');

  FFilePath := ExtractFilePath(Application.ExeName) +'000.bin';
  FXorValue := 23;
end;


//  TBytesが実装されていないDelphiではこれを定義する
type
  TBytes = array of Byte;

//-----------------------------------------------------------------------------
//  簡易暗号化復号化のルーチン
//  各バイト値に対してxorしているので,もう一度xorすれば元のバイト値に戻る
//  xorする値は任意(バイト値の範囲内)
//
//  TMemoryStreamの読み出しと書き込み位置の設定に注意
//  TMemoryStreamのの値を1バイトづつ処理してもよい
//-----------------------------------------------------------------------------
procedure XorBytesStreamEx(AStream: TMemoryStream; AByte: Byte);
var
  LBytes : TBytes;
  i      : integer;
begin
  //バイト配列のサイズを設定
  SetLength(LBytes, AStream.Size);

  //ストリームの内容をバイト配列に読み出す
  AStream.Position := 0;
  AStream.ReadBuffer(LBytes[0], AStream.Size);

  //暗号化または復号化
  for i := 0 to Length(LBytes) - 1 do begin
    LBytes[i] := LBytes[i] xor AByte;
  end;

  //バイト配列のデータをストリームに書き込む
  AStream.Position := 0;
  AStream.WriteBuffer(LBytes[0], Length(LBytes));
  AStream.Position := 0;
end;

//=============================================================================
//  TMemoに表示しているテキストを暗号化してファイルに保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStream : TMemoryStream;
begin
  LStream := TMemoryStream.Create;
  try
    Memo1.Lines.SaveToStream(LStream);
    XorBytesStreamEx(LStream, FXorValue);
    LStream.SaveToFile(FFilePath);
  finally
    FreeAndNil(LStream);
  end;
end;

//=============================================================================
//  ファイルから読み出した暗号化したテキストを復号化して表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStream : TMemoryStream;
begin
  Memo1.Lines.Clear;

  LStream := TMemoryStream.Create;
  try
    LStream.LoadFromFile(FFilePath);
    XorBytesStreamEx(LStream, FXorValue);
    Memo1.Lines.LoadFromStream(LStream);
  finally
    FreeAndNil(LStream);
  end;
end;

end.




05_テキスト形式とバイナリ形式ファイルの判定


ファイルがバイナリ形式かテキスト形式であるかは,テキストエディタで表示すれば分かりますが,形式の定義があるわけではないので,データから判定するのは結構面倒です.

以下は,ファイルがテキスト形式であるか,バイナリ形式であるかを判定する例です.
[MIME 判定] ボタンのコードは,UrlMon.pas に実装されている FindMimeFromData 関数を使用しています.ファイルの MIME タイプがテキスト以外は全てバイナリファイルとしています.
[判定] ボタンは,昔の Delphi のコミュニティの場に掲載されていたコードです.テキスト以外のファイルでも,内容によってはテキストファイルと判定することがあります.



[備考 1]
下のコードで,MIME タイプの文字列判定の部分を text/plain に変更すると,プレインテキスト (プレーンテキスト) だけを検出できます.


[備考 2]
データの転送操作等で,テキストデータはコードページの変換が必要となることがあります.コートページの変換では元の文字コード (バイナリデータ) の値が変わります.一方,画像等のデータは,元のデータが変わってしまっては困ります.そのためにその区別が必要になります.



図7
実行結果の例
  • ファイルを Edit1 にドラッグ & ドロップ
  • [MIME 判定] ボタンをクリック
  • IDE から起動した場合はドラッグ & ドロップの機能は動作しない
  • EXE を管理者権限で実行した場合もドラッグ & ドロップの機能は動作しない

リスト5
テキスト形式かバイナリ形式かの判定
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    ApplicationEvents1: TApplicationEvents;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    function IsBinaryFile(Filename: string): Boolean;
    function IsBinaryFileByMIME(AFilename: string): Boolean;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI,
  UrlMon,
  StrUtils;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //ドラッグアンドドロップを許可
  //usesにShellAPIが必要
  DragAcceptFiles(Edit1.Handle, True);
end;

//=============================================================================
//  ファイルがバイナリかテキストかを判定する
//  ファイルのMIMEタイプで調べる
//  調べるファイルは,Edit1に入力するか,ドラッグ&ドロップする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if IsBinaryFileByMIME(Edit1.Text) then begin
    MessageBox(Handle, 'バイナリ', '結果', MB_ICONINFORMATION);
  end else begin
    MessageBox(Handle, 'テキスト', '結果', MB_ICONINFORMATION);
  end;
end;

//=============================================================================
//  ファイルがバイナリかテキストかを判定する
//  調べるファイルは,Edit1に入力するか,ドラッグ&ドロップする
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if IsBinaryFile(Edit1.Text) then begin
    MessageBox(Handle, 'バイナリ', '結果', MB_ICONINFORMATION);
  end else begin
    MessageBox(Handle, 'テキスト', '結果', MB_ICONINFORMATION);
  end;
end;

//=============================================================================
//  TApplicationEventsコンポのOnMessageイベント処理
//  エクスプローラからEdit1にドラッグ&ドロップした時の処理
//
//  このプログラムを管理者権限で実行した場合はドラッグ & ドロップできない
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LBuffer   : array [0..MAX_PATH-1] of Char;
  LFilePath : String;
begin
  if Msg.message = WM_DROPFILES then begin
    if (FindDragTarget(Msg.pt, False) = Edit1) then begin
      try
        if DragQueryFile(Msg.wParam, High(DWORD), nil, 0) = 1 then begin
          DragQueryFile(Msg.WParam, 0, @LBuffer, MAX_PATH);
          LFilePath  := LBuffer;
          Edit1.Text := LFilePath;
        end;
      finally
        //ドロップが完了したことを伝える
        DragFinish(msg.WParam);
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  バイナリかテキストファイルかを自動判定(自動判別)する関数
//  ファイルのMIMEタイプで調べる
//
//  FindMimeFromDataの使用にはusesにUrlMonが必要
//-----------------------------------------------------------------------------
function TForm1.IsBinaryFileByMIME(AFilename: string): Boolean;
const
  MAX_COUNT = 256;
var
  LStream    : TFileStream;
  LMimeType  : PWideChar;
  LBinReader : TBinaryReader;
  LBinArray  : TArray<Byte>;
  LReadSize  : Cardinal;
  LRet       : HRESULT;
begin
  Result := False;

  //ファイルストリームを生成
  LStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  if LStream.Size > MAX_COUNT then begin
    LReadSize := MAX_COUNT;
  end else begin
    LReadSize := LStream.Size;
  end;

  //TBinaryReaderのインスタンス生成
  LBinReader := TBinaryReader.Create(LStream, TEncoding.Default, False);
  try
    //バイト型配列に格納
    LBinArray := LBinReader.ReadBytes(LReadSize);

    LMimeType := nil;
    LRet := FindMimeFromData(nil, nil, LBinArray, LReadSize, nil, 0, LMimeType, 0);
    if LRet = S_OK then begin
      //プレインテキストだけを判定する場合は text/plain とする
      if not StrUtils.ContainsText(LMimeType, 'text/') then begin
        Result := True;
      end;
    end;
  finally
    FreeAndNil(LBinReader);
    FreeAndNil(LStream);
  end;
end;

//-----------------------------------------------------------------------------
//  バイナリかプレインテキストファイルかを自動判定(自動判別)する関数
// ファイルの先頭255バイト内にコントロールコードでなおかつ改行,タブ以外のもの
//  が6つ以上あればバイナリーファイルと判定
//  判定に使用するバイト数を大きくすればより確実となる
//
//  NiftyのFDELPHI 16番会議室 玉石混淆みんなで作るSample蔵
//  [01104] ぶんぶん鈴木さんのコードより検出部分を改変
//-----------------------------------------------------------------------------
function TForm1.IsBinaryFile(Filename: String): Boolean;
const
  ReadSize = 255;
var
  LFile      : File;
  LNumRead   : Integer;
  i          : Integer;
  LCtrlCount : integer;
  LOrdInt    : Integer;
  LReadBuff  : array [0..ReadSize-1] of Byte;
begin
  Result := False;

  AssignFile(LFile, Filename );
  Reset(LFile, 1);
  BlockRead( LFile, LReadBuff, ReadSize, LNumRead );
  CloseFile(LFile);

  LCtrlCount := 0;
  for i := 0 to LNumRead do begin
    LOrdInt := Ord(LReadBuff[i]);
    if (LOrdInt in [0..$1F,$7F,$FD..$FF]) and not (LOrdInt in [9..$0D,$20]) then begin
      Inc(LCtrlCount);
    end;
    if LCtrlCount > 5 then Result := True;
  end;
end;

end.




06_ユーザに変更されたくないテキストや画像の扱い


もし,ファイルをメモ帳等のテキストエディタで編集させたくない場合,保存した後にそのファイルを書き込み禁止属性にすることも考えられます.自アプリで操作する時は,書き込み禁止属性を解除して操作することになります.
また,そのファイルを隠し属性にしてしまうことも考えられます.ただし,エクスプローラで隠し属性のファイルも表示する設定にしてあればファイル名は表示されます.

SQLite のような単一のファイル形式の組み込み型で可搬型のテータベースを利用することも考えられます.データベースはいろいろな形式のデータが扱えます.パスワードも設定できます.

アプリケーション内で単に表示だけに使用するテキストまたは画像であって,利用者に変更されたくないのであれば,リソースとして EXE に埋め込む方法もあります.エクセルのブックやワードの文書は,リソースから取り出してディスクに保存しないと表示できませんが,テキストや画像は,ディスクに保存しなくても表示用のコントロールに表示できます.



[備考 1]
アプリケーションで使用するデータを,環境変数 %APPDATA% フォルダ内の階層下に保存すれば,エンドユーザに操作される可能性は低くなります.

[備考 2]
どんなファイルでも,パワーユーザであればテキストエディタ等で開いて表示できるようにできます.つまり,変更できます.したがって,実際には,表示した時に意味のない記号のような羅列であれば,これは操作したら問題が発生するかも知れない,と思わせることになります.
この意味では,拡張子を変えて,ダブルクリックで他のアプリからは表示できないようにするだけでもエンドユーザには効果があります.




07_ユーザに変更されたくないテキストや画像の扱い - ZIP 書庫の例


ファイルを ZIP 形式の書庫にして,拡張子を BIN 等に変更します.更にパスワードを設定すれば,他のプログラムからは簡単には操作できなくなります.Office 2007 以降のエクセルのブックやワードの文書も XML コードを格納した ZIP 書庫形式のファイルです.
ZIP 書庫内のファイルは,解凍展開してディスクに保存しなくても操作が可能です.



下に示すコードは,ZIP 書庫ファイルの操作例です.000.bin という名前で書庫を作成し,その中に Test.txt というファイル名でテキストを保存しています.ZIP 書庫の操作には TZipFile クラスを使用しています.TZipFile は Delphi XE2 以降で利用できます.



リスト6
ZIP 書庫の操作例.Delphi XE2 以降用
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
    FXorValue : Byte;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.Zip;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;

  //文字列をセット
  Memo1.Lines.Add(#$4F60#$597D + ' !!');
  Memo1.Lines.Add(#$D842#$DFB7 + ' (サロゲートペアな文字)');
  Memo1.Lines.Add('テキストを ZIP 書庫で保存');

  FFilePath := ExtractFilePath(Application.ExeName) +'000.bin';
  FXorValue := 23;
end;

//=============================================================================
//  TMemoに表示しているテキストをZIP書庫を作成してそこに保存
//  Delpoi XE2以降用
//
//  usesにSystem.Zipが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStream  : TStream;
  LZipFile : TZipFile;
begin
  LStream  := TMemoryStream.Create;
  LZipFile := TZipFile.Create;
  try
    //TMemoのテキストをストリームに保存
    Memo1.Lines.SaveToStream(LStream, TEncoding.Unicode);

    //ZIP書庫を開く(なければ作成)
    LZipFile.Open(FFilePath, zmWrite);
    //作成したZIP書庫にストリームのデータをTest.txtという名前で追加
    LStream.Position := 0;
    LZipFile.Add(LStream, 'Test.txt');
  finally
    FreeAndNil(LStream);
    FreeAndNil(LZipFile);
  end;
end;

//=============================================================================
//  ZIP書庫内の特定のファイルを取り出す
//  Delpoi XE2以降用
//
//  usesにSystem.Zipが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStream       : TStream;
  LZipFile      : TZipFile;
  LLocalHeader  : TZipHeader;
begin
  Memo1.Lines.Clear;

  LStream  := TMemoryStream.Create;
  LZipFile := TZipFile.Create;
  try
    //ZIP書庫を開く
    LZipFile.Open(FFilePath, zmRead);
    //ZIP書庫内の指定したファイルの内容をストリームに読み込む
    LZipFile.Read('Test.txt', LStream, LLocalHeader);

    //TMemoに表示
    Memo1.Lines.LoadFromStream(LStream, TEncoding.Unicode);
  finally
    FreeAndNil(LStream);
    FreeAndNil(LZipFile);
  end;
end;

end.




08_TurboPower Abbrevia 使用の ZIP 書庫操作 - Delphi 2009 以降用


次のコードも ZIP 書庫ファイルの操作例です.
上のコードは Delphi XE2 以降で使用可能な TZipFile クラスを使用していますが,Delphi 2009 以降であれば TurboPower Abbrevia が利用できます.その TurboPower Abbrevia を使用するサンプルコードです.



図8
設計時の画面

リスト7
ZIP 書庫の操作例
TurboPower Abbrevia を使用するサシンプル (Delphi 2009 以降用)
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, AbZipper, AbBase, AbBrowse, AbZBrows, AbUnzper;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    AbUnZipper1: TAbUnZipper;
    AbZipper1: TAbZipper;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
    FXorValue : Byte;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  AbUtils,
  AbArcTyp;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;

  //文字列をセット
  Memo1.Lines.Add(#$4F60#$597D + ' !!');
  Memo1.Lines.Add(#$D842#$DFB7 + ' (サロゲートペアな文字)');
  Memo1.Lines.Add('テキストを書庫ファイルで保存');

  FFilePath := ExtractFilePath(Application.ExeName) +'000.bin';
  FXorValue := 23;
end;

//=============================================================================
//  TMemoに表示しているテキストをZIP書庫を作成してそこに保存
//  拡張子が.ZIPでないと保存できないようなので,拡張子を変更して操作
//
//  atZip等の定数名の使用するにはusesに AbUtilsが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath : string;
  LStream   : TStream;
begin
  //ファイルの拡張子を.ZIPに変更
  LFilePath := ChangeFileExt(FFilePath, '.zip');
  if FileExists(FFilePath) then begin
    RenameFile(FFilePath, LFilePath);
  end;

  LStream  := TMemoryStream.Create;
  try
    //TMemoのテキストをストリームに保存
    Memo1.Lines.SaveToStream(LStream, TEncoding.Unicode);

    //ZIP書庫の設定
    AbZipper1.ArchiveType   := atZip;
    AbZipper1.BaseDirectory := ExtractFilePath(LFilePath);
    AbZipper1.FileName      := ExtractFileName(LFilePath);
    AbZipper1.Password      := '';
    AbZipper1.AutoSave      := False;
    //格納するファイルは置き換えとする
    AbZipper1.StoreOptions  := AbZipper1.StoreOptions + [soReplace];

    //ZIP書庫にファイルを追加して保存
    LStream.Position := 0;
    AbZipper1.AddFromStream('Test.txt', LStream);
    AbZipper1.Save;
    //保存したら閉じる
    AbZipper1.CloseArchive;

    //ファイルの拡張子を.BINに変更
    RenameFile(LFilePath, FFilePath);
  finally
    FreeAndNil(LStream);
  end;
end;

//=============================================================================
//  ZIP書庫内の特定のファイルを取り出す
//
//  atZip等の定数名の使用するにはusesに AbUtilsが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStream : TStream;
begin
  Memo1.Lines.Clear;

  LStream  := TMemoryStream.Create;
  try
    //既存のZIP書庫の設定
    AbUnZipper1.ArchiveType   := atZip;
    AbUnZipper1.BaseDirectory := ExtractFilePath(FFilePath);
    AbUnZipper1.FileName      := ExtractFileName(FFilePath);
    AbUnZipper1.Password      := '';

    //ZIP書庫内の指定のファイルをストリームに読み込む
    AbUnZipper1.ExtractToStream('Test.txt', LStream);
    //処理が終わったら閉じる
    AbUnZipper1.CloseArchive;

    //ストリームから読み出して表示
    LStream.Position := 0;
    Memo1.Lines.LoadFromStream(LStream, TEncoding.Unicode);
  finally
    FreeAndNil(LStream);
  end;
end;

end.




09_SciZipFile 使用の ZIP 書庫操作 - Delphi 6 以降用


次のコードも ZIP 書庫ファイルの操作例です.
Delphi 6 以降で使用可能な SciZipFile を使用する例です.SciZipFile はインストールする必要はありません.ライブラリのパスが通った場所に置けば利用できます.
SciZipFile は Ansi 版です.ユニコードは扱えません.




リスト8
ZIP 書庫の操作例
Delphi 6 以降で使用可能な SciZipFile を使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    FFilePath : string;
    FXorValue : Byte;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  SciZipFile;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.Clear;

  //文字列をセット
  Memo1.Lines.Add(#$4F60#$597D + ' !!');
  Memo1.Lines.Add(#$D842#$DFB7 + ' (サロゲートペアな文字)');
  Memo1.Lines.Add('テキストを ZIP 書庫ファイルで保存');

  FFilePath := ExtractFilePath(Application.ExeName) +'000.bin';
  FXorValue := 23;
end;

//=============================================================================
//  TMemoに表示しているテキストをZIP書庫を作成してそこに保存
//  SciZipFileを使用
//  SciZipFileはAnsi版なのでユニコードな文字は扱えない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStream  : TMemoryStream;
  LZipFile : TZipFile;
  {$IFDEF UNICODE}
  LRawStr : RawByteString;
  {$ELSE}
  LRawStr : AnsiString;
  {$ENDIF}
begin
  LStream  := TMemoryStream.Create;
  LZipFile := TZipFile.Create;
  try
    //TMemoのテキストをストリームに保存
    Memo1.Lines.SaveToStream(LStream);

    //ZIP書庫にファイルを追加
    //既に存在する場合は上書きとなる
    LZipFile.AddFile('Test.txt');

    //追加したファイルの内容を設定
    LStream.Position := 0;
    SetLength(LRawStr, LStream.Size);
    LStream.Read(LRawStr[1], LStream.Size);
    LZipFile.Data[0] := LRawStr;

    //保存
    LZipFile.SaveToFile(FFilePath);
  finally
    FreeAndNil(LStream);
    FreeAndNil(LZipFile);
  end;
end;

//=============================================================================
//  ZIP書庫内の特定のファイルを取り出す
//  SciZipFileを使用
//  SciZipFileはAnsi版なのでユニコードな文字は扱えない
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStream  : TStringStream;
  LZipFile : TZipFile;
begin
  Memo1.Lines.Clear;

  LZipFile := TZipFile.Create;
  try
    //ZIP書庫を読み出す
    LZipFile.LoadFromFile(FFilePath);

    //書庫の最初のファイルをストリームに読み出す
    LStream := TStringStream.Create(LZipFile.Data[0]);
    try
      //ストリームから読み出して表示
      LStream.Position := 0;
      Memo1.Lines.LoadFromStream(LStream);
    finally
      FreeAndNil(LStream);
    end;
  finally
    FreeAndNil(LZipFile);
  end;
end;

end.