某所で失敗したのは3日間ほどなのですが、これ意外と知られていないので流しておきます。
以前、会社にいたころに XML-DB を扱う自社製品があって、それにアクセスする I/F を作っていました。外部的には Java ライブラリを使ってアクセスするのですが、ちょっと事情があって内部的にアクセスするライブラリを C/C++ で開発することになったのです。
で、その XML-DB がですね、実は Delphi で作られていたのです!!!
いまだと Delphi という名はあまり知られていないものの 30 年近く前には、意外とメジャーな言語でした。ググると分かるのですが、Delphi のベースは Pascal です。もともと Pascal は教育言語として開発されたものなのですが、それを実用化したのが Delphi というわけです(当時は Borland 社が開発・販売していて、名前は違うのですが、ここでは Delphi に統一しておきます)。
今だと何故に Pascal 言語が? と思うかもしれませんが、まだ Java も広がっていなくて C# も出ていない時代です。Windows アプリケーションを組むとなると VC++ でがりがり組むか、Visual Basic 6.0 で GUI を組むかという時代です。
当時、クラスライブラリが乏しくて、C++ だと XML をパースするのに Xerces などの外部ライブラリを使う必要がありました。で、VB6.0 の場合は GUI は簡単に組めるのですが、この手のロジックあたりが弱くて、そういう場合は仕方がなく VC++ で組むことになります。
まあ、当時の VC++ は GUI の開発環境が貧弱で MFC のライブラリ以前だったり、その後に出てきた MFC も使いづらかったりしたのですが、そこで登場したのが Delphi だったのです。VCL と言う GUI ライブラリがあってですね、それが広く使われていたのです。
で、今で言えば科学計算を学ぶには Python が一番!!! というように、Windows アプリケーションを組むならば Delphi!!! という時代があったわけです。皆さん、わざわざ Pascal 言語を学んでいたのですね。すごい。
そういう経緯があって、ちょっと先進的な GUI だったり、ちょっと気の利いたライブラリだったりは Delphi で作られていることが多かった時代があるので、ちょっと優秀なプログラマが XML-DB を組むときに Delphi で組むのも不思議ではなかった時代があったのです。
で、悲劇はその後に起こります。
その後 Microsoft が VC++ を改良して、MFC というクラスライブラリを充実させていきました。先に書いた Xerces などのライブラリも C/C++ から使えるようになってきました。MFC のボリュームが VCL に近づき、会社の規模から見て VCL を凌駕していったわけです。
そうなると、開発者のボリュームとして、Delphi を使う人が減少してきました。VC++ の GUI 環境が整うにつれて、VB6.0 からも VC++ に流れて来たのです。最終的には C# や VB.NET に移っていくわけですが、その間に VC++ で書かれた Windows アプリケーションのコードがたくさんあります。
その時点で起こる現象として、Delphi で作られた XML-DB のライブラリを C/C++ で呼び出すことになります。つまり、遺産をうまく使っていこうという発想です。
そんな状況で Pascal を知らない C/C++ プログラマ(つまり私)が移植担当になって、調べていくとですね、あれ? 文字列の呼び出しが変? ということになります。
ご存じの通り、C/C++ の文字列の終端は NULL 文字(’\0’)になっています。
C 言語
char str[] = "Hello";この場合、str は ‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’ という文字列になります。
ってな具合に Copilot に書いてもらうくらい簡単なものです。
じゃあ、Delphi の場合は? というと、Delphi の文字列は長さ情報を持っているのです。つまり、文字列の先頭に長さ情報が入っているわけです。
Pascal
var str: string;
str := 'Hello';この場合、str は長さ 5 の情報と ‘H’, ‘e’, ‘l’, ‘l’, ‘o’ という文字列になります。
Pascal の場合には、NULL 終端ではなくて、長さ情報が先頭に入っているわけですね。
C 言語的に言えば
- str[0] が文字列の先頭
- str[-4] が文字列の長さ(4 バイト整数)
ってわけですよ。
えええ!!! 配列をマイナスにしていいのかい???
ってくらいです。C/C++ プログラマにとっては。
文字列よりも前に何かメモリが必要ってのは不思議でもありますが、ASN.1 の規約を知っていれば不思議じゃないもので(これもググってください)、規約として先頭に文字列の長さを入れることがあります。
Java や C# だと string クラスを使うのが普通ですが、C/C++ や Pascal だと文字列は配列なので、こういうことが起こるわけです。まあ、正確に言えば、配列に埋めているだけなので、ASCII なのか、Unicode なのか、UTF-8 なのか、UTF-16 なのかで終端も変わってくるんですけどね。特に Unicode の場合には wchar_t 型を使うので、2 バイトの NULL 終端になります。これも 1 バイト終端と間違えてバグる可能性大です。
C/C++ と Delphi の文字列の扱いや配列の扱いが異なるので、これを相互変換しなければなりません。さらに、この手の情報をファイルに落としたり、メモリで一括で扱うときには、NULL 終端の場合は 1 バイトなんですが、長さ情報は 4 バイト使っている !!! ってことになり、バイナリデータの扱いも変わってしまうのです。
Delphi でバイナリデータを作って C 言語で読む場合にはちょっと面倒なことになります。
配列とかすべて再定義しないといけないですからね、もう大変ですよ。
そんなわけで、当時はまだ Delphi の名残が残っていたので、まあなんとか文字列終端の違いに気が付いたものですが、さらに 30 年程経ってしまうとですね。
化石的な Delphi ライブラリが残っていて、これに遭遇するわけです。
Java でアクセスするとか、C# でアクセスするとかの問題ではありません。
なにこれ、変? なんなのこれ???
と3日間ほど悩んでも仕方がないところです。
と、まあ、このあたりは私の想像なので、実際のところはよくわかりませんが、そのあたりが垣間見れてしまったので、ちょっと面白かったです、って話です。自分のことだときついのですが、所詮、他人事ですから。ははははははは、はぁ、お疲れ様です。
