TOP > カテゴリ > Delphi >

Delphi5でZeosLibを使用してMySQL(UTF8)に高速アクセスする

ネットで日本語の情報が皆無に等しかったので書いてみました。(Lazarusの記事は少しある。)これができればDelphi5(1999年製)でも2020年ぐらい迄、使えそうです@@;

目的

BDE(Borland Database Engine)を使用しないでデータベースを扱う。
・データベースの文字コードはDelphi5で対応していないUTF8を扱う。

目的を達成するには・・・

・オープンソースコンポーネントの「ZeosLib」を使用してネイティブで高速にデータベースを扱う。

開発環境

・WindowsXp(32bit)
・Delphi5(Professional)
・ZeosLib6.6.6
・libmysql.dll(ネイティブ用)

実行環境

・Windows7(64bit)
・MySQL5.5(32bit/ローカル/UTF8)
・libmysql.dll(ネイティブ用)

※ZeosLibのバージョンを下げればDelphi4なども動きます。
※Personal版は未確認ですが恐らく動くはずです。
※開発環境と実行環境が異なる場合はMySQLがある環境のファイアウォールをオフにします。

以下からインストールなどの作業となります。

1.ZeosLibをダウンロードする

公式サイト:http://zeoslib.sourceforge.net/
sourceforge:http://sourceforge.net/projects/zeoslib/

今回はDelphi5で使用する為に「ZEOSDBO-6.6.6-stable.zip」をダウンロードします。

http://sourceforge.net/projects/zeoslib/files/Zeos%20Database%20Objects/zeosdbo-6.6.6-stable/

2.libmysql.dllをダウンロードする

http://dev.mysql.com/downloads/connector/c/からZIP Archiveをダウンロードします。ダウンロードを押したらページ下部の「No thanks, just start my download.」をクリックしてください。

その次にbinまたはlibフォルダにある「libmySQL.dll」を取得して、名前を「libmysqld51.dll」に変更してください。変更したDLLはDelphiで作成する実行ファイル(EXE)と同一のフォルダ内かシステムフォルダ?に置いてください。

3.DelphiにZeosLibをインストールする

3.1 ツール→環境オプションでライブラリパスにパスを追加する。

※本当はもっとスマートに出来ると思います。10年ぐらいぶりにDelphiを触ったもので・・

3.2 Delphiから以下のファイルを開き「コンパイル」する。

・ZEOSDBO-6.6.6-stable\packages\delphi5\ZCore.dpk
・ZEOSDBO-6.6.6-stable\packages\delphi5\ZPlain.dpk
・ZEOSDBO-6.6.6-stable\packages\delphi5\ZParseSql.dpk
・ZEOSDBO-6.6.6-stable\packages\delphi5\ZDbc.dpk

3.3 次は以下のファイルを開き「インストール」する。

ZEOSDBO-6.6.6-stable\packages\delphi5\ZComponent.dpk

※Professional版だと「vcl50ado」辺りがエラーになると思いますので、それはコメントにして下さい。

インストールするとこんな感じになります。

4.DelphiでZeosLibを使用する。

TZConnection/TZReadOnlyQueryを設置する。

いきなりソース書きます@@;

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ZConnection, Db, ZAbstractRODataset, ZDataset;

type
  TForm1 = class(TForm)
    ZReadOnlyQuery1: TZReadOnlyQuery;
    ZConnection1: TZConnection;
    Button1: TButton;
    Memo1: TMemo;
    procedure ZReadOnlyQuery1AfterOpen(DataSet: TDataSet);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

// UTF8→SJIS変換
function Utf8NToSjis(const s: string): string;
var
  Len: integer;
  OutPwc: PWideChar;
  SIn: string;
begin
  Result := '';
  SIn := S + #0#0;
  Len := MultiByteToWideChar(CP_UTF8, 0, PChar(SIn), Length(SIn), nil, 0);
  if Len = 0 then
   raise Exception.Create('指定のUTF8文字列の変換は出来ません.');
  OutPwc := AllocMem(Len * 2);
  try
   MultiByteToWideChar(CP_UTF8, 0, PChar(SIn), Length(SIn), OutPwc, Len);
   WideCharToStrVar(OutPwc, Result);
  finally
   FreeMem(OutPwc);
  end;
end;

procedure TForm1.ZReadOnlyQuery1AfterOpen(DataSet: TDataSet);
var
 Field : TField;
 S : string;
begin
    Field := DataSet.FindField('old_text') ;
    if Field.IsNull then
    begin
       // データがないよ
       Exit;
    end;

    // Memoへ表示(utf8からsjisへ変換)
    memo1.text := Utf8NToSjis(Field.AsString);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
    // 自動コミットなし
    ZConnection1.AutoCommit :=false;

    // ホスト名(ローカルならlocalhost ※MySQLの設定による)
    ZConnection1.HostName :='192.168.1.x'; // 

    // DB名
    ZConnection1.Database :='データベース名';

    // ユーザー名
    ZConnection1.User :='ユーザー名';

    // パスワード
    ZConnection1.Password :='パスワード' ;

    // プロトコル
    ZConnection1.Protocol :='mysqld-5';

    // 読み込み専用
    ZConnection1.ReadOnly := true;

    // DB接続
    ZConnection1.connected :=true ; 

    // クエリー設定
    ZReadOnlyQuery1.Connection := ZConnection1;

    // 文字コードをUTF8に設定する(必須)
    ZReadOnlyQuery1.SQL.Text := 'SET CHARACTER SET utf8';
    ZReadOnlyQuery1.ExecSQL;
    ZReadOnlyQuery1.SQL.Text :='';

    // SQLの実行
    ZReadOnlyQuery1.SQL.Text := '';
    ZReadOnlyQuery1.SQL.add('SELECT old_text FROM text where old_id =50467911');
    ZReadOnlyQuery1.Open;
    
    // DB接続解除
    ZConnection1.Connected := false;
end;

end.

※Utf8NToSjisはDelphi掲示板から引用しました。
※日本語を含むSQLは文字コードをUTF8に変換してからデータベースに投げて下さい。

実行すると下図のようになります。
※wikipediaのローカルデータベースから織田信長のデータ抽出

5.実は... ZeosLib6.6.6は「いにしえ」のバージョンなのでメモリリークがあり修正が必要です^^;

このままの状態ですとデータベースに接続して解放をするだけで以下のようになります@@;

※メモリリークチェッカーは「MemCheck2.0」を使用しています。(フランスのMemCheck.pasでも確認済み)

メモリリークの箇所は「ZPlainMySqlDriver.pas」にあります。

// 呼び出し箇所
procedure TZMySQLD5PlainDriver.BuildArguments(Options: TStrings);
begin
  BuildServerArguments(Options);
end;

// メモリリーク箇所
implementation
uses SysUtils;

var
  ServerArgs: array of PChar;  // なんとここにポインタ配列が@@;
  ServerArgsLen: Integer;

procedure BuildServerArguments(Options: TStrings);
var
  TmpList: TStringList;
  i: Integer;
begin
  TmpList := TStringList.Create;
  try
    TmpList.Add(ParamStr(0));
    for i := 0 to Options.Count - 1 do
      if SameText(SERVER_ARGUMENTS_KEY_PREFIX,
                  Copy(Options.Names[i], 1,
                       Length(SERVER_ARGUMENTS_KEY_PREFIX))) then
{$IFDEF VER140BELOW}
        TmpList.Add(Options.Values[Options.Names[i]]);
{$ELSE}
        TmpList.Add(Options.ValueFromIndex[i]);
{$ENDIF}
    //Check if DataDir is specified, if not, then add it to the Arguments List
    If TmpList.Values['--datadir'] = '' then
       TmpList.Add('--datadir='+EMBEDDED_DEFAULT_DATA_DIR);
    ServerArgsLen := TmpList.Count;
    SetLength(ServerArgs, ServerArgsLen);
    for i := 0 to ServerArgsLen - 1 do
      ServerArgs[i] := StrNew(PChar(TmpList[i]));  // ここで領域を確保しているが解放していない
  finally
    TmpList.Free;
  end;
end;

以下は、修正後の状態です。


TZMySQLD5PlainDriver = class (TZMySQL5PlainDriver)
public
  destructor Destroy; override;   // ここにデストラクタを追加
  function GetProtocol: string; override;
  function GetDescription: string; override;
  procedure Initialize; override;
  function Init(var Handle: PZMySQLConnect): PZMySQLConnect; override;
  procedure BuildArguments(Options: TStrings); override;
end;

{ TZMySQLD5PlainDriver }
// デストラクタを追加
destructor TZMySQLD5PlainDriver.Destroy;
var
 i : Integer;
begin
  for i := 0 to ServerArgsLen - 1 do
     StrDispose(ServerArgs[i]);

  inherited;
end;  
  
procedure BuildServerArguments(Options: TStrings);
var
  TmpList: TStringList;
  i: Integer;
begin
  TmpList := TStringList.Create;
  try
    TmpList.Add(ParamStr(0));
    for i := 0 to Options.Count - 1 do
      if SameText(SERVER_ARGUMENTS_KEY_PREFIX,
                  Copy(Options.Names[i], 1,
                       Length(SERVER_ARGUMENTS_KEY_PREFIX))) then
{$IFDEF VER140BELOW}
        TmpList.Add(Options.Values[Options.Names[i]]);
{$ELSE}
        TmpList.Add(Options.ValueFromIndex[i]);
{$ENDIF}
    //Check if DataDir is specified, if not, then add it to the Arguments List
    If TmpList.Values['--datadir'] = '' then
       TmpList.Add('--datadir='+EMBEDDED_DEFAULT_DATA_DIR);

    // ここに解放処理を追加
    if (ServerArgsLen<>-1) then
       for i := 0 to ServerArgsLen - 1 do
         StrDispose(ServerArgs[i]);

    ServerArgsLen := TmpList.Count;
    SetLength(ServerArgs, ServerArgsLen);
    for i := 0 to ServerArgsLen - 1 do
      ServerArgs[i] := StrNew(PChar(TmpList[i])); 
  finally
    TmpList.Free;
  end;
end;

※慣れた方がやればもっとスマートに修正できると思います^^;
※ここで修正したのはMySQL5の場合のみです。その他のバージョンやデータベースを使う場合は適宜、バグがあるか確認してから修正して下さい。

6.補足 データベース(UTF8)にSQLを投げる前に...

データベースのテーブルがUTF8の場合のSQLですが、SQLの文字コードはutf8である必要があります。そこでsjisからutf8へ変換するサンプルをネットで探すと思うのですが、以下には誤りがありますので、ご参考までに修正部分を書いておきます。

// SJIS→UTF8変換
function SjisToUtf8N(const s: string): string;
var
  Len: integer;
  InStr: PWideChar;
  OutStr: PChar;
begin
  Result := '';
  Len := Length(s) * 2 + 2;
  InStr := AllocMem(Len);
  try
    StringToWideChar(s, InStr, Len);
    OutStr := AllocMem(Len);
    try
      // WideCharToMultiByte(CP_UTF8, 0, InStr, Length(InStr) * 2, OutStr, Len, nil, nil);
      // 上記のまま実行するとアプリが時々落ちます。なので下記に修正すると良いです^^
      WideCharToMultiByte(CP_UTF8, 0, InStr, -1, OutStr, Len, nil, nil);
      Result := OutStr;
    finally
      FreeMem(OutStr);
    end;
  finally
    FreeMem(InStr);
  end;
end;

7.補足 よく見たら...

公式のサイトにコンポーネントなどの簡単な説明がありました。
http://edn.embarcadero.com/article/33775





関連記事



公開日:2015年02月05日
記事NO:00194