見出し画像

Notes C API探訪: 旅を続けるために

これまでこの「Notes C API探訪」シリーズでは、散発的にAPIのトピックを提供してきました。APIの構造を説明し、どんな使い方をすればいいのか、わかる限りの範囲でサンプルコードを作り、検証し、提供してきました。

Notes/DominoのAPIでプログラミングする場合、データベース(ファイル)、文書(ノート)、フィールド(アイテム)という階層を行き来するので、ハンドルの管理、エラーの検出などを適切に行う必要があります。また、ベースがC言語なので、ソースコードも冗長になりがちです。

さまざまことを検討した結果、場当たり的なソースコードではなく、長期的に使用できるようなC++ライブラリ体系の構築と、一貫したUIによるAPI動作の見える化を図るため、今後はAPI関数のC++ラッピングライブラリとQt 5ベースのUIによるコードで動作確認をしていきたいと思います。

動作環境

・OS: Windows 10
・コンパイラ: Visual Studio 2017
・Qt: 5.14.2
・C++17 (とは言えそんなに使いこなせない)
・Notes C API: Version9.0.1

構造

基本的には、Qt5によるUIプロジェクト(notesexplorer2)と、Notes C APIラッピングライブラリのプロジェクト(nxpp)は、次のような構造を取ります。

スクリーンショット 2021-08-10 22.09.15

いずれもqmakeによるプロジェクトで構成しています。notesexplorer2のプロジェクトファイルでは、以下のようにしてヘッダーファイルを関連付けています。

// notesexplorer2.pro
INCLUDEPATH += ../nxpp/include

nxppライブラリ

今回は、Notes C APIを初期化して、後続のメインプログラムを動かす、startSessionテンプレート関数を紹介します。

// nxpp/nxpp_sessiion.hpp

#ifndef NXPP_SESSION_HPP
#define NXPP_SESSION_HPP

#include <iostream>

#ifdef NT
#pragma pack(push, 1)
#endif

#include <global.h>

#ifdef NT
#pragma pack(pop)
#endif

namespace nxpp {

template <class Func>
int startSession(int argc, char *argv[], Func func) {
 STATUS status = NotesInitExtended(argc, argv);
 return (status == NOERROR)
 ? [&]() {
   int retCode = func(argc, argv);
   NotesTerm();
   return retCode;
 }()
 : [&]() {
   std::cerr << "Error code: " << ERR(status) << "\n" << std::endl;
   return -1;
 }();
}

} // namespace nxpp

#endif // NXPP_SESSION_HPP

APIの初期化(NotesInitExtended)に成功すれば、第3引数の関数を実行します。関数から処理が戻ってきたら、APIの終了処理をし(NotesTerm)、関数から帰ってきたリターンコードで処理を終了します。
APIの初期化に失敗すれば、標準エラーにエラーコードを出力して、-1を返して終了します。

NotesInitExtended、NotesTerm

#include <global.h>
STATUS LNPUBLIC NotesInitExtended (int argc, char far * far *argv);
void   LNPUBLIC NotesTerm (void);

argcとargvは、main関数の引数をそのまま渡します。
NotesInitExtendedはAPIの初期化、NotesTermはAPIの終了処理をします。スタンドアロンプログラム(WindowsのEXEファイルなど)では、APIを初期化をせずに利用すると、一部を除きほとんどの関数でエラーが発生して、プログラムが異常終了します。一方、Notes/Dominoの一部となって動作するアドイン(DLLや共有オブジェクト)では、すでに初期化が済んでいるのでNotesInitExtendedを呼び出す必要はありません。Ⅰプロセスで1回呼び出せばよい、と覚えておけばいいでしょう。

ちなみに、初期のAPIではNotesInit、NotesInitIniという関数がありましたが、いずれもOBSOLETE(廃止)扱いになっています。リファレンスによると「後方互換のためOS/2とWindowsのみサポート」とあります。OS/2ですか〜。とにもかくにもこれらを使わず、「NotesInitExtended」を使いましょうということです。
またNotes C APIには、スタンドアロンプログラム用にNotesMainという関数が用意されています。明示的に初期化、終了処理をしなくてもいいのですが、プラットフォームごとにオブジェクトファイルをリンクしなくてはいけません。マルチプラットフォーム開発ではリンク先を分けるのも面倒なので、ここではnxpp::startSessionテンプレート関数を使っていきます。

notesexplorer2アプリケーション

Qt5ベースのUIアプリケーションです。QtはマルチプラットフォームでC++ベースのGUIアプリケーションを作れるライブラリです。Notes/Dominoのようなマルチプラットフォームで提供されるアプリケーションとは相性がよく、Notes C APIとの組合せで利用を始めてもう10年くらいになります。

notesexplorer2は、explorer(探検家)の名の通り、Notesの構造を探検できるようなアプリとして、これから徐々に成長させていきます。

基本的な土台は次のようなものです。

スクリーンショット 2021-08-10 22.33.59

画面の下のペインには、処理内容を確認できるような、ログコンソールを用意します。画面左のペインには、処理の起点になるブックマークリストがあります。ラベル、サーバー、パスの3列からなっており、随時ブックマークを増減できるようにします。空きスペースには探索した情報などをリストアップできるようにしていこうと思っています。

では以下にアプリを構成するソースコードを掲載します。なお、UIそのものはコーディングではなく、Qtのデザイナーで作成しているので、先ほどの図を持ってソースコードは割愛します。

main.cpp

// main.cpp

#include "mainwindow.h"

#include <QApplication>
#include <nxpp/nxpp_session.hpp>

int main(int argc, char *argv[])
{
 return nxpp::startSession(argc, argv, [](int argc, char *argv[]) {
   QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
   QApplication a(argc, argv);
   MainWindow w;
   w.show();
   return a.exec();
 });
}

main.cpp関数では、nxpp::startSessionを通じてQtアプリケーション、Qtウィンドウを生成し、表示しています。表示が終わると、イベントループによって処理の待機状態に入ります。

bookmarkmodel.h

// bookmarkmodel.h

#ifndef BOOKMARKMODEL_H
#define BOOKMARKMODEL_H

#include <QStandardItemModel>

class BookmarkModel
   : public QStandardItemModel
{
 Q_OBJECT
public:
 explicit BookmarkModel(QObject *parent = nullptr);
};

#endif // BOOKMARKMODEL_H

QtではMVC構造によってデータ(モデル)と表示(ビュー)の機能を分離できるライブラリが用意されています。今回は、汎用性が高いQStandardItemModelクラスを継承して、ブックマークを管理するBookmarkModelを構築します。

bookmarkmodel.cpp

// bookmarkmodel.cpp

#include "bookmarkmodel.h"

BookmarkModel::BookmarkModel(QObject *parent)
 : QStandardItemModel(parent)
{
 setHorizontalHeaderLabels(
       QStringList()
       << tr("Label")
       << tr("Server")
       << tr("Path")
       );
}

現段階では、列の定義のみ行います。ラベル、サーバー、パスの3つをヘッダーラベルとして追加します。

mainwindow.h

// mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "bookmarkmodel.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
 Q_OBJECT

public:
 MainWindow(QWidget *parent = nullptr);
 ~MainWindow();
 void load();

private:
 Ui::MainWindow *ui_;
 BookmarkModel *pModel_;
};

#endif // MAINWINDOW_H

メインウィンドウでは、モデルとUIを子オブジェクトとして生成します。

mainwindow.cpp

// mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
 : QMainWindow(parent)
 , ui_(new Ui::MainWindow)
{
 ui_->setupUi(this);

 pModel_ = new BookmarkModel(this);
 ui_->bookmarksView->setModel(pModel_);
 load();

 connect(
       ui_->actionE_xit, &QAction::triggered,
       this, &MainWindow::close
       );
}

MainWindow::~MainWindow()
{
 delete ui_;
}

void MainWindow::load() {
 // ダミーデータ
 pModel_->appendRow(
       QList<QStandardItem*>()
       << new QStandardItem("Local")
       << new QStandardItem("")
       << new QStandardItem("")
       );
 pModel_->appendRow(
       QList<QStandardItem*>()
       << new QStandardItem("Unicorn2")
       << new QStandardItem("unicorn2/chiburu9")
       << new QStandardItem("")
       );
}

UIはもちろん、ブックマークモデルもコンストラクタで初期化します。モデルは、thisを渡すことで、ブックマークモデルのデストラクタを明記しなくても、MainWindowがモデルのメモリを解放してくれます。
loadメンバ関数がありますが、実際のデータはハードコードされています。将来的に、QSettingsクラスを通じて、レジストリ内に読み書きするようにしていきます。

まとめ

Notes C API探訪は新章に突入!といった感じです。私が長年取り組んできているQtも最近6がリリースされていますが、Qt5もまだまだ現役です。私自身、UI構築は少し離れていたので、再入門のつもりで勉強していきたいと思います。

この記事が気に入ったらサポートをしてみませんか?