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







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


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

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

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



[備考 1]
テキストを保存したファイルをテキストファイル,あるいはテキスト形式のファイルと言います. メモ帳などで,テキストファイルを開いて表示すると,保存したテキストが表示されます.ただし, メモ帳で開いて表示しても文字化けするテキストファイルもあります.

テキストファイル以外のファイルをバイナリファイル,あるいはバイナリ形式のファイルと言うこともありますが,それは,テキストファイルと区別が必要な場合に使用する用語です.通常は,保存されているデータの内容に応じて,画像ファイル,あるいはビットマップ画像ファイルとか,音声ファイル,EXE ファイルなどと言っています.

テキスト形式とかバイナリ形式という形式の定義はありません.
バイナリファイルやバイナリ形式のファイルとは,「保存したのがテキストではないファイル」,あるいは「テキストではないデータを保存したファイル」などという意味です.テキストファイルも,バイナリファイルも,ディスクに保存されているデータはバイナリです.コンピュータのデータは全てバイナリです.


[備考 2]
バイナリ形式という形式の定義があるわけではuありません.
定義がないのですから,テキストをバイナリ形式で保存するというのが,一体どのようなことなのか分かりません.そこで,本ページでは,ファイルをメモ帳で開いて表示した時に,期待したテキストが表示されない場合,そのファイルをバイナリ形式のファイルと言うことにしました.

最初のサンプルは,バイナリデータの先頭に適当なバイナリデータを追加して,バイナリデータを別のコードポイント (別の文字) として処理させてしまう方法です.
後の方の暗号化は,一定の規則に従って,バイナリ値を全く別の値にしてしまう方法です.
つまり,これらは「テキストを文字化け表示させる方法」です.処理したデータをファイルに保存します.そのファイルをメモ帳で表示すると文字化けしますが,プログラムの処理では元のテキストに戻せます.





01_TBinaryWriter を使用してバイナリ形式で保存


TBinaryWriter は,ファイルの先頭に,書き込むデータのバイト数のメタ情報を追加してデータを書き込みます.その結果,保存したテキストは,メモ帳等のテキストエディタでは文字列として認識できないようにすることができます.
TBinaryWriter で保存したファイルは,TBinaryReader を使用して読み込みます.TBinaryReader は,メタ情報から読み出すバイト数を取得してデータを読み込みます.



図1
Button1 クリック
  • テキストをバイナリ形式で保存
図2
Button2 クリック
  • 保存したファイルから読み出して表示
図3
保存したファイルをメモ帳で表示

リスト1
TBinaryWriter を使用して,テキストをバイナリ形式で保存
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;

//=============================================================================
//  文字列を TBinaryWriter を使用して,テキストをバイナリ形式で保存
//=============================================================================
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;

//=============================================================================
//  保存したデータを TBinaryReader を使用して読み出す
//=============================================================================
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.


上のコードで保存したファイルは,メモ帳では文字列として正常に表示できません.また,以下のように,通常のテキストファイル表示用のコードを実行しても正常に表示できません.
このような現象を一般に「文字化け」と言います.



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


リスト2
TBinaryWriter を使用して保存したデータを,TBinaryReader を使用しないで読み込む
//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFilePath := ExpandFileName('..\000.bin');
  OpenDialog1.InitialDir := ExtractFileDir(FFilePath);
end;

//=============================================================================
//  TBinaryWriter を使用して保存したデータを読み込む
//  TMemo1.Lines.LoadFromFile メソッドを使用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;
  OpenDialog1.FileName := ExtractFileName(FFilePath);
  if not OpenDialog1.Execute then exit;

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

  Memo1.Lines.LoadFromFile(FFilePath);
end;

//=============================================================================
//  TBinaryWriter を使用して保存したデータを読み込む
//  TFileStream のストリームを TMemo1.Lines.LoadFromFile メソッドで読み込む
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFileStream : TFileStream;
  LMemStream  : TMemoryStream;
  LText       : string;
begin
  Memo1.Lines.Clear;
  OpenDialog1.FileName := ExtractFileName(FFilePath);
  if not OpenDialog1.Execute then exit;

  LFileStream := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
  try
    Memo1.Lines.LoadFromStream(LFileStream);
  finally
    FreeAndNil(LFileStream);
  end;
end;

//=============================================================================
//  TBinaryWriter を使用して保存したデータを読み込む
//  TFileStream のストリームを TMemo1.Lines.LoadFromFile メソッドで読み込む
//  Unicode として読み込む
//  TBinaryWriter では文字列を Unicode で保存している
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LFileStream : TFileStream;
begin
  Memo1.Lines.Clear;
  OpenDialog1.FileName := ExtractFileName(FFilePath);
  if not OpenDialog1.Execute then exit;

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

  LFileStream := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
  try
    Memo1.Lines.LoadFromStream(LFileStream, TEncoding.Unicode);
  finally
    FreeAndNil(LFileStream);
  end;
end;


TBinaryWriter は,Write7BitEncodedInt メソッドを使用して,テキストのバイト数をファイルの先頭にメタ情報として追加して保存しているだけです.
このメタ情報はファイルの先頭に書き込まれます.T BinaryWriter で保存したファイルを読み出して,このメタ情報のデータを除外すれば元のテキストのバイナリデータとなります.以下は,そのことを確認するサンプルです.



図6
実行結果

リスト3
TBinaryWriter がファイル先頭に書き込んだメタ情報を除外して,ファイルのデータを読み込む
type
  TMyBinReader = class(TBinaryReader);

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FFilePath := ExpandFileName('..\000.bin');
  OpenDialog1.InitialDir := ExtractFileDir(FFilePath);
end;

//=============================================================================
//  TBinaryWriter を使用して保存したデータを読み込む
//
//  TBinaryWriter は Write7BitEncodedInt でファイルの先頭にバイト数を書き込む
//  そのバイト数が実際の文字列のバイナリデータのバイト数
//  そのバイト数を TBinaryReader.Read7BitEncodedInt メソッドで取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBinReader  : TBinaryReader;
  LFileStream : TFileStream;
  LStartByte  : Integer;
  LMemStream  : TMemoryStream;
  LText       : string;
begin
  Memo1.Lines.Clear; 

  OpenDialog1.FileName := ExtractFileName(FFilePath);
  if not OpenDialog1.Execute then exit;

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

  LFileStream := TFileStream.Create(FFilePath, fmOpenRead or fmShareDenyNone);
  LBinReader  := TBinaryReader.Create(LFileStream, TEncoding.Unicode, False);
  try
    // 実際の文字列のバイナリデータの保存開始位置を取得
    LStartByte := LFileStream.Size - TMyBinReader(LBinReader).Read7BitEncodedInt;
  finally
    FreeAndNil(LBinReader);
    FreeAndNil(LFileStream);
  end;

  LMemStream := TMemoryStream.Create;
  try
    // ファイルのデータをメモリストリームに読み込む
    LMemStream.LoadFromFile(FFilePath);

    // 文字列のエレメント数を設定
    SetLength(Ltext, (LMemStream.Size - LStartByte) div SizeOf(Char));

    // 読出し開始位置まで移動して文字列型の変数に読み込む
    LMemStream.Position := LStartByte;
    LMemStream.Read(LText[1], Length(LText) * SizeOf(Char));

    // メモリストリームから読み出した文字列を TMemo で表示
    Memo1.Lines.Text := LText;
  finally
    FreeAndNil(LMemStream);
  end;
end;


保存したファイルを,メモ帳等のテキストエディタで表示した時に文字化けさせるという上のサンプルと同じような処理は,以下のようなコードでも可能です.
このコードは,テキストの先頭に $08 というバイトデータを追加して保存します.$08 は,文字コードの先頭バイトとしては使用されていません.そうすることによって,保存したバイナリテータを元のテキストのコードポイント (文字コード) とは別の値として認識させるようにします.
ファイルからデータを読み込んだ時は,先頭の余分なバイトデータを削除します.

この方法は,Shift_JIS や UTF-8 では利用てきません.Shift_JIS と UTF-8 には 1 バイトの文字あります.$08 だけが文字として認識しない結果となります.



リスト4
メモ帳等のテキストエディタで表示した時に文字化けするデータにして保存
//=============================================================================
//  フォーム生成時の処理
//=============================================================================
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.txt');
end;

//=============================================================================
//  TMemo に表示しているテキストをファイルに保存
//  先頭にバイトテータ ( $08 ) を書き込む
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMemStream : TMemoryStream;
  LByte      : Byte;
  LText      : string;
begin
  LMemStream := TMemoryStream.Create;
  try
    LText := Memo1.Lines.Text;

    LByte := $08;
    LMemStream.Write(LByte, 1);

    LMemStream.Write(LText[1], Length(LText) * SizeOf(Char));
    LMemStream.SaveToFile(FFilePath);
  finally
    FreeAndNil(LMemStream);
  end;
end;

//=============================================================================
//  ファイルのデータをメモリストリームに読み込む
//  先頭の余分なデータを削除して,元のテキストを取得
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMemStream : TMemoryStream;
  LByte      : Byte;
  LText      : string;
begin
  Memo1.Lines.Clear;

  LMemStream := TMemoryStream.Create;
  try
    LMemStream.LoadFromFile(FFilePath);

    LMemStream.Read(LByte, 1);

    SetLength(LText, (LMemStream.Size - 1) div 2);
    LMemStream.Read(LText[1], LMemStream.Size - 1);
    Memo1.Lines.Text := LText;
  finally
    FreeAndNil(LMemStream);
  end;
  Memo1.Lines.Text := LText;
end;


[備考]
TBinaryWriter を使用したサンプルも最後のサンプルも,先頭にバイトデータを追加して保存しただけです.したがって,追加したバイトデータ以外は,間違いなく Unicode としての文字コードのバイトデータがディスク内に存在します.しかし,メモ帳等のテキストエディタで表示しても,正常な文章には見えません.文字化けします.





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


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

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



図7
実行結果

リスト5
ファイルのデータをバイナリ値 (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;
  LIndex    : 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 := '';
    for LIndex := 0 to LStream.Size - 1 do begin
      if ((LIndex mod 16) = 0) and (LIndex <> 0) then begin
        LStrList.Add(TrimRight(LLineText));
        LLineText := '';
      end else
      if (LIndex mod 8) = 0 then begin
        LLineText := LLineText + ' ';
      end;
      LLineText := LLineText + IntToHex(LStream.Bytes[LIndex], 2) + ' ';

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

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

end.




03_バイナリデータを 16 進数の文字列にして保存


テキスト (文字列) のバイナリデータは,文字のエレメントのコードポイントの値です.この値を,16 進数の文字列にして.上のサンプルと同じバイナリダンプの状態で保存します.



図8
実行時の画面
  • Button1 は保存ボタン
  • Button2 は保存したファイルの読み込み用ボタン

下図は,保存したテキストファイルをメモ帳で表示した結果です.この値は,TMemo のテキストの文字のエレメントのバイナリ値 (コードポイントの値) です.

「テキストをバイナリで保存したい」あるいは「テキストをバイナリに変換して保存したい」という質問もあります.これらの質問は,テキストを保存すればテキストのデータが,バイナリで保存すればバイナリのデータが表示される,という意味に解釈することもできます.そうであれば,下図の結果はまさにその通りてす.
このファイルを Button2 クリックで読み込むと,保存前の TMemo のテキストを表示します.



図9
保存したファイルをメモ帳で表示
  • 前項のバイナリダンプと同じ
  • これはテキスト形式のファイル

リスト6
バイナリデータを 16 進数の文字列にして保存
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('バイナリデータを 16 進数文字列にして保存');

  FFilePath := ExtractFilePath(Application.ExeName) + '000.txt';
end;

//=============================================================================
//  文字列のバイナリデータはコードポイントの値
//  それをバイナリダンプ形式の 16 進数の文字列にして保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStream   : TBytesStream;
  LStrList  : TStringList;
  LLineText : string;
  LIndex    : Integer;
begin
  LStream  := TBytesStream.Create;
  LStrList := TStringList.Create;
  try
    // テキストをストリームに格納
    Memo1.Lines.SaveToStream(LStream, TEncoding.Unicode);

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

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

    LStrList.SaveToFile(FFilePath);
  finally
    FreeAndNil(LStrList);
    FreeAndNil(LStream);
  end;
end;

//=============================================================================
//  保存した 16 進数の文字列を読み出して処理
//  16 進数の 4 桁の文字列を整数に変換して,それを Char で変換
//  
//  バイナリデータにデータに型の情報はない
//  保存する時に型情報も保存しないと元に戻せない
//  Unicode の文字 1 エレメントは 2 バイト (16 進数の 4 桁) だから型情報は不要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrList : TStringList;
  LLoadTxt : string;
  LBOMText : string;
  LText    : string;
  LBinText : string;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    // 保存した文字列を読み込む
    LStrList.LoadFromFile(FFilePath);

    // 不要な文字を削除
    // 文字単位で検索して処理した方が速いかも知れない
    LLoadTxt := LStrList.Text;
    LLoadTxt := StringReplace(LLoadTxt, ' ', '', [rfReplaceAll]);
    LLoadTxt := StringReplace(LLoadTxt, '-', '', [rfReplaceAll]);
    LLoadTxt := StringReplace(LLoadTxt, sLineBreak,'', [rfReplaceAll]);

    // BOM ( U+FEFF - ZERO WIDTH NO-BREAK SPACE ) 判定用文字列
    LBOMText := Copy(LLoadTxt, 1, 4);
    // BOM の文字列は除外
    LLoadTxt := copy(LLoadTxt, 5, Length(LLoadTxt));

    LText := '';
    for LIndex := 0 to Length(LLoadTxt) div 4 - 1 do begin
      LBinText := '$' + Copy(LLoadTxt, LIndex * 4 + 1, 4);

      // リトルエンディアンの時はバイトオーダを逆にする
      if LBOMText = 'FFFE' then begin
        LText := LText + Char(Swap(StrToInt(LBinText)));
      end else begin
        LText := LText + Char(StrToInt(LBinText));
      end;
    end;
    Memo1.Lines.Add(LText);
  finally
    FreeAndNil(LStrList);
  end;
end;

end.


上のサンプルと同じく,テキスト (文字列) の各文字のエレメントのコードポイントの値を,16 進数の文字列にして保存しますが,1 行だけの文字列にする例です.



図10
保存したファイルをメモ帳で表示
  • 1 行だけの文字列
  • これはテキスト形式のファイル
図11
メモ帳を折り返し表示した状態

リスト7
バイナリデータを 16 進数の 1 行だけの文字列にして保存
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('バイナリデータを 16 進数文字列にして保存');

  FFilePath := ExtractFilePath(Application.ExeName) + '000.txt';
end;

//=============================================================================
//  文字列のバイナリデータはコードポイントの値
//  それを 16 進数の文字列にして保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LText    : string;
  LBinText : string;
  LIndex   : Integer;
begin
  LStrList := TStringList.Create;
  try
    LText := Memo1.Lines.Text;

    LBinText := '';
    for LIndex := 1 to Length(LText) do begin
      LBinText := LBinText + IntToHex(Ord(LText[LIndex]), 4);
    end;
    LStrList.Add(LBinText);
    // Shift_JIS で保存
    LStrList.SaveToFile(FFilePath);
  finally
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  保存した 16 進数の文字列を読み出して処理
//  16 進数の 4 桁の文字列を整数に変換して,それを Char で変換
//  Unicode の文字 1 エレメントは 2 バイト (16 進数の 4 桁)
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrList : TStringList;
  LText    : string;
  LBinText : string;
  LIndex   : Integer;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    LStrList.LoadFromFile(FFilePath);

    LText := '';
    for LIndex := 0 to Length(LStrList.Text) div 4 - 1 do begin
      LBinText := '$' + Copy(LStrList.Text, LIndex * 4 + 1, 4);
      LText := LText + Char(StrToInt(LBinText));
    end;
    Memo1.Lines.Add(LText);
  finally
    FreeAndNil(LStrList);
  end;
end;

end.


上と同じく 1 行だけの文字列にする例ですが,Delphi に実装されている BinToHex, HexToBin 手続きを使用する例です.
テストに使用した文字列の最初の文字のエレメントのコードポイントは 4F60 です.上のサンプルでは,この 16 進数をそのまま文字列にして保存しています.このサンプルでは,システムのエンディアンにしたがって,バイトオーダが逆順の 604F になっています.



図12
保存したファイルをメモ帳で表示
  • 1 行だけの文字列
  • これはテキスト形式のファイル

リスト8
バイナリデータを 16 進数の 1 行だけの文字列にして保存.BinToHex, HexToBin を使用する例
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('バイナリデータを 16 進数文字列にして保存');

  FFilePath := ExtractFilePath(Application.ExeName) + '000.txt';
end;

//=============================================================================
//  文字列のバイナリデータはコードポイントの値
//  それを 16 進数の文字列にして保存
//  BinToHex 関数を使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LText    : string;
  LBinText : string;
begin
  LStrList := TStringList.Create;
  try
    LText := Memo1.Lines.Text;

    // 出力の 16 進数の桁数を設定
    SetLength(LBinText, Length(Memo1.Lines.Text) * SizeOf(Char) * 2);
    BinToHex(@Memo1.Lines.Text[1], PWideChar(LBinText), Length(LBinText));

    LStrList.Add(LBinText);
    LStrList.SaveToFile(FFilePath);
  finally
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  保存した 16 進数の文字列を読み出して処理
//  HexToBin 関数を使用する例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LStrList : TStringList;
  LLoadTxt : string;
  LText    : string;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    LStrList.LoadFromFile(FFilePath);
    // 16 進数の文字に小文字があっても処理可能にする
    LLoadTxt := UpperCase(LStrList.Text);

    // 出力の文字のエレメント数を設定
    SetLength(LText, Length(LLoadTxt) div (SizeOf(Char) * 2));
    HexToBin(PWideChar(LLoadTxt), @LText[1], Length(LLoadTxt));

    Memo1.Lines.Add(LText);
  finally
    FreeAndNil(LStrList);
  end;
end;

end.




04_暗号化と復号化 ( 簡易版 ) - TBytesStream


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



このサンプルで暗号化したファイルをメモ帳で表示すると下図のようになります.



図13
暗号化したファイルをメモ帳で表示
  • これはバイナリ形式のファイル

リスト9
バイナリデータの 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.




05_暗号化と復号化 ( 簡易版 ) - TMemoryStream と TBytes


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



リスト10
バイナリデータの 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.




06_Base64 による符号化と復号化


Base94 は,データを,ASCII コードの 64 文字だけに変換するエンコード方式です.
Base64 エンコードで符号化したテキストは,1 文字が 1 バイトの ASCII コードだけの文字や記号を組み合せたデータになります.メモ帳等のテキストエディタ等で表示はできますが,元の文字列としては読めません.






07_UTF-7 にエンコードして保存


テキストを UTF-7 にエンコードすると,半角の英数字は 1 バイトの文字に変換され,漢字などの日本語は Base64 による符号化と同様に,ASCII コードだけの文字や記号を組み合せたデータになります.メモ帳等のテキストエディタで表示した場合,日本語の文字は読めません.
ディスクからファイルを読み出す時は,エンコードに UTF-7 を指定するだけです.



図14
実行時の画面
図15
保存したファイルをメモ帳で表示
  • UTF-7 でエンコードされている
  • メモ帳は右端で折り返し表示

リスト11
UTF-7 にエンコードして保存.読み出した時は UTF-7 でデコード
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('テキストを UTF-7 でエンコードして保存');

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

//=============================================================================
//  テキストを UTF-7 でエンコードして保存
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.SaveToFile(FFilePath, TEncoding.UTF7);
end;

//=============================================================================
//  UTF-7 でエンコードして保存したテキストファイルを読み出す
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Lines.Clear;

  Memo1.Lines.LoadFromFile(FFilePath, TEncoding.UTF7);
end;

end.




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


ファイルがテキストファイルであるか,そうではなないファイルであるかを判定するのは結構面倒です.ファイルに保存されているバイナリデータが,メモ帳などで表示した場合,テキストとして表示されるデータかを調べる必要があります.
本サンプルは,ファイルの MIME タイプを調べる方法です.ファイルのバイナリデータがどのような形式のデータであるかを調べます.テキスト形式とかバイナリ形式という定義はありませんから,調べようがありません.調べるのは,ビットマップ形式の画像ファイルであるとかいった,具体的な形式です.本ページの最初のサンプルの場合は,形式がないファイルとなります.

[MIME 判定] ボタンのコードは,UrlMon.pas に実装されている FindMimeFromData 関数を使用しています.ファイルの MIME タイプに text が含まれている場合は全てテキストファイルと判定しています.必要に応じて変更してください.

[判定] ボタンは,昔の Delphi のコミュニティの場に掲載されていたコードです.参考として掲載しておきます.このコードは Shift_JIS 用です.バイナリデータをバイト単位で検索して調べます.



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


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



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

リスト12
テキスト形式かバイナリ形式かの判定
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.




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


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

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

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



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

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




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


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



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



リスト13
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.




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


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



図17
設計時の画面

リスト14
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.




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


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



リスト15
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.