お知らせ

電子会議

ライブラリ

パレット

Delphi FAQ検索

Delphi FAQ一覧

サンプル蔵





FDelphi FAQ
16番会議室「玉石混淆みんなで作るSample蔵」に寄せられたサンプル

"base64、エンコード&デコード"






 base64形式でのエンコードとデコードのサンプルです。


 先にこの会議室に出した nifty:FDELPHI/MES/16/1326 のコードでは
データの扱いに不統一があったので手直ししました。

・バイナリーデータ側は TStream型で、
・テキストデータ側は string型で、

それぞれ扱うようにしました。


 エンコードは、バイナリーデータを6ビットずつに切り分けて、
64種類の文字('A'〜'Z'、'a'〜'z'、'0'〜'9'、'+'、'/')に
割り当てます。6ビット毎の変換なので、元データ3バイトにつき
4バイトの出力になります。元データが3の倍数でない場合には
出力の不足分を‘=’で埋めます。

 デコードはエンコードの逆です...(手抜きの説明で失礼)


 下記のサンプルコードは、フォームに以下の5個のオブジェクト
を置き、

    RichEdit1: TRichEdit;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    Button1: TButton;
    Button2: TButton;

Button1.OnClick でファイルをエンコードしてRichEdit1に表示、
Button2.OnClick でデコードしてファイルに保存します。

 注意すべき点は6ビットずつ読み書きする際に、バイトデータ
の並び順の調整が必要になることです。
 詳しくは「エンディアン変換」をキーにしてWeb等を検索し
てみてください。


// 3バイト分のバイトオーダーを修正する(Kylixでは不要のはず)
function exchange_0_2(Src: DWORD): DWORD;
type
  TTemp = array[0..3]of BYTE;
begin
  Result := Src;
  TTemp(Result)[2] := TTemp(Src)[0];
  TTemp(Result)[0] := TTemp(Src)[2];
end;

// StreamからSizeバイトを読み込み、エンコードした文字列を返す
function base64encode(Stream: TStream; Size: Integer): string;
const
  Table: PChar =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var
  Buffer, Src: Pointer;
  Dst: PChar;
  I: Integer;

  procedure doNbyte(N: Integer);
  var
    Value: DWORD;
    I: Integer;
  begin
    if not(N in [1..3]) then Exit;
    Value := 0;
    // SrcからNバイトをValueに読み込む
    Move(PBYTE(Src)^, Value, N);
    // バイトオーダーを修正(Kylixでは不要のはず)
    Value := exchange_0_2(Value);
    // Srcの読み込み位置を進める
    Inc(Integer(Src), N);
    // 6ビットずつエンコード×4回
    for I := 3 downto 0 do
    begin
      if I > N then
        Dst[I] := '=' // 変換しない分は‘=’でパディングする
      else
        Dst[I] := Table[Value and $3f];
      Value := Value shr 6;
    end;
    // 次の書き込み位置にする
    Inc(Integer(Dst), 4);
  end;

begin
  Buffer := AllocMem(Size);
  try
    Size := Stream.Read(PBYTE(Buffer)^, Size);
    // エンコード後の文字列の長さにする
    SetLength(Result, (Size + 2) div 3 * 4);
    if Size > 0 then
    begin
      // 読み込み元のポインタ
      Src := Buffer;
      // 書き込み先のポインタ
      Dst := PChar(Result);
      // 3バイトずつ書き込む
      for I := 0 to Size div 3 - 1 do
        doNbyte(3);
      if (Size mod 3) > 0 then
        doNbyte(Size mod 3);
    end;
  finally
    FreeMem(Buffer);
  end;
end;

// TextをデコードしてStreamに書き込み、書き込んだバイト数を返す
function base64decode(Stream: TStream; Text: string): Integer;
var
  Src: PChar;
  I: Integer;

  // 各文字を6ビットデータに変換する
  function decode(code: BYTE): BYTE;
  begin
    case Char(code) of
    'A'..'Z': Result := code - BYTE('A');
    'a'..'z': Result := code - BYTE('a') + 26;
    '0'..'9': Result := code - BYTE('0') + 52;
    '+': Result := 62;
    '/': Result := 63;
    else Result := 0; // ‘=’の場合も‘0’を返す
    end;
  end;

  function doNbyte: Integer;
  var
    I, N: Integer;
    Value: DWORD;
  begin
    // パディング文字‘=’の有無を調べる
    N := 3; // デコード後のバイト数をNにセット
    for I := 2 to 3 do
    begin
      if Src[I] = '=' then
      begin
        N := I - 1;
        break;
      end;
    end;
    // 4文字をデコードする
    value := 0;
    for I := 0 to 3 do
    begin
      // 1文字を6ビットに変換×4回
      Value := Value shl 6;
      Inc(Value, decode(PBYTE(Src)^));
      Inc(Integer(Src));
    end;
    // バイトオーダーを修正(Kylixでは不要のはず)
    Value := exchange_0_2(Value);
    // デコードしたデータの書き込み
    Result := Stream.Write(Value, N);
  end;

begin
  Result := 0;
  Src := PChar(Text);
  // 4文字ずつデコード
  for I := 0 to (Length(Text) div 4) - 1 do
    // 書き込んだバイト数をResultにカウントする
    Inc(Result, doNbyte);
end;

procedure TForm1.SaveToFile(FileName: string);
var
  Stream: TFileStream;
  I: Integer;
begin
  Stream := TFileStream.Create(FileName, fmCreate);
  try
    // RichEdit1の各行をデコードしてStreamに書き込む
    for I := 0 to RichEdit1.Lines.Count - 1 do
      base64decode(Stream, RichEdit1.Lines[I]);
  finally
    Stream.Free;
  end;
end;

procedure TForm1.LoadFromFile(FileName: string);
const
  LineSize = 76; // 1行76文字まで
var
  Stream: TFileStream;
  Size: Integer;
  S: string;
begin
  RichEdit1.Clear;
  RichEdit1.Lines.BeginUpdate;
  try
    Stream := TFileStream.Create(FileName, fmOpenRead);
    try
      // エンコード後の文字数がLineSizeを超えないバイト数
      Size := LineSize div 4 * 3;
      // Sizeバイト毎にエンコードしてRichEdit1の行に追加
      while true do
      begin
        S := base64encode(Stream, Size);
        if Length(S) <= 0 then // エンコード終了
          break;
        RichEdit1.Lines.Add(S);
      end;
    finally
      Stream.Free;
    end;
  finally
    RichEdit1.Lines.EndUpdate;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  // base64でエンコードしてRichEdit1に表示する
  if OpenDialog1.Execute then
    LoadFromFile(OpenDialog1.FileName);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  // RichEdit1のテキストをデコードして保存する
  if SaveDialog1.Execute then
    SaveToFile(SaveDialog1.FileName);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  RichEdit1.Align := alClient;
end;


2001/04/24、河邦 正(GCC02240@nifty.com)
(http://homepage2.nifty.com/kht0000/、NIFTY外へ私作Componentの
公開用)
 


- FDELPHI  MES(16):玉石混淆みんなで作るSample蔵【見本蓄積】 01/04/27 -

Original document by 河邦 正         氏 ID:(GCC02240)


ここにあるドキュメントは NIFTY SERVEの Delphi Users' Forum の16番会議室「玉石混淆みんなで作るSample蔵」に投稿されたサンプルです。これらのサンプルはボーランド株式会社がサポートする公式のものではありません。また、必ずしも動作が検証されているものではありません。これらのサンプルを使用したことに起因するいかなる損害も投稿者、およびフォーラムスタッフはその責めを負いません。使用者のリスクの範疇でご使用下さい。

Copyright 1996-2002 Delphi Users' Forum