ホーム >プログラム >Delphi 6 ローテクTips

TStrings.AddOjectでUnicodeを扱う

ウムラウトのようなUnicode文字を含んだファイルをTStringsで管理したいと思いました。
それができればTListBoxやTStringGridなどのコンポーネントで利用できるので便利ですし。

始めに

UnicodeとAnsiStringの可逆変換関数を使ってTStringsでUnicodeを扱うことができるようになりました。
とはいえなんとなく釈然としない感じです。
もっと素直にUnicodeをそのままで扱えないものだろうか、と。
そこでTStrings.AddObjectの登場となるわけです。
TObjectであればなんでもかんでもリストに持ててしまうようです。
ならばUnicode(WideString)だっていけるのじゃなかろうかと。

TObject

ものはためしとAddObject(WideString型の文字列)なんてやってみましたらTObjectとWideStringは互換性がないとかなんとかいわれてしまいました。
ふむふむ。
AddObjectはTObjectでないと受け付けないわけですか。
そうですか。
ならば作りましょう。

type
  TMyWideString = class(TObject)
  public
    sWideString: WideString;
  end;


procedure TForm1.Button1Click(Sender: TObject);
var
  lmy_WObj: TMyWideString;
begin
  lmy_WObj := TMyWideString.Create;
  //テストなのでString→WideString変換関数を使っているが、実際は直接WideStringを与える
  lmy_WObj.sWideString := gfnsStrToWideEx('Queensr$00FFche');  //'Queensrÿche'
  ListBox1.Items.AddObject(lmy_WObj.sWideString, lmy_WObj);
  myDebug.gpcDebugAdd(TMyWideString(ListBox1.Items.Objects[0]).sWideString);
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to ListBox1.Items.Count -1 do begin
    ListBox1.Items.Objects[i].Free;
  end;
end;

表示させてみるとOKなようです。

基本的にはこれでOKとしてもこのままではやはり使いにくいしメモリーリークを起こしやすくなっています。
気をつけないといけないのは、リストの大きさが変わるような操作をするたびにオブジェクトの開放を行わないとメモリーリークが発生してしまうということです。
リストを開放する前にもオブジェクトを自分で開放しないといけません。
リストはオブジェクトの開放まで面倒をみてくれないのです。
ということで入出力を関数にまとめてみました。

入出力の関数と手続き

function gfnsWideStringGet(slList: TStrings; const iIndex: Integer): WideString;
//取得
begin
  if (slList.Objects[iIndex] <> nil) and (slList.Objects[iIndex] is TMyWideString) then begin
    Result := TMyWideString(slList.Objects[iIndex]).sWideString;
  end else begin
    Result := slList.Strings[iIndex];
  end;
end;

procedure gpcWideStringAdd(slList: TStrings; const sWStr: WideString);
//追加
var
  lmy_WObj: TMyWideString;
begin
  lmy_WObj := TMyWideString.Create;
  lmy_WObj.sWideString := sWStr;
  slList.AddObject(sWStr, lmy_WObj);
end;

procedure gpcWideStringSet(slList: TStrings; const iIndex: Integer; const sWStr: WideString);
//セット
var
  lmy_WObj: TMyWideString;
begin
  slList.Objects[iIndex].Free;
  lmy_WObj := TMyWideString.Create;
  lmy_WObj.sWideString   := sWStr;
  slList.Objects[iIndex] := lmy_WObj;
end;

procedure gpcWideStringInsert(slList: TStrings; const iIndex: Integer; const sWStr: WideString);
//挿入
var
  lmy_WObj: TMyWideString;
begin
  slList.Objects[iIndex].Free;
  lmy_WObj := TMyWideString.Create;
  lmy_WObj.sWideString := sWStr;
  slList.InsertObject(iIndex, sWStr, lmy_WObj);
end;

procedure gpcWideStringDelete(slList: TStrings; const iIndex: Integer);
//1行削除
begin
  slList.Objects[iIndex].Free;
  slList.Delete(iIndex);
end;

procedure gpcWideStringClear(slList: TStrings);
//クリア
begin
  gpcWideStringFree(slList);
  slList.Clear;
end;

procedure gpcWideStringFree(slList: TStrings);
//オブジェクト開放
var
  i: Integer;
begin
  for i := 0 to slList.Count -1 do begin
    slList.Objects[i].Free;
  end;
end;

この他にもまだ対応しないといけないメソッドがありそうですが、とりあえずはこれで。
あとフィルへの保存と読み込みが必要ならLoadFromFileとSaveToFileのUnicode対応実装もしないといけません。
思ったよりずっと大変。。。
というかこれなら可逆変換関数を使った方がずっと楽なんでないか?と。
メモリーリークの心配はないしファイルへの読み書きもオリジナルのでいけるし。
と、こうしてみると可逆変換関数を使ったアプローチはカッコは悪いけど意外といい線行ってたのかも、、と思ったりもして。

Unicodeのファイルへの読み書きが必要な方は上記サイトを参考にどうぞ。
私は心が折れました。

その後折れた心をギプスで固めてUnicodeのファイルへの読み書きを実装しました。

使い方

Unicode対応のドラッグアンドドロップを例として。
WindowsMediaPlayerを取り込んで貼り付け。

//ドラッグアンドドロップ
procedure TForm1.WMDropFiles(var Msg: TWMDROPFILES);
var
  i, li_Buff: Integer;
  ls_PWFile:  PWideChar;
  lsl_List:   TStringList;
begin
  lsl_List := TStringList.Create;
  try
    //ドロップされたファイルの数だけ処理を行う
    for i := 0 to DragQueryFileW(Msg.Drop, $FFFFFFFF, nil, 0) -1 do begin
      li_Buff   := DragQueryFileW(Msg.Drop, i, nil, 0);
      ls_PWFile := AllocMem((li_Buff +1) * 2);
      try
        DragQueryFileW(Msg.Drop, i, ls_PWFile, li_Buff +1);
        //リストへWideStringを追加
        gpcWideStringAdd(lsl_List, WideString(ls_PWFile));
      finally
        FreeMem(ls_PWFile);
      end;
    end;
    DragFinish(Msg.Drop);  //ドラッグアンドドロップ処理の終了

    //--------------------------------
    //リストのファイルをランダムに取り出して再生
    Randomize;
    //リストからWideStringで取り出し
    WindowsMediaPlayer1.URL := gfnsWideStringGet(lsl_List, Random(lsl_List.Count));
    WindowsMediaPlayer1.controls.play;
    //--------------------------------
  finally
    gpcWideStringFree(lsl_List);
    lsl_List.Free;
  end;
end;

メモリーリークに注意

※ TStringsのClearメソッドとDeleteメソッドですが、ヘルプを見るとなんとなくオブジェクトの開放もやってくれそうに思うのですが、それは誤解です。
やりません。
やってくれるものと思ってオブジェクトの開放をしないとメモリーリークします。
またTMemoとTRichEditのLinesプロパティはTStringsですがオブジェクトまわりの実装は行われていないとのことで、これもAddObjectとかやると結局メモリーリークします。

>注意 Lines は TStrings の下位オブジェクトとして実装されますが,
>リスト内の文字列にオブジェクトを関連付ける処理のサポートは実装しません。

Delphi 6のTMemoのLinesプロパティのヘルプにはこのように書かれています。
実際AddOjectをやってみるとMemo1.Lines.Objects[i].Free;としても開放できずにメモリーリークします。

TMemoやTRichEditでUnicodeを扱いたい場合はUnicode入力対応のEditとMemoをどうぞ。
とことん機能縮小版ですがUnicodeの入力・表示はできます。