NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その9 - 共有ライブラリコンパイル編)

ここまでをおさらいします。

最初に、Statusクラス(STATUS値のラッパークラス)を作成しました。

次に、Lmbcsクラス(LMBCS文字列のラッパークラス)をNLS版で作成しました。

最後に、Databaseクラス(NSFファイルのラッパークラス)を、必要最低限の実装で作成しました。

これを、ntlxライブラリとしてまとめていきます。

Qtでは、qmakeというツールを使って、補足コードを生成したり、プラットフォームに応じたコンパイラオプション、リンカオプションを展開し、そのあとで、各プラットフォームのコンパイラやリンカを使って、ソースコードをバイナリ形式にしていきます。

そのqmakeに渡す指示書のようなものが、proファイル(プロジェクトファイル)です。

QT       -= gui

TARGET = ntlx
TEMPLATE = lib

DEFINES += NTLX_LIBRARY

SOURCES += lmbcs.cpp \
    database.cpp

HEADERS += lmbcs.h\
        ntlx_global.h \
    status.h \
    database.h

win32 {
    DEFINES += W32 NT
}
else:macx {
#   DEFINES += MAC
# Mac環境でDWORDが32bitではなく、64bitになってしまうため、LONGIS64BITを識別子に加える。
    DEFINES += MAC LONGIS64BIT
}
else:unix {
    DEFINES += UNIX LINUX W32
    QMAKE_CXXFLAGS += -std=c++0x
    target.path = /usr/lib
    INSTALLS += target
}

LIBS += -lnotes

DISTFILES += \
    .gitignore

gitなどを使ってWindowsMacOSXLinuxに1つのコードを配布することを前提に、絶対パスを必要とするパラメータはここに記述しないこととしています。

そのため、リンク先となるNotesの共有ライブラリは、ファイル名のみ共通して使えるので、「LIB += -lnotes」として記述しています(拡張子は省略できます)が、場所(パス)については、Qt Creatorのプロジェクト管理機能で補完しています。

例えば、MacOSXでは、ビルドステップにおいて以下のような設定をします。

"INCLUDEPATH+=/Users/Shared/notesapi/include" "LIBS+=-L'/Applications/IBM Notes.app/Contents/MacOS'"

【追記】2017.3.24 記事をアップした時の追加の引数が間違えておりました。訂正してお詫びします。

【追記】2017.4.6 Mac環境でDWORDが64bit幅のunsigned longが使われてしまうことを発見しました。そのため、Qtプロジェクトファイルで、DEFINESに識別子LONGIS64BITを加えるようにします。

このうち、INCLUDEPATH、LIBSをqmake「追加の引数」で追記されているものです。

これができたらビルドをしていきましょう。

NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その8 - データベース・基本クラス編)

前回紹介したデータベース関連のAPIを踏まえて、Databaseクラスを定義していきます。 方針としては、Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミングを踏襲して、コピー不可のクラスとします。

<database.h>

#ifndef NTLX_DATABASE_H
#define NTLX_DATABASE_H

#include "ntlx_global.h"
#include <QString>
#include "status.h"

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <nsfdb.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx {

class Lmbcs;

/**
 * @brief データベースクラス
 */
class NTLXSHARED_EXPORT Database
{
public:
  /**
   * @brief デフォルトコンストラクタ
   */
  explicit Database();

  /**
   * @brief パスによるコンストラクタ
   * @param path パス
   * @param server サーバ名(省略時はローカル)
   * @param port ポート名(省略時はデフォルトポート)
   */
  explicit Database(const QString& path
                    , const QString& server = QString()
                    , const QString& port = QString()
                    , Status* status = nullptr
      );

  /**
   * @brief デストラクタ
   */
  virtual ~Database();

  /**
   * @brief DBHANDLEキャスト演算子
   */
  operator DBHANDLE() const { return handle_; }

  /**
   * @brief データベースのタイトルを取得する
   * @param status 関数の実行結果を取得したい場合はSTATUS変数へのポインタ
   * @return データベースタイトル
   */
  QString getTitle(STATUS* status = nullptr) const;

  /**
   * @brief データベースカテゴリ名を取得する
   * @param status 関数の実行結果を取得したい場合はSTATUS変数へのポインタ
   * @return データベースカテゴリ名
   */
  QString getCategories(STATUS* status = nullptr) const;

  /**
   * @brief データベースクラス(引き継ぎ元テンプレート名)を取得する
   * @param status 関数の実行結果を取得したい場合はSTATUS変数へのポインタ
   * @return データベースクラス(引き継ぎ元テンプレート名)
   */
  QString getClass(STATUS* status = nullptr) const;

  /**
   * @brief データベース設計クラス(自身のテンプレート名)を取得する
   * @param status 関数の実行結果を取得したい場合はSTATUS変数へのポインタ
   * @return データベース設計クラス(自身のテンプレート名)
   */
  QString getDesignClass(STATUS* status = nullptr) const;

  /**
   * @brief データベースを開く
   * @return 結果ステータス
   */
  Status open(
      const QString& path
      , const QString& server
      , const QString& port
      );

  /**
   * @brief データベースを閉じる
   * @return 結果ステータス
   */
  Status close();

  /**
   * @brief パス、サーバ名、ポート名からネットパスを構築する
   * @param path パス
   * @param server サーバ名
   * @param port ポート名
   * @param status 結果ステータス
   * @return ネットパス
   */
  static Lmbcs constructNetPath(
      const QString& path
      , const QString& server
      , const QString& port
      , STATUS* status = nullptr
      );

protected:
  /**
   * @brief データベースハンドルを返す
   * @return データベースハンドル
   */
  DBHANDLE handle() const { return handle_; }

  /**
   * @brief ハンドルを設定する
   * @param handle データベースハンドル
   */
  void setHandle(DBHANDLE handle) { handle_ = handle; }

  /**
   * @brief データベース情報を取得する
   * @param what データベース情報の種類(INFOPARSE_XXX)
   * @param status 結果ステータス
   * @return データベース情報
   */
  QString getInfo(WORD what, STATUS* status = nullptr) const;

private:
  DBHANDLE handle_;

  Database(const Database&);
  Database& operator=(const Database&);
};

} // namespace ntlx

#endif // NTLX_DATABASE_H



<database.cpp>

#include "database.h"
#include "lmbcs.h"

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <osfile.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx {

Database::Database()
  : handle_(NULLHANDLE)
{
}

Database::Database(const QString &path
                   , const QString &server
                   , const QString &port
                   , Status* status
                   )
  : handle_(NULLHANDLE)
{
  Status result = open(path, server, port);
  if (status != nullptr) *status = result;
}

Database::~Database()
{
  close();
}

QString Database::getTitle(STATUS* status) const
{
  return getInfo(INFOPARSE_TITLE, status);
}

QString Database::getCategories(STATUS* status) const
{
  return getInfo(INFOPARSE_CATEGORIES, status);
}

QString Database::getClass(STATUS* status) const
{
  return getInfo(INFOPARSE_CLASS, status);
}

QString Database::getDesignClass(STATUS* status) const
{
  return getInfo(INFOPARSE_DESIGN_CLASS, status);
}

Status Database::open(
    const QString& path
    , const QString& server
    , const QString& port
    )
{
  close();

  Lmbcs netPath = constructNetPath(path, server, port);
  Status result = NSFDbOpen(netPath.constData(), &handle_);
  if (result.failure()) handle_ = NULLHANDLE;
  return result;
}

Status Database::close()
{
  if (handle_ != NULLHANDLE)
  {
    Status result = NSFDbClose(handle_);
    handle_ = NULLHANDLE;
    return result;
  }
  return NOERROR;
}

Lmbcs Database::constructNetPath(const QString &path
                                 , const QString &server
                                 , const QString &port
                                 , STATUS* status
                                 )
{
  Lmbcs lmPath = Lmbcs::fromQString(path);
  Lmbcs lmServer = Lmbcs::fromQString(server);
  Lmbcs lmPort = Lmbcs::fromQString(port);
  char netPath[MAXPATH];

  Status result = OSPathNetConstruct(
        lmPort.isEmpty() ? 0 : lmPort.constData()
        , lmServer.constData()
        , lmPath.constData()
        , netPath
        );
  if (status != nullptr) *status = result;

  return result.success() ? Lmbcs(netPath) : Lmbcs();
}

/**
 * @note NSF_INFOは以下のような構成を取る
 * @note タイトル+改行+カテゴリ+改行+#1+設計クラス+改行+#2+クラス+終端0
 * @note 全体で128バイト以内になる
 */
QString Database::getInfo(WORD what, STATUS* status) const
{
  Q_ASSERT(handle());

  char infoData[NSF_INFO_SIZE];
  char nameData[NSF_INFO_SIZE];

  Status result = NSFDbInfoGet(handle(), infoData);
  if (status != nullptr)
    *status = result;

  if (result.failure())
    return QString();

  NSFDbInfoParse(infoData, what, nameData, NSF_INFO_SIZE - 1);
  nameData[NSF_INFO_SIZE - 1] = '\0';
  Lmbcs lmbcs(nameData);
  return lmbcs.toQString();
}

} // namespace ntlx

NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その7 - データベース・基本編)

Notesデータベースは、Note(文書)の集合体です。文書はアイテムの集合体で、アイテムの中でもリッチテキストアイテムはコンポジットデータの集合体です。Notesデータにアクセスするには、何はなくともデータベースにアクセスする必要があります。データベースにアクセスするには、データベースハンドル(DBHANDLE型)を取得する必要があります。

Notesデータベースのハンドルを取得する基本は、パスを使った方法です。Notesのフルパス(以下、ネットパス)は、Notesポート、サーバ名、ファイルパスの3つから構成されます。Notesポートは、TCP/IP以外に思いつかない現在では、ポートを意識することはほとんどないですが、AppleTalkNetWare(IPX/SPX)、モデムを使った通信など、ネットワークプロトコルがいくつもあった時代の名残と言えるでしょう。おそらく今は、Notesポートはデフォルト(指定なし)で済むはずです。サーバ名はDominoサーバ名を指定するか、指定なし(=ローカル)とします。ファイルパスは、データディレクトリからの相対パスを指定することになります。ローカルであれば、フルパスも指定できます。これら3つの要素を組み合わせて、一つのテキストとして表されるのがネットパスです。

ネットパス自体は、文字列を特定の区切り文字でつなげればいいのですが、仕様が変更されることもありますし、移植性の観点からも、API関数を通じて作成した方が無難です。

ネットパスを作るには、OSPathNetConstructという関数を使います。逆に、できあがっているネットパスを3要素に分解するのがOSPathNetParse関数です。

STATUS LNPUBLIC OSPathNetConstruct (const char far *PortName,
                                    const char far *ServerName,
                                    const char far *FileName,
                                    char far *retPathName);

STATUS LNPUBLIC OSPathNetParse (const char far *PathName,
                                char far *retPortName,
                                char far *retServerName,
                                char far *retFileName);

Notesデータベースを、ネットパスを使ってオープンし、ハンドルを得るのがNSFDbOpen関数です。データベースへのアクセスを終了する場合はNSFDbClose関数を使います。

STATUS LNPUBLIC NSFDbOpen (const char far *PathName, DBHANDLE far *rethDB);

STATUS LNPUBLIC NSFDbClose (DBHANDLE hDB);

最後にNotesデータベースの基本情報についてです。Notesデータベースの情報バッファは1つの文字列、128バイトで構成されます。データベースタイトルはこの中の1つで、ほかにカテゴリ、クラス(引き継ぎ元テンプレート名)、設計クラス(マスターテンプレート名)も、この128バイト中に共存します。4つとも設定すると、平均32バイト、日本語の全角LMBCSで設定すると、1つあたり全角10文字程度しか設定できません。何とも窮屈なデータです。

タイトルを含む4つの基本情報を取得するには、NSFDbInfoGet関数を使います。長さはNSF_INFO_SIZEとして定義されているので、バッファを用意して取得すればOKです。

取得したい基本情報から、目的のデータ(例えばタイトル)を取り出したい時はNSFDbInfoParse関数を使います。データの種類はINFOPARSE_XXXとして定義されています。バッファサイズはNSF_INFO_SIZEを用意すればいいでしょう。必ずしも全体の4分の一とは限らないですし。

基本情報を変更する場合は、先の2つに加えて、NSFDbInfoModifyとNSFDbInfoSetを使いますが、詳しくは折を見て説明したいと思います。

STATUS LNPUBLIC NSFDbInfoGet (DBHANDLE hDB, char far *retBuffer);

void LNPUBLIC NSFDbInfoParse(char far *Info, WORD What, char far *Buffer, WORD Length);

#define INFOPARSE_TITLE 0
#define INFOPARSE_CATEGORIES 1
#define INFOPARSE_CLASS 2
#define INFOPARSE_DESIGN_CLASS 3

void LNPUBLIC NSFDbInfoModify(char far *Info, WORD What, const char far *Buffer);

STATUS LNPUBLIC NSFDbInfoSet (DBHANDLE hDB, char far *Buffer);

ここまでの関数を踏まえて、Notesデータベースクラスを定義していきましょう。

NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その6 - LMBCS変換・応用編)

LMBCSを扱うにあたり、前回のコードでは、LMBCSの区切り位置を計測できずにいました。

APIを詳しく見ていくと、LMBCS文字列を含む言語サービスが用意されています。ヘッダファイルnls.hで提供されるNational Language Services(NLS)には、多国語のキャラクタセットに対応した文字列処理関数が用意されていて、OSTranslate関数の代替となるNLS_Translate関数もあります。 LMBCSの文字区切り判定についても、文字数を返すNLS_string_chars、バイト数を返すNLS_string_bytesを組み合わせることで、LMBCSを正しい位置で区切ることが可能になり、WORD幅を超えるLMBCS文字列も、範囲ごとに変換してつなぎ合わせていけば処理が可能になります。

それでは、NLSを活用した新しいLmbcsクラスを紹介します。ヘッダファイルは変更がないので、ソースファイルのみ紹介します。

<lmbcs.cpp>

#include "lmbcs.h"

#include <QString>

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <osmisc.h>
#include <nls.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx {

const WORD UNICODE_BYTE = (sizeof(ushort) / sizeof(char));
const WORD LMBCS_DELTA = (1024 * 31);
const int UNICODE_DELTA = (1024 * 42 / UNICODE_BYTE);

Lmbcs::Lmbcs()
  : QByteArray()
{
}

Lmbcs::Lmbcs(const char *s, int len)
  : QByteArray(s, len)
{
}

Lmbcs::Lmbcs(STATUS status)
  : QByteArray()
{
  char buffer[MAXWORD] = "";
  WORD lmbcsLen = OSLoadString(0, Status(status).error(), buffer, MAXWORD);
  *this = Lmbcs(buffer, lmbcsLen);
}

Lmbcs::Lmbcs(const Lmbcs& other)
  : QByteArray(other)
{
}

Lmbcs& Lmbcs::operator=(const Lmbcs& other)
{
  if (this == &other) return *this;
  QByteArray::operator=(other);
  return *this;
}

QString Lmbcs::toQString() const
{
  // 変換後の文字列をセットする変数
  QString result;
  char buffer[MAXWORD] = "";

  // キャラクタセットを設定する
  NLS_PINFO pLmbcsInfo = OSGetLMBCSCLS();
  NLS_PINFO pUnicodeInfo;
  NLS_load_charset(NLS_CS_UNICODE, &pUnicodeInfo);

  // 現在の文字列のポインタとサイズ(バイト数)を計算する
  BYTE* ptr = (BYTE*)constData();
  int restSize = size();

  while (restSize > 0)
  {
    // 想定する境界値を計算する
    WORD delta = restSize < (int)LMBCS_DELTA ? (WORD)restSize : LMBCS_DELTA;

    // 境界値から文字数を割り出す
    WORD chars = 0;
    NLS_string_chars(ptr, delta, &chars, pLmbcsInfo);

    // 文字数から実際のバイト数を割り出す
    WORD bytes = 0;
    NLS_string_bytes(ptr, chars, &bytes, pLmbcsInfo);

    // LMBCSからUNICODEに変換する
    WORD retLen = MAXWORD;
    NLS_STATUS status = NLS_translate(
          ptr, bytes
          , (BYTE*)buffer, &retLen
          , NLS_NONULLTERMINATE | NLS_SOURCEISLMBCS | NLS_TARGETISUNICODE
          , pUnicodeInfo
          );
    Q_ASSERT(status == NLS_SUCCESS);

    // UNICODE配列からQStringを作成してストリームに追加する
    result += QString::fromUtf16(reinterpret_cast<ushort*>(buffer)
                                 , (int)retLen / UNICODE_BYTE
                                 );

    // ポインタを進め、残りサイズを減らす
    ptr += (int)bytes;
    restSize -= bytes;
  }

  // ロードしたUNICODEキャラクタセットをアンロードする
  NLS_unload_charset(pUnicodeInfo);

  // ストリームを介して作成したQString文字列を返す
  return result;
}

Lmbcs Lmbcs::fromQString(const QString &qs)
{
  // 変換後の文字列をセットする変数
  QByteArray result;
  char buffer[MAXWORD] = "";

  // キャラクタセットを設定する
  NLS_PINFO pLmbcsInfo = OSGetLMBCSCLS();

  // 開始文字位置とサイズ(文字数)を計算する。
  int index = 0, restSize = qs.size();

  while (restSize > 0)
  {
    // 定義済みのUNICODE処理文字数と比較して変換する文字数を決める
    int chars = restSize < (int)UNICODE_DELTA ? restSize : (int)UNICODE_DELTA;

    // 開始位置と変換文字数から文字列を抜き出す
    QString input = qs.mid(index, chars);

    // 抜き出した文字列をUTF-16の配列に変換する
    const ushort* unicode = input.utf16();

    // 変換するバイト数を計算する
    WORD unicodeLen = (WORD)input.size() * UNICODE_BYTE;

    // UNICODEからLMBCSに変換する
    WORD retLen = MAXWORD;
    NLS_STATUS status = NLS_translate(
          reinterpret_cast<BYTE*>(const_cast<ushort*>(unicode)), unicodeLen
          , (BYTE*)buffer, &retLen
          , NLS_NONULLTERMINATE | NLS_SOURCEISUNICODE | NLS_TARGETISLMBCS
          , pLmbcsInfo
          );
    Q_ASSERT(status == NLS_SUCCESS);

    // LMBCS配列をQByteArrayに追加する
    result.append(buffer, (int)retLen);

    // 開始文字位置を進め、残りサイズを減らす
    index += chars;
    restSize -= chars;
  }

  // ストリームを介して作成したバイト列をLmbcsオブジェクトにして返す
  return Lmbcs(result.constData(), result.size());
}

} // namespace ntlx

LMBCS_DELTAとUNICODE_DELTAは、変換元文字列の単位を表します。 LMBCSをUNICODEに変換する場合は、半角英数字が最大2倍になるので、WORD幅の半分、より気持ち少なめをLMBCSの変換バイト単位にしています。 UNICODEをLMBCSに変換する場合は、全角文字や半角カナが最大1.5倍になるので、WORD幅の約2/3をUNICODEの変換バイト単位にしています。

次に静的メソッドfromQStringを見ていきます。

NLSの関数を使う場合、目的のキャラクタセット(CS)に応じた構造体NLS_INFOへのポインタを用意しておきます。LMBCSとネイティブ文字列(日本語ならShift-JISか?)については、OSGetLMBCSCLSとOSGetNativeCLSで取得できます。それ以外のCSを使う場合については、後述します。

与えられたQString文字列を、UNICODE_DELTAごとに区切りながら、LMBCSに変換していきます。NLSの文字列変換の使い方は、おおよそOSTranslate関数と同じです。変換元、変換先の指定は、5番目の引数で指定します。NLS_SOURCEISUNICODE | NLS_TARGETISLMBCSという組み合わせが、UNICODEからLMBCSへの変換を示します。NLS_NONULLTERMINATEは、変換結果にNULL文字を付加しなくてもいいことを指示します。6番目の引数には、変換後のCSを表すNLS_INFOを指定します。戻り値はNLS特有のNLS_STATUS型のステータス値で、NLS_SUCCESSであれば成功です。

最終的に、つなぎ合わせたLMBCSをLmbcsオブジェクトにして返します。

最後にtoQStringメソッドです。

このメソッドは、変換後のCSがUNICODEになるので、UNICODE用のNLS_INFOを用意します。関数NLS_load_charsetでは、任意のCSをロードして、そのNLS_INFOへのポインタを取得できます。使い終わったらNLS_unload_charsetでアンロードしておきます。

LMBCS文字列も、fromQString同様に変換バイト単位に区切りながら変換、つなぎを繰り返します。その際の区切り位置は、前述の通りNLS_string_charsとNLS_string_bytesを組み合わせて算出します。まず、区切りたいバイト位置まででNLS_string_charsを使って文字数を計算します。区切り位置に次の文字のプレフィックス¥x10が引っかかっていればこの文字は計算に入りません。次に文字数からバイト数を求めるNLS_string_bytesを使ってバイト数を計算します。こうすることで、LMBCS文字列でも正しく区切って処理することが可能になります。

LmbcsクラスはQByteArrayの派生クラスなので、Lmbcsクラス、QStringクラスともに文字数としては、理論上int幅の2Gバイトまで使うことができます。対してOSTranslate関数、NLS_Translate関数ともに処理可能なバイト数はWORD幅の64kバイトです。私が見つけられていないだけかもしれませんが、処理可能なバイト数についてのAPIレベルでの改善が望まれます。(続く)

NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その5 - LMBCS変換・基本編)

それでは、LMBCSをラップしたクラス、Lmbcsを定義していきます。

<ntlx_global.h>

#ifndef NTLX_GLOBAL_H
#define NTLX_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(NTLX_LIBRARY)
#  define NTLXSHARED_EXPORT Q_DECL_EXPORT
#else
#  define NTLXSHARED_EXPORT Q_DECL_IMPORT
#endif

#endif // NTLX_GLOBAL_H
<lmbcs.h>

#ifndef NTLX_LMBCS_H
#define NTLX_LMBCS_H

#include "ntlx_global.h"
#include "status.h"

#include <QByteArray>

namespace ntlx {

const int MAX_UNICODE_LEN = (1024 * 42 / 2);

class NTLXSHARED_EXPORT Lmbcs
    : public QByteArray
{
public:
  /**
   * @brief コンストラクタ
   */
  Lmbcs();

  /**
   * @brief コピー元LMBCSの文字列ポインタと長さによるコンストラクタ
   * @param コピー元LMBCSのポインタ
   * @param len コピー元LMBCSの長さ、0終端している場合は-1
   */
  Lmbcs(const char* s, int len = -1);

  /**
   * @brief ステータス値からエラーメッセージのLMBCSを生成する
   * @param status ステータス値
   */
  Lmbcs(STATUS status);

  /**
   * @brief コピーコンストラクタ
   * @param other コピー元
   */
  Lmbcs(const Lmbcs& other);

  /**
   * @brief 代入演算子
   * @param other 代入元
   * @return 自身への参照
   */
  Lmbcs& operator=(const Lmbcs& other);

  /**
   * @brief QStringに変換する
   * @return LMBCSから変換されたQString文字列
   */
  QString toQString() const;

  /**
   * @brief QStringからLmbcsに変換する
   * @note 変換できる文字列の長さはMAX_UNICODE_LENに制限
   * @param qs 変換元のQString
   * @return QStringから変換されたLmbcsオブジェクト
   */
  static Lmbcs fromQString(const QString& qs);

};

} // namespace ntlx

#endif // NTLX_LMBCS_H
<lmbcs.cpp>

#include "lmbcs.h"

#include <QString>

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <osmisc.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx {

const WORD UNICODE_BYTE = (sizeof(ushort) / sizeof(char));

Lmbcs::Lmbcs()
  : QByteArray()
{
}

Lmbcs::Lmbcs(const char *s, int len)
  : QByteArray(s, len)
{
}

Lmbcs::Lmbcs(STATUS status)
  : QByteArray()
{
  char buffer[MAXWORD];
  WORD lmbcsLen = OSLoadString(0, Status(status).error(), buffer, MAXWORD);
  *this = Lmbcs(buffer, lmbcsLen);
}

Lmbcs::Lmbcs(const Lmbcs& other)
  : QByteArray(other)
{
}

Lmbcs& Lmbcs::operator=(const Lmbcs& other)
{
  if (this == &other) return *this;
  QByteArray::operator=(other);
  return *this;
}

QString Lmbcs::toQString() const
{
  char buffer[MAXWORD];
  int unicodeLen = OSTranslate(
        OS_TRANSLATE_LMBCS_TO_UNICODE
        , constData(), size() <= (int)MAXWORD ? (WORD)size() : MAXWORD
        , buffer, MAXWORD
        );
  return QString::fromUtf16(reinterpret_cast<ushort*>(buffer)
                            , unicodeLen / UNICODE_BYTE);
}

Lmbcs Lmbcs::fromQString(const QString &qs)
{
  char buffer[MAXWORD];
  QString input = qs.left(MAX_UNICODE_LEN);
  const ushort* unicode = input.utf16();
  WORD unicodeLen = (WORD)input.size() * UNICODE_BYTE;
  int lmbcsLen = OSTranslate(
        OS_TRANSLATE_UNICODE_TO_LMBCS
        , reinterpret_cast<char*>(const_cast<ushort*>(unicode))
        , unicodeLen
        , buffer, MAXWORD
        );
  return Lmbcs(buffer, lmbcsLen);
}

} // namespace ntlx

コンストラクタには、以下の4つを用意しました。

  • デフォルトコンストラクタ(空のLMBCS)
  • LMBCSポインタと長さによるコンストラクタ
  • ステータス値によるコンストラクタ
  • コピーコンストラクタ

3番目の「ステータス値によるコンストラクタ」は、API関数OSLoadStringを利用したコンストラクタです。OSLoadStringは、指定したモジュール(指定しなければNotes)からエラーコードに対応した文字列をロードします。

<osmisc.h>

WORD LNPUBLIC OSLoadString (HMODULE hModule
                            , STATUS StringCode
                            , char far *retBuffer
                            , WORD BufferLength);

hModuleハンドルは、文字列を読み込むリソースDLLで、見つからなければNotesが保有する文字列リソースを検索します。リソースDLLがなかったり、MacLinuxなどの非Windowsプラットフォームで利用する場合は0を指定します。

StringCodeがステータス値です。ここにはERR()マクロを使ってエラーコードのみにマスクする必要があります。先に定義したntlx::Status::errorメソッドが役に立ちます。

retBufferとBufferLengthは、読み込んだ文字列を書き込む領域とそのサイズを指定します。

戻り値は、実際に書き込んだ文字列の長さになります。

次に、OSTranslateを使ってどのようにLMBCSからQString、QStringからLMBCSに変換しているかを見ていきます。

Unicode(UTF-16)の1文字は、全角半角に関係なく、1文字2バイトです。一方LMBCS日本語は、半角英数字は1バイト、全角と半角カナ文字は3バイトです。UnicodeからLMBCSに変換する場合1/2〜3/2倍になります。そこで、64k÷3×2≒42kを入力Unicodeの最大値とし、文字数としては21,504文字とします。

逆に、LMBCSからUnicodeに変換する場合、2/3〜2倍になるという式は成り立ちますが、LMBCSの文字の区切りを手動で判別するのは大変な手間を要します。そこで今回は、入力LMBCSの最大値はWORD最大値まで使用するものとし、バッファあふれは無視します。

次回は、このLMBCS文字列を適切に区切り、WORD幅より大きいサイズの文字列が扱えないか考察します。(続く)

NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その4 - LMBCS概略編)

次はLMBCSです。

LMBCS(リンビックス、Lotus Multi-Byte Character Set)はNotes固有の文字コードセットで、STATUSの次にラッパークラスを用意したい概念です。 日本語以外の知識を持ち合わせていないのですが、日本語のみで考えればLMBCSの日本語はシフトJISがベースになっています。これは、Windows版に限らず、Max OS Xでも、Linux版でも同様だそうです。

シフトJISは、ざっくりと半角英数字、半角カナ文字、全角文字の3つに分類されます。半角文字は1バイト、全角文字は2バイトです。LMBCSは、半角英数字はそのままで1バイト、全角文字には文字コード¥x10を頭に付けて3バイト、半角カナ文字には文字コード¥x10を2つ頭に付けて3バイトの形式を取ります。

シフトJIS バイト数 LMBCS バイト数
ABCDE 5 ABCDE 5
あいうえお 10 ¥x10あ¥x10い¥x10う¥x10え¥x10お 15
アイウエオ 5 ¥x10¥x10ア¥x10¥x10イ¥x10¥x10ウ¥x10¥x10エ¥x10¥x10オ 15

ただし、これらをユーザがいちいち変換する必要はありません。Notes APIにOSTranslateという関数があります。Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミングでは、std::stringを用いてLMBCSを表現し、シフトJISとLMBCSを相互に変換する関数を紹介しています。

OSTranslateは次のような仕様になっています。

<osmisc.h>

WORD LNPUBLIC OSTranslate(WORD TranslateMode
                          , const char far *In, WORD InLength
                          , char far *Out, WORD OutLength);

引数TranslateModeは定数で、どの文字コードからどの文字コードに変換するかが決まった定数を割り当てます。

<osmisc.h>

/* Charsets used with OSTranslate */

#define OS_TRANSLATE_NATIVE_TO_LMBCS    0    /* Translate platform-specific to LMBCS */
#define OS_TRANSLATE_LMBCS_TO_NATIVE    1    /* Translate LMBCS to platform-specific */
#define OS_TRANSLATE_LOWER_TO_UPPER     3    /* current int'l case table */
#define OS_TRANSLATE_UPPER_TO_LOWER     4    /* current int'l case table */
#define OS_TRANSLATE_UNACCENT           5    /* int'l unaccenting table */

#ifdef DOS
#define OS_TRANSLATE_OSNATIVE_TO_LMBCS  7    /* Used in DOS (codepage) */
#define OS_TRANSLATE_LMBCS_TO_OSNATIVE  8    /* Used in DOS */
#elif defined (OS2)
#define OS_TRANSLATE_OSNATIVE_TO_LMBCS  OS_TRANSLATE_NATIVE_TO_LMBCS
#define OS_TRANSLATE_LMBCS_TO_OSNATIVE  OS_TRANSLATE_LMBCS_TO_NATIVE
#else
#define OS_TRANSLATE_OSNATIVE_TO_LMBCS  OS_TRANSLATE_NATIVE_TO_LMBCS
#define OS_TRANSLATE_LMBCS_TO_OSNATIVE  OS_TRANSLATE_LMBCS_TO_NATIVE
#endif

#if defined(DOS) || defined(OS2)
#define OS_TRANSLATE_LMBCS_TO_ASCII     13
#else
#define OS_TRANSLATE_LMBCS_TO_ASCII     11
#endif

#define OS_TRANSLATE_LMBCS_TO_UNICODE   20
#define OS_TRANSLATE_LMBCS_TO_UTF8      22
#define OS_TRANSLATE_UNICODE_TO_LMBCS   23
#define OS_TRANSLATE_UTF8_TO_LMBCS      24

「NATIVE」というのが、日本語環境ではシフトJISにあたります。

Inには変換元の文字列、InLengthにはその長さ(バイト単位)、Outには変換後の文字列を格納する領域、OutLengthにはその領域の長さ(バイト単位)を指定します。戻り値は、変換後の文字列の実際の長さ(バイト単位)になります。

注意したいのは、入出力ともにWORD=64kバイト分の文字列しか処理できないことです。これは、最新のAPIキットでも変わっていません。

最近では、OSレベルでUnicodeがサポートされ、Qtにおいても文字コードUnicodeが標準です。幸いなことに、OSTranslate関数はNATIVE以外にUNICODE、UTF8も変換対象になっています。

Qtにおいて文字はUnicode(UTF-16)表現のQCharクラス、文字列表現はQCharの配列、QStringクラスを使います。Unicode以外の文字コードを扱う場合は、QByteArrayが使われます。

そこで、ここではLMBCSをQByteArrayの派生クラスとして定義し、QStringと相互に変換できるようにします。(続く)

NotesとQtでWindows、Mac OS X、Ubuntuのデスクトップアプリ(その3 - STATUS編)

Notes C APIにおけるSTATUS型は、ほとんどの関数の戻り値となっています。関数の実行結果が成功したのか、失敗したのか、判断する材料となります。

「Notes/Domino APIプログラミング〜」では、std::exceptionを拡張した例外処理として実装しています。私も以前はこれを利用していましたが、Visual C++で例外の仕様が変更され、サンプルをコンパイルすると警告が出るようになりました。それを機に、C++における例外処理について調べてみると、処理の組み方によっては思わぬ処理フローを引き起こす場合があるようで、Notes APIを扱う場合は不向きではないかと考えるようになりました。そこで、STATUS値をラッピングするクラスは、単なるラッパークラスにすることにしました。以下はStatusクラスのヘッダーファイルです。

 

status.h

#ifndef NTLX_STATUS_H
#define NTLX_STATUS_H

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <global.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx {

/**
 * @brief The Status class
 */
class Status
{
public:

  /**
   * @brief コンストラクタ
   */
  Status()
  {}

  /**
   * @brief コンストラクタ
   * @param value ステータス値
   */
  Status(STATUS value)
    : value_(value)
  {}

  /**
   * @brief コピーコンストラクタ
   * @param other コピー元
   */
  Status(const Status& other)
    : value_(other.value_)
  {}

  /**
   * @brief 代入演算子
   * @param other 代入元
   * @return 自身への参照
   */
  Status& operator=(const Status& other)
  {
    if (this == &other) return *this;
    value_ = other.value_;
    return *this;
  }

  /**
   * @brief キャスト演算子
   */
  operator STATUS() const { return value_; }

  /**
   * @brief エラー値
   * @return 上位2ピットをマスクしたエラー値
   */
  STATUS error() const { return ERR(value_); }

  /**
   * @brief エラーがないと真を返す
   * @return エラーがなければ真
   */
  bool success() const { return error() == NOERROR; }

  /**
   * @brief エラーがあると真を返す
   * @return エラーがあれば真
   */
  bool failure() const { return !success(); }

  /**
   * @brief すでに表示されていれば真を返す
   * @return 表示済であれば真
   */
  bool hasDisplayed() const { return ((value_ & STS_DISPLAYED) != 0); }

  /**
   * @brief リモート(Dominoサーバ)起因のエラーであれば真を返す
   * @return リモート起因なら真
   */
  bool isRemote() const { return ((value_ & STS_REMOTE) != 0); }

private:
  STATUS value_;
};

}

#endif // NTLX_STATUS_H

 

マルチプラットフォームとして記述するので、Notes APIのヘッダーファイルをインクルードする場合、Windowsにおいては、構造体のアラインメントに配慮する必要があります。最初の方に書かれている#pragma pack(push,1)と#pragma pack(pop)はそのための記述で、これらでAPIヘッダーのインクルード行を挟みます。なお、MacLinuxでは、構造体のアラインメントを考慮する必要はないそうです。

また、WindowsにおいてはWIN32という識別子が定義されるので、これを#ifに使うこともできますが、実はLinuxもこの識別子が必要になります。なので、識別子NTの方が判別しやすいでしょう。(続く)