W64APIにまたしても!DNParseの落とし穴

いつぞやのNSFItemInfoNext関数に続き、またしてもW64で正常に動かないC APIを発見しました。今回は、DNParse関数です。まず、DNParse関数とはなんぞや?というところから。

DNParse関数の接頭辞、「DN」は「Distinguished Name」の略で、識別名を表します。Notesでいうところの、

CN=Taro Yamada/O=Acme/C=JP

というID名を指します。ユーザ名、サーバ名などがそれにあたります。実際に捌ける要素はこれだけにとどまらないようですが、ここでは割愛します。ここでのポイントは、「DN」を冠した関数は識別名を操作する関数ということです。

DNParseは、その識別名をパーツごとに分解することができます。例えば、前出の例を元に分解すると、

Common Name => Taro Yamada
Org Name => Acme
Country Name => JP

となります。ヘッダーでは、DNParse関数は次のように定義されています。

#include <dname.h>

STATUS LNPUBLIC DNParse(
    DWORD Flags,
    const char far *TemplateName,
    const char far *InName,
    DN_COMPONENTS far *Comp,
    WORD CompSize);

FlagsとTemplateNameは0を渡します。 InNameに分解したい識別名へのポインタを入力します。 続く「DN_COMPONENTS」という構造体に、分解された情報が入ってくるので、事前にDN_COMPONENTS変数を用意して、そのポインタをCompに、変数サイズをCompSizeに与えれば取得できます。 問題なく分解できれば、NOERRORを返します。

DN_COMPONENTS dn;
STATUS result = DNParse(0, 0, "CN=admin/O=acme", &dn, sizeof(dn));

ヘッダーでDN_COMPONENTSを見ると、Ver3はここまで、Ver4はここから、みたいなコメントがあり、拡張が繰り返されてきたことが伺えます。

#include <dname.h>

typedef struct {
  DWORD Flags;                    /* Parsing flags */
/* (中略) */
  WORD CLength;                   /* Country name length */
  char far *C;                    /* Country name pointer */
  WORD OLength;                   /* Organization name length */
  char far *O;                    /* Organization name pointer */
  WORD OULength[DN_OUNITS];       /* Org Unit name lengths */
  /*  OULength[0] is rightmost org unit */
  char far *OU[DN_OUNITS];        /* Org unit name pointers */
  /*  OU[0] is rightmost org unit */
  WORD CNLength;                  /* Common name length */
  char far *CN;                   /* Common name pointer */
  WORD DomainLength;              /* Domain name length */
  char far *Domain;               /* Domain name pointer */

  /* Original V3 structure ended here.  The following fields were added in V4 */

  WORD PRMDLength;                /* Private management domain name length */
  char far *PRMD;                 /* Private management domain name pointer */
/* (中略) */
} DN_COMPONENTS;

実際にDNParseをDomino Win32bitで動かすと、DN_COMPONENTSには以下のような情報が返ってきます。

f:id:takahide-kondoh:20180604101604p:plain

ハイフンを挟んで左が文字数、右が文字列へのポインタです。Country name、Org name、Common nameのそれぞれに文字数2、文字数4、文字数11(0x0B)と、文字列が格納されているポインタが格納され、Org Unit name0〜3にはデータがないのがわかります。

全く同じコードをWin64用にコンパイルして動かすと以下のようになります。

f:id:takahide-kondoh:20180604122823p:plain

「あれれ〜おかしいなあ〜。ポインタっぽい値が文字数変数に入り込んでいるぞ〜。」という感じになっています。

このズレは何なのか、いろいろ考えてみました。単純に、メモリ配置的に、ポインタが4バイトから8バイトになったというだけでは説明が付きませんでした。試行錯誤の結果、国名(C)、組織名(O)共通名(CN)までの変数であれば、次のようにパディングすることで解決しました。

#ifdef W64

typedef struct {
  DWORD Flags;                    /* Parsing flags */
/* (中略) */
  WORD CLength;                   /* Country name length */
  WORD c_padding; // パティング
  char far *C;                    /* Country name pointer */
  WORD OLength;                   /* Organization name length */
  WORD o_padding[3]; // パティング
  char far *O;                    /* Organization name pointer */
  WORD OULength[DN_OUNITS];       /* Org Unit name lengths */
  /*  OULength[0] is rightmost org unit */
  char far *OU[DN_OUNITS];        /* Org unit name pointers */
  /*  OU[0] is rightmost org unit */
  WORD CNLength;                  /* Common name length */
  WORD cn_padding[3]; // パティング
  char far *CN;                   /* Common name pointer */
  WORD DomainLength;              /* Domain name length */
  char far *Domain;               /* Domain name pointer */

  /* Original V3 structure ended here.  The following fields were added in V4 */

  WORD PRMDLength;                /* Private management domain name length */
  char far *PRMD;                 /* Private management domain name pointer */
/* (中略) */
} DN_COMPONENTS;

#else

/* (オリジナルのDN_COMPONENTS定義をここに) */

#endif

CLengthとCの間にWORD一つ分、OLengthとOの間にWORD3つ分、CNLengthとCNの間も同じく3つ分パディングすると、以下のように正しく変数に格納されたのが確認できました。

f:id:takahide-kondoh:20180604135457p:plain

Domain以下の変数に対しては試せていませんが、ズレているのであれば、逐次直していくしかないでしょう。

まとめ

Notes/Dominoは登場初期からマルチプラットフォーム指向で、C APIも早い段階から提供され、Notes/Dominoのバージョンとともに更新されてきました。ただ残念なことに、C APIに目を向ける人口が少ないせいか、時々このようにお粗末なところが放置され、それを指摘するような利用者サイドも皆無となっています。 また、こういう関数が放置されているのにWin64 Dominoサーバがほぼ問題なく動いているのは、Undocumentedな関数で実装されていることは想像に難くなく、Windows ToolのDependency Walkerにかければそれっぽい関数名が浮かび上がります。 C APIのドキュメントもきちんと整備し、Documentableになった関数からユーザに開放してくれることを切に願うばかりです。