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