はじめに
今年はAcrobatのプラグインを使った提案や開発の機会が多くありました。手っ取り早く開発を始めたかったので、その度にAcrobat SDKの中に入っているサンプルのプロジェクトをコピーして使うという方法で、なるべくVisual Studioのプロジェクト設定に触れずに済ませていました。たまに設定の変更が必要になることがあり、プロジェクトのプロパティを開いてみると、項目数の多さに圧倒されて、何を変更すればよいのか分からなくなることもしばしばでした。
- インクルードパスはどこで足されている?
-
ACRO_SDK_LEVELってどこで定義されているの? - どこで
.apiという出力指定しているの?
分からないまま触ると壊れるので触らない。結果、サンプルプロジェクトが秘伝のタレとして受け継がれていき、何をやっているのかは永遠に分からないままです。
今回、この状態から抜け出すために、Acrobat SDKをCMake化してプラグインプロジェクトから参照する構成を作ってみました。やってみると、ビルドが楽になっただけでなく、設定が簡潔になったおかげで、Acrobatプラグインがどのような仕組みでビルドされているのかの理解が進んだのが一番の収穫でした。
この記事の対象読者
- Acrobat SDKでプラグインを作ったことがある人
- CMakeは名前を知っている程度、もしくは使ったことはあるが雰囲気で書いている人
検証環境
| 項目 | バージョン |
|---|---|
| OS | Windows 11 |
| Acrobat SDK | Acrobat DC SDK Windows 2021 v3 |
| CMake | 3.16 以上 |
| コンパイラ | MSVC (Visual Studio 2022) |
ゴール
この記事で作る構成のゴールは、次の3つです。
- プラグイン側のリポジトリに置くのは、ソースコードとCMakeLists.txt 1枚だけにする(vcxprojは置かない)
- SDKのヘッダをプロジェクトごとにコピーして持ち回る作業をなくす
- ビルドはコマンド2つで完結し、特定のIDEに縛られない状態にする
最終的なCMakeLists.txtは40行弱に収まります。プラグイン本体の中身はステップ2で1行ずつ見ていきます。
CMakeとは
CMakeは、ビルドシステムそのものではなく、ビルドシステムを生成するツールです。CMakeLists.txt というテキストファイルに「何を、どのソースから、どんな設定でビルドするか」を書いておくと、そこから環境に合わせたビルド環境を作ってくれます。
- Windows + Visual Studio なら、
.slnと.vcxproj - NinjaやMakefileを使うなら、その設定ファイル
- macOSなら、Xcodeのプロジェクト
つまり、ビルド設定は1か所にまとめて書いておき、生成先は後から選べるということです。今回のようにWindowsとmacOSの両方でプラグインを作りたい場合に効果的です。
使い方は2段階に分かれます。
- configure:
CMakeLists.txtを読んで、環境に応じたビルド環境を生成する - build:生成されたビルド環境を使って、実際にビルドする
この記事ではAcrobat SDKに必要な設定だけを扱うので、CMake自体の入門には踏み込みません。基本的な書き方や文法は、公式ドキュメントと先人の方々がQiitaに残していただいている豊富な記事があるので、そちらを参照してください。
(公式ドキュメント)
今さらCMakeという感じもしますが、裏を返せば枯れているので情報が多いということでもあり、なんといってもC++プロジェクトのCI/CDでは必須の技術だと思います。
なぜCMakeなのか
vcxprojが読みにくい問題
Visual Studioのプロジェクトファイルが読みにくいのには理由があります。設定が構成(Debug/Release)× プラットフォーム(x64/Win32)× プロパティシートの3次元に散らばっていて、さらにその多くがIDEのGUIからしか見えないからです。ファイルをテキストで開くこともできますが、XMLの構造が複雑で、内容を理解するのは容易ではありません。UIからだと、
とか、
とか。ちょっとうんざりです。
CMakeにすると何が変わるか
- 設定が1枚のテキストファイルに集約される。
git diffが読める形になります - 依存関係が明示される。「このターゲットはAcrobat SDKに依存している」とCMakeLists.txtに簡潔に書くことになるので、何に依存しているか分からない状態がなくなります
- IDEを選ばない。Visual Studio Code(CMake Tools拡張)でも、Visual Studioでも、そのまま開いてコマンドラインからビルド・デバッグできます
- Visual Studioに同梱されている。「C++によるデスクトップ開発」ワークロードを入れていれば、CMakeは既に入っています。追加インストールは不要です
全体像:2つのリポジトリに分ける
今回の構成では、リポジトリを2つに分けます。SDK側を再利用可能なCMakeパッケージとして管理し、プラグイン側はそれを参照するだけにします。こうすることで、プラグイン側のビルド設定を簡潔に保つことができます。
SDK側を一度作っておけば、2つ目以降のプラグインは参照するだけで済みます。
ライセンスについての注意
Acrobat SDKのヘッダ類をそのままパブリックなリポジトリに置くことは、再配布につながるためライセンス上問題になります。必ず private リポジトリ、または社内のGitサーバで管理してください。この記事ではリポジトリURLはプレースホルダにしています。
ステップ1:Acrobat SDKをCMakeパッケージにする
ディレクトリを用意する
Acrobat SDKの展開先から、プラグイン作成に必要な PluginSupport\Headers を丸ごとコピーするだけです。
Acrobat_DC_SDK_Windows_2021_v3\Adobe\Acrobat DC SDK\Version 1\PluginSupport\Headers
これを新しいリポジトリ(ここでは acrobat-sdk-cmake)の直下に置きます。
acrobat-sdk-cmake/
├── Headers/
│ ├── API/ ← PIMain.c, ASCalls.h, AVCalls.h ...
│ └── SDK/ ← PIHeaders.h ...
└── CMakeLists.txt ← これから書く
ヘッダは加工しません。そのまま置きます。やることは、このディレクトリの使い方をCMakeに教えるファイルを1枚足すだけです。
CMakeLists.txtを書く
SDK側のCMakeLists.txtは以下のように設定します。
cmake_minimum_required(VERSION 3.10)
project(AcrobatSDK CXX)
# ビルド成果物を持たない「設定だけのターゲット」を作る
add_library(Acrobat_SDK INTERFACE)
# このターゲットを使う側に渡すインクルードパス
target_include_directories(Acrobat_SDK INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/Headers/API"
"${CMAKE_CURRENT_SOURCE_DIR}/Headers/SDK"
)
# プラットフォーム固有のプリプロセッサ定義
if(WIN32)
# Windows向けの定義
target_compile_definitions(Acrobat_SDK INTERFACE
WIN_PLATFORM
_WINDOWS
WIN_ENV
)
elseif(APPLE)
# 今回は使わないが、Mac用の定義もここに書いておく
target_compile_definitions(Acrobat_SDK INTERFACE
MAC_PLATFORM=1
)
else()
message(FATAL_ERROR "Unsupported platform: Acrobat SDK supports only Windows and macOS.")
endif()
# 共通のプリプロセッサ定義
target_compile_definitions(Acrobat_SDK INTERFACE
ACRO_SDK_LEVEL=0x000A0000
PI_ACROCOLOR_VERSION=0x00060000
PDMETADATA_HFT=1
PLUGIN=1
# SDKのサンプルを利用する場合の定義
ACRO_SDK_PLUGIN_SAMPLE=1
)
ここに出てくる定義の多くは、vcxprojのプロパティページに書いてあったのと同じものです。GUIの奥に埋まっていたものが、テキストとして目の前に出てきました。これだけでも、だいぶ見通しが良くなりました。
ただし、プロパティページだけを見ていても定義は揃いませんでした。SDKのサンプルに付属する次のファイルにも、ビルド時に渡される追加のコンパイルオプションが書かれています。
Acrobat_DC_SDK_Windows_2021_v3\Adobe\Acrobat DC SDK\Version 1\PluginSupport\Samples\AcroDspOptions.rsp
こちらに書かれているものも、上記の target_compile_definitions にまとめて含めておきます。逆に言えば、サンプルプロジェクトが暗黙に受け取っていた設定がここに隠れていたということで、秘伝のタレの正体の一部はこれだったわけです。
INTERFACE ライブラリ
add_library(Acrobat_SDK INTERFACE) で作られるものは、コンパイルもリンクもされない、空っぽのターゲットです。.lib も .dll も生成されません。
ではなぜ作るのか。このターゲットは「自分を使う人に適用してほしい設定」を運ぶための入れ物、という位置づけだからです。CMakeではこれを usage requirements(使用要件)と呼ぶらしいです。
Acrobat_SDK(INTERFACE ライブラリ)
│
├── インクルードパス: Headers/API, Headers/SDK
└── コンパイル定義: WIN_PLATFORM, ACRO_SDK_LEVEL=... など
↓ target_link_libraries() した相手に、そっくり引き継がれる
MyCustomPlugin(実際にビルドされるターゲット)
使う側は、後述するように1行書くだけで、インクルードパスやプリプロセッサ定義が自動的に適用されます。
target_link_libraries(MyCustomPlugin PRIVATE Acrobat_SDK)
PRIVATE / PUBLIC / INTERFACE の使い分け
target_ 系のコマンドには必ずこのキーワードを付けます。意味は「その設定を誰に適用するか」を示すものです。
| キーワード | 自分自身に適用 | 使う側に伝播 |
|---|---|---|
PRIVATE |
○ | × |
PUBLIC |
○ | ○ |
INTERFACE |
× | ○ |
INTERFACEライブラリは自分自身をコンパイルしないので、指定できるのは INTERFACE だけです。逆にプラグイン側は誰にも使われないので、基本的にすべて PRIVATE で設定します。
以上でAcrobat SDKのプラグイン作成に必要なインクルードファイルのパッケージ化は終わりです。これをリモートリポジトリにpushして、タグ(またはコミットSHAを控えておく)を打ちます。
ステップ2:プラグインを作成する
題材にするサンプル:BasicPlugin
この記事ではプラグインをゼロから書くことはせず、SDKに付属する BasicPlugin サンプルをそのまま題材にします。
Acrobat_DC_SDK_Windows_2021_v3\Adobe\Acrobat DC SDK\Version 1\PluginSupport\Samples\BasicPlugin
BasicPluginは、プラグインとして成立する最小限のコードだけを持つサンプルです。Acrobatのメニューに「Basic Plugin」という項目を追加し、それを選ぶと、プラグイン名と現在開いているPDFのページ数をダイアログで表示します。プラグイン開発を始めるときの出発点としてSDKが用意しているもので、ビルドの話に集中するにはちょうどよい題材です。
使うのは次の2ファイルだけです。
-
BasicPlugin.cpp… メニュー項目の追加と、選ばれたときの処理 -
BasicPluginInit.cpp… Acrobatとのハンドシェイクと初期化
これを自分のリポジトリの src/ にコピーすると、プラグイン側は次の構成になります。プラグイン側のプロジェクトはmy-acrobat-pluginという名前をつけています。この記事で作るのは、ここに置く CMakeLists.txt 1枚です。
my-acrobat-plugin/
├── src/
│ ├── BasicPlugin.cpp ← SDKのサンプルからコピー
│ └── BasicPluginInit.cpp ← SDKのサンプルからコピー
└── CMakeLists.txt ← この記事で書く
C++のコードには手を入れません。入れ替えるのはビルド設定だけです。
CMakeLists.txtを書く
プラグイン側のCMakeLists.txtを、SDKの取得から順に書いていきます。
SDKを取ってくる:FetchContent
include(FetchContent)
FetchContent_Declare(
AcrobatSDK
GIT_REPOSITORY https://github.com/<your-account>/acrobat-sdk-cmake
GIT_TAG <tag or commit hash>
)
FetchContent_MakeAvailable(AcrobatSDK)
FetchContent はCMake標準の機能で、configure時に外部リポジトリをcloneして、そのCMakeLists.txtを自分のビルドに取り込みます。これでAcrobat_SDKが使えるようになります。
同じようなことは git submodule でもできます。ただ今回FetchContentを選んだのは、
- 利用側のcloneが普通の
git cloneで済む(--recursiveの付け忘れ事故がない) - バージョン指定がCMakeLists.txtの中で完結するので、ビルド設定とSDKのバージョンを同じファイルで管理できる
という理由です。社内で既にsubmodule運用が確立しているなら、add_subdirectory() で取り込む形でも問題ありません。
GIT_TAG にブランチ名を書かない
開発中はつい GIT_TAG main と書いてしまいますが、これをやるとビルドするタイミングによって降ってくるSDKが変わる可能性があります。同じコミットをcloneしても同じ成果物にならないので、「昨日は通ったのに今日は通らない」「自分の環境では再現しない」という事故の原因になります。
GIT_TAG にはコミットSHAかタグを指定します。SHAはフルで書くのが確実です。
ターゲットを作る
add_library(MyCustomPlugin SHARED
src/BasicPlugin.cpp
src/BasicPluginInit.cpp
${acrobatsdk_SOURCE_DIR}/Headers/API/PIMain.c
)
BasicPluginのソース2つに加えて、SDK側のディレクトリにある PIMain.c をビルド対象に含めます。
${acrobatsdk_SOURCE_DIR} は、FetchContentが自動で定義してくれる変数で、cloneされたSDKリポジトリのパスが入っています(変数名は FetchContent_Declare の第1引数を小文字にしたもの + _SOURCE_DIR)。実体は build/_deps/acrobatsdk-src あたりです。
PIMain.c は、SDK が提供するプラグインの入口を実装したファイルです。ここには Acrobat が最初に呼び出すエントリポイント PlugInMain() に加えて、続くハンドシェイク処理である PISetupSDK() と PIHandshake() も入っています。開発者が中身を書き換える必要はなく、プロジェクトに含めるだけで使えます。
Acrobat がプラグインをロードすると、まずこの PlugInMain() が呼ばれます。ここで渡されるのは、アプリ側のハンドシェイクバージョン、プラグインが使うバージョンを書き戻すためのポインタ、SDK セットアップ関数(PISetupSDK)を受け取るためのポインタ、そして Windows では HWND と HINSTANCE を含むプラットフォーム固有のデータです。PIMain.c はこれらをグローバル変数に保存し、ハンドシェイクのバージョンをすり合わせて、PISetupSDK() をアプリ側に渡し返します。
HFT(Host Function Table)はこの'PISetupSDK()' の中でインポートされます。ここで "Core" "Cos" "PDModel" "AcroView" といった HFT 名とバージョンを指定して SDK 関数のポインタ表を取得し、gCoreHFT gAcroViewHFT のようなグローバル変数に格納します。
AVMenuItemNew() のような Acrobat SDK の関数は、こうして取得した HFT に並んだ関数ポインタを経由して呼び出されます。実際のコードでは #define マクロがこの間接参照を隠しているため、開発者は普通の関数呼び出しと同じ感覚で書けます。
出力ファイルの拡張子を .api にする
set_target_properties(MyCustomPlugin PROPERTIES
PREFIX ""
SUFFIX ".api"
)
Acrobatプラグインの拡張子は .api です。CMakeに SHARED と伝えると、Windowsでは既定で .dll が出力されるので、SUFFIX で上書きします。
PREFIX "" も追加しておく
PREFIX を空にしておかないと、MinGWなど一部のツールチェーンで libMyCustomPlugin.api のようにファイル名の頭に lib が付いてしまいます。MSVCでは既定が空なので実害はありませんが、1行なので入れておくのが無難だと思います。
SDKを繋ぐ
target_link_libraries(MyCustomPlugin PRIVATE Acrobat_SDK)
ステップ1で作ったINTERFACEライブラリを繋ぎます。これにより、インクルードパスとプリプロセッサ定義がまとめて降ってきます。
ここで混乱しやすいのですが、この行は .lib を一切リンクしていません。前述のとおりAcrobatプラグインはHFT経由でSDK関数を解決するので、そもそもリンクすべきライブラリファイルが存在しないのです。
target_link_libraries という名前なのに「リンク」していないのは紛らわしいですが、CMakeではINTERFACEライブラリに対してこう書くのが正式な作法のようです。
MSVC固有のオプション
if(MSVC)
target_compile_options(MyCustomPlugin PRIVATE /utf-8)
target_link_options(MyCustomPlugin PRIVATE /EXPORT:PlugInMain)
endif()
/EXPORT:PlugInMain
前述のとおり、Acrobat は DLL を読み込んだあと、その中の PlugInMain というシンボルを名前で探して呼び出します。つまりこのシンボルは、Acrobat がプラグインを見つけるための唯一の入口であり、エクスポートされていなければ Acrobat は入口を特定できず、プラグインはロードされません。ビルド自体は成功していて DllMain まで到達しているのに、コールバックが一切呼ばれない——というのは非常によくあるハマるポイントです。
エクスポートするには、リンカオプションに /EXPORT:PlugInMain を指定します。
このオプションを if(WIN32) に入れない
/EXPORT: はMSVCリンカ専用の書式です。if(WIN32) の中に入れてしまうと、MinGWでビルドしようとしたときに壊れます。また、分岐の外に置いたまま将来macOS対応を足すと、clang が /EXPORT:PlugInMain をファイルパスと解釈してリンクエラーになります。
「プラットフォーム」と「コンパイラ」は別の軸なので、条件も分けておくのが安全です。
/utf-8
SDKのサンプルコードは古く、日本語コメントがShift-JISで書かれていることがあります。これをUTF-8に変換すると、今度はMSVCが(BOMがないため)Shift-JISとして読もうとして文字化けや警告が出ます。
/utf-8 を付けると、MSVCはソースをUTF-8として読み、実行文字セットもUTF-8にします。ソースをUTF-8(BOMなし)で統一すると決めたなら、このオプションをセットで付けるのが一番すっきりします。
できあがったCMakeLists.txt
ここまでを1枚にまとめると、プラグイン側のCMakeLists.txtは次のようになります。
cmake_minimum_required(VERSION 3.16)
project(MyCustomPlugin VERSION 1.0.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
AcrobatSDK
GIT_REPOSITORY https://github.com/<your-account>/acrobat-sdk-cmake
GIT_TAG <tag or commit hash>
)
FetchContent_MakeAvailable(AcrobatSDK)
if(WIN32)
add_library(MyCustomPlugin SHARED
src/BasicPlugin.cpp
src/BasicPluginInit.cpp
${acrobatsdk_SOURCE_DIR}/Headers/API/PIMain.c
)
set_target_properties(MyCustomPlugin PROPERTIES
PREFIX ""
SUFFIX ".api"
)
else()
message(FATAL_ERROR "Unsupported platform: Windows only")
endif()
target_link_libraries(MyCustomPlugin PRIVATE Acrobat_SDK)
if(MSVC)
target_compile_options(MyCustomPlugin PRIVATE /utf-8)
target_link_options(MyCustomPlugin PRIVATE /EXPORT:PlugInMain)
endif()
プラットフォームで分けている if(WIN32) と、コンパイラで分けている if(MSVC) が別々にあるのは、見ている軸が違うためです。macOS対応を足すときは前者に elseif(APPLE) を追加することになります。
ビルドする
コマンドライン
# configure:ビルドツリーを build/ に生成する
cmake -B build
# build
cmake --build build --config Release -j
-B build は「build というディレクトリにビルド成果物を作る」という指定です。ソースツリーを汚さない、いわゆる out-of-source ビルドになります。つまり、buildディレクトリを削除すれば、プロジェクトはクリーンな状態に戻ります。
--config Release は、Releaseビルドを行うための指定です。省略するとDebugでビルドされます(Visual Studioの場合)。
成果物は build/Release/MyCustomPlugin.api に出力されます。
64bitであることを確認する
現行のAcrobatは64bitのみです。もし cmake -B build -A Win32 としてしまうと、エラーにならずに32bitの .api が出来上がり、Acrobatがロードできず、プラグインが確認できないという分かりにくい失敗をします。
確実に64bitでビルドされるように、保険としてCMakeLists.txtに数行入れておくと良いです。
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "Acrobat plug-ins must be 64-bit (configure with -A x64)")
endif()
Acrobatに読み込ませる
生成された .api を、Acrobatのプラグインフォルダにコピーしてから Acrobat を起動します。
C:\Program Files\Adobe\Acrobat DC\Acrobat\plug_ins\
パスはAcrobatのバージョンによって異なります。また、プラグインのロードには保護モードの設定などが関わる場合があるため、動かないときはSDKのドキュメントを確認してください(Acrobat Reader向けに配布する場合は、別途Adobeから発行されるキーが必要です)。
保護モード(サンドボックス)の必須化について
アドビから、保護モードを無効化するオプションを廃止するアナウンスが出ています。予定時期はmacOSが2026年10月、Windowsが2026年12月で、以降はサードパーティ製プラグインもサンドボックス環境内で動作する必要があります。対応していないプラグインは正常に動作しなくなる可能性があるとされているので、プラグインを配布している場合は早めに確認しておいた方がよさそうです。今回利用したサンプルのBasicPluginはサンドボックス内で動作するために追加の対応は不要ですが、外部ファイルやデータストリームの取り込みや書き出しを行う場合はこれを対応する必要があります。
おわりに
CMake化して一番良かったのは、ビルド作業が簡単になったこと以上に、「なぜその設定が必要なのか」を説明できるようになったことです。Visual StudioのUIで操作していたときには、どれも「そういうものなのね」という感じで細かな設定はスルーしていました。設定をテキストに書き下すと、分からない部分を分からないまま残せなくなるのがCMakeの良いところだと思います。そうすると、マクロの役割や意味への理解が深まり、SDKのヘッダファイルが各種定義に応じてどのように動作や解釈を切り替えているのかを、より正確に理解できるようになりました。秘伝のタレを取り出して分析してみた、という感覚です。
次回は、この構成にmacOS向けのビルド設定を足す話を書く予定です。.acroplugin バンドルの作り方、Objective-C++でのコンパイルが必要になる理由など、思った以上にハマりどころが多いので、そのあたりも含めてまとめる予定です。

