translation:adc:cocoa:foundation:introduction_to_dynamically_loading_code:0600_plugins

原文:Code Loading Programming Topics: Plug-in Architectures

プラグインアーキテクチャ

本章では、プラグインを通じた拡張性を持つアプリケーションの構築方法を説明します。 モジュラー式で、カスタマイズ性があり、容易な拡張性を持つアプリケーションを作りたいならば、本章を読みプラグイン機構を構築するための様々な方法を学ぶべきです。

プラグインアーキテクチャは、モジュラー式でカスタマイズ性があり、容易に拡張可能なアプリケーションの構築を試みる開発者にとり、魅力的な解決策です。 サードパーティがアプリケーションのソースコードに触れることなく、アプリケーションへ機能の追加を可能にする巧みな方法として始まったこの手法は、多くの開発者にとって、アプリケーション開発用の成熟した方法論へと発展しました。

プラグインホストの枠組みとして適切な設計で構築されたアプリケーションとプラグインのセットは、開発者たるあなたに多くの利益をもたらします:

  • アプリケーションの機能の実装と組み込みを、非常に素早く行うことが出来ます。
  • プラグインは明確に定義されたインタフェースでモジュールに分離されるので、問題の素早い分離と解決が可能です。
  • ソースコードを変更することなく、アプリケーションのカスタム版を作成することが出来ます。
  • アプリケーション原開発者の手を煩わせずに、第三者が追加機能を開発することが出来ます。
  • 異なる言語で書かれたレガシーコードをラップするのにプラグインインタフェースが利用できます。

プラグインアーキテクチャを持つアプリケーションの利用により、エンドユーザーもまた恩恵を受けます:

  • 個々のワークフローに合わせて機能をカスタマイズ出来ます。
  • 不必要な機能を無効にすることが出来、潜在的に、アプリケーションのユーザーインタフェースの単純化、必須メモリ量の削減(reducing memory footprint)、そして実行性能の改善に繋がります。

プラグインとは、明確に定義された拡張用の機構を通して、アプリケーション(ホストアプリケーションと呼ばれます)に機能を追加するバンドルです。 これはサードパーティ開発者が、アプリケーションのソースコードに触れることなく機能を追加することを可能にします。 またこれは、ユーザーが適切なフォルダに新しいバンドルをインストールするだけで、新機能をアプリケーションに追加することも可能にします。 スクリーンセーバーモジュール、環境設定パネル、Interface Builderパレット、Adobe Photoshopのグラフィックスフィルタ、iTunesの視覚効果、これら全てがプラグインの例です。

グラフィックスプログラムの新しい出力フィルタやビデオ編集プログラムの新しいトランジッションスタイル、あるいはその他の機能といった明確な機能単位を提供する、それぞれの種類のモジュールの複数インスタンスを追加したい時はいつでもプラグインを使います。 ホストアプリケーションを、新しいピースをはめ込む箇所が無限にある一種のジグソーパズルと考えることが出来ます。 プラグインはパズルにはめる追加のピースで、プラグインアーキテクチャはピースの取りうる形を決定します。 もしプラグインが不適切な形だったら、パズルにはめることは出来ません。 図1は、この比喩を表しています。適合するピースはアプリケーションに機能をもたらし、適合しないピースは右辺から外れています。

図1 終わりのないジグソーパズルとしてのプラグインアーキテクチャ

典型的なプラグイン機構は、プラグインが実装しなければならないメソッドまたは関数の一覧の形、もしくはプラグインが使用しなければならない基底クラスの形を取りますが、これは十分ではありません。 ホストアプリケーションの開発者は、プラグインの形式のみならず、アプリケーションのプラグイン環境で正しく機能させるために、各メソッドまたは関数が取りうる全ての挙動を文章化しなければなりません。

プラグインは大抵、アプリケーション用の新機能が実装された実行可能コードを持つ、読込み可能バンドルです。 プラグインがリソースを持つ必要は必ずしもありません。場合によっては、コードだけが必要です。 稀なケースとして、Mac OS Xのスクリーンセーバー用の.slideSaverモジュールといった、コードを持たずにリソースを追加するプラグインもあります。

通常、プラグインはいくつかの決まった場所にインストールされます。 プラグインはどこからでも読み込むことが出来ますが、Mac OS X APIはこれら標準パスからの検索をサポートします。

標準パスは以下の通りです(applicationSupportDirectory は、あなたのアプリケーションのサポートファイルを含むディレクトリを指します)。

  • ~/Library/Application Support/applicationSupportDirectory/PlugIns
  • /Library/Application Support/applicationSupportDirectory/PlugIns
  • applicationBundleDirectory/Contents/PlugIns

アプリケーションにプラグイン機能を搭載するなら、サードパーティ開発者や組織内の開発者向けに実装のための明確な定義を持つプラグインアーキテクチャを策定する必要があります。 プラグインアーキテクチャは開発者次第です。 理にかなった具体的なアーキテクチャは、大部分がアプリケーションに依存します。 しかし、多くのプラグインアーキテクチャは同一の基本プランを共有しています。

オブジェクト指向言語において、アプリケーション開発者はプラグインアーキテクチャ─パズルピースの取りうる形─を、独自クラスの要件仕様によって定義します。 典型的にこれら要件は、仮想基底クラスまたは(Objective-Cのプロトコルのような)メソッドの一覧の形を取ります。 主要クラス(principal class)として知られるこの独自クラスは、他の補助コードリソースとともにプラグインバンドルの一部として含まれます。 プラグイン読み込みの時が来ると、ホストアプリケーションはそれが要件に適合するかどうか確認します。 ピースが合致すれば、アプリケーションはクラス(インスタンスのファクトリ)にインスタンスの生成を要請します。 プラグインがアーキテクチャに適合しなければ、そのコードは読み込まれるものの、不正なファクトリがインスタンスを生成することは一切ありません。

C言語は直接的なオブジェクト指向の概念はサポートしませんが、エントリーポイントとプラグインの“オブジェクト群”とやり取りするコールバック関数に基づくプラグインアーキテクチャを定義することが可能です。 プラグインクラスの要件定義の代わりにアプリケーション開発者は、プラグインが実装する関数セットと、異なる種類のメッセージを処理するコールバック関数の登録メカニズムを定義します。 アプリケーションは、必須のエントリーポイント関数が実装されているかどうか確認するために、プラグインに問い合わせます。 実装されていれば、アプリケーションはプラグインの機能を呼び出すために、それら関数をコールします。 エントリーポイントの内部では、プラグインは他のメッセージに応答するためのコールバック関数を登録することが出来ます。

Carbonアプリケーションでのより高度な動作のために、CおよびC++プラグインの両方と共に動作し“オブジェクト指向”のアーキテクチャを定義するCore FoundationのCFPlugIn不透明型が使用可能です。

プラグイン用アーキテクチャの定義には幾つかの方法があり、最適解はプログラミング環境とプラグインの種類によって異なります:

  • 適合するプラグイン用にObjective-Cのプロトコルを定義する。
  • プラグインが継承するための抽象基底クラスを定義する。
  • プラグインが実装するC言語の関数と登録コールバック関数の仕組みを定義する。
  • Core FoundationのCFPlugIn不透明型と用いてプラグインインタフェースを定義する。

プラグインインタフェースを定義する容易な方法は、アプリケーションを記述したものと同一の言語を用いる事ですが、これは必須ではありません。 例えば、Objective-CのCocoaアプリケーションを書いていた場合、プロトコルまたは基底クラスに基づくプラグインアーキテクチャを使う事が最も理に適います。 しかしながら、CFPlugInかシンプルな所定のコールバック関数セットを用いた、Cベースアーキテクチャの非Cocoaプラグインを提供することも可能です。

真にオブジェクト指向のプラグインアーキテクチャを記述するのであれば、プラグイン用の実装パーツを基底クラスで提供するのか、もしくはプラグインが実装するメソッド・関数セットの単純な定義を提供するのかを決めなければなりません。 プラグインが多数の機能性を共有し、それらをメインアプリケーションのコード内で提供することが不便だと考えられる場合は、基底クラスを使う方法が最適です。 データ処理モジュールといった他のプラグインでは、ベースとなる機能を要求しないこともあり、標準のメソッド集合をシンプルに実装出来ます。

以後の項では、Mac OS Xで利用可能なプラグイン形態と、使用すべき状況についてより詳細に解説します。

プロトコル

Objective-Cは、クラスの継承ヒエラルキーとは隔絶された、メソッドの抽象的なリストの考えを持ちます。 これは、開発者にクラス定義やメソッドの実装を行うことなく、関連する機能集合を定義する手段を与えます。 これにより、継承ヒエラルキーの場所を問わず、どんなクラスに対してもメソッドを実装することが可能です。 Objective-Cではこれらメソッドリストをプロトコルと呼びます。 C++では純粋仮想関数のみを含む抽象クラスが、異なるメカニズムではあるものの、本質的にはプロトコルと同じ目的を果たします。

プラグインアーキテクチャの定義にプロトコルを使う事が出来ます。 プラグイン開発者はプロトコルに適合するクラスを書き、このクラスをプラグインバンドルの主要クラスとして使用します。 実行時、ホストアプリケーションはプラグインの使用前にプロトコルに合致しているかどうか、確認する事が出来ます。

以下の3つの条件のうち少なくとも1つが当てはまるのならば、プラグインアーキテクチャにプロトコルを使うべきです:

  1. それぞれのプラグインでコードの共有部分が少なく、そのため基底クラスが精々メソッドの一覧程度でしかない。
  2. プラグイン開発者は、プラグインの主要クラスを様々な基底クラスから派生させるたいことがある。
  3. アプリケーションが多くの異なる種類のプラグインに対応しており、プラグイン開発者はいくつかの異なるプラグインタイプを1つのプラグインとして書きたい事がある。

プラグインがコードの中核セットを共有する必要があるならば(条件の1つめ)、プラグインの主要クラスが継承するための基底クラスを用意した方が良いでしょう。 プラグイン間でコードを共有する必要があるが、異なる基底クラスに対応したり、1つのプラグインで複数のプラグインタイプに対応したい場合は、共有コードをアプリケーションの中に置き、プラグインからアクセスするためのフックを提供するか、プラグインがメンバーとして使える単独のクラスを作成するべきです。

Cocoaには形式プロトコル(formal protocol)と、非形式プロトコル(informal protocol)の違いがあります。 プラグイン定義の必要に応じて、どちらかのプロトコルを使用することが出来ます。 クラスが形式プロトコルを採用するという事は、本質的にはプロトコルの全メソッドを実装するという“契約にサイン”する事です。 クラスがプロトコルを採用したもののメソッドの実装漏れがあった場合、コンパイラは警告を出し、実装されていないメソッドが呼ばれた時は実行時エラーが発生します。 形式プロトコルはリスト1に示されるように、@protocol@endの間にメソッドのプロトタイプを記述するというObjective-Cの文法を用いて定義されます:

リスト1 簡単な形式プロトコル

@protocol MyStringProcessing
 
- (NSString *)processString:(NSString *)aString;
 
@end

一方、非形式プロトコルは実装が任意のメソッドの一覧です。 リスト2が示すように、非形式プロトコルは通常NSObjectのカテゴリとして宣言され、任意のCocoaクラスでそのメソッドを実装する事が可能です。 クラスが非形式プロトコルメソッドの実装を省略した場合、コンパイル時の警告は出ません。 しかし、クラスであるメソッドの実装が期待されるも実装されていなかった時は、実行時エラーが発生するでしょう。

リスト2 簡単な非形式プロトコル

@interface NSObject(MyStringProcessing)
 
- (NSString *)processString:(NSString *)aString;
 
@end

ホストアプリケーションが全てのプラグインにメソッド集合の厳密な実装を要求するなら、形式プロトコルでプラグインアーキテクチャを定義すべきです。 一部ないし全てのメソッドの実装が任意ならば、非形式プロトコルを使用し、実行時に主要クラスがどのメソッドに応答するか確認しましょう。 一部のメソッドの実装が必須で一部がオプションであれば、プラグイン作者のためにメソッドの実装区分をドキュメントに明記した上で、非形式プロトコルとしてアーキテクチャを定義する事が出来ます。

抽象基底クラス

インスタンス化される事が目的ではなく、他のクラスから継承される基底クラスとして従事するクラスがあります。 これら抽象基底クラスは、多くの異なる下位クラスから使用されるメソッドやインスタンス変数の共通定義を提供します。 抽象基底クラス自体は不完全ですが、下位クラス実装の負担を軽くする便利なコードを含む事でしょう。

しばしば、ホストアプリケーションのプラグインは幾つかの共通の機能を持つもので、多くのプラグインアーキテクチャはプラグインの主要クラスが継承するための抽象クラスを定義します。 その基底クラス及び関係するコードは、通常フレームワークの中にまとめられます。 プラグイン開発者はそのアプリケーションのフレームワークをリンクし、適切なヘッダをインクルードする事が出来ます。

Mac OS Xのスクリーンセーバーアーキテクチャが、抽象基底クラスを用いた良い例です。 全てのスクリーンセーバーは本質的に同じ事を行います: スクリーンにアニメーションを描画するという事です。 スクリーンに描画するために必要なコードの大部分は、Application KitのNSViewクラスによって既に取り扱われており、スクリーンセーバーがこの挙動を再実装する必要はないのです。 加えて、全てのスクリーンセーバーはフェードイン・アウトやその他のアニメーションタイミングを扱うコードを必要とします。 したがって、Mac OS Xのスクリーンセーバーは須く、NSViewにスクリーンセーバー特有の機能を追加するScreenSaverViewクラスを継承します。

エントリーポイントとコールバック関数

CでCarbonアプリケーションを記述している場合、プラグインアーキテクチャを定義する一番簡単な方法は、プラグインが実装しなければならない関数のセットを定義する事です。 これはObjective-Cのプロトコルと類似していますが、必然的にクラスは含まれません。また、関数のリストはどのヘッダファイルでも明示的に定義されず、プラグインアーキテクチャのドキュメントの中でのみ定義されます。 最も単純なプラグイン形態を除き、Core FoundationのCFPlugIn不透明型の利用を検討すべきです。

多くのプラグインアーキテクチャは1つのプラグインエントリーポイントを定義します。エントリーポイントとはアプリケーションがプラグインにメッセージを送るために使用する物です。 これらメッセージに応答し、プラグインは処理の様々な面を扱うためのコールバック関数を登録する事が出来ます。

例えば、iTunesの視覚効果プラグインはメインエントリーポイントを通して、初期化、後片付け、アイドルのメッセージを受信します。 初期化フェイズでは、プラグインは視覚効果関連のメッセージを扱うコールバック関数を登録します。 曲の再生開始、一時停止、その他の状態変化が発生した際、iTunesはプラグインに対しコールバックを通して、描画実行の問い合わせや他の視覚効果関連メッセージの提供を行います。

ホストアプリケーションは、実際にプラグインのエントリーポイント関数や関数群を見つける方法を必要とします。 これはエントリーポイント関数名の規格化と、実行時に適切な識別子を持つ関数ポインタの検索によって解決します。 Core FoundationのCFBundle不透明型を使用し、関数名と関数ポインタを変換することが出来ます。

プラグインの読み込みと関数の検索を行うCFBundleの使い方については、Core FoundationプログラミングトピックのBundle Programming Guideをご覧ください。

Core FoundationのCFPlugIn

Carbonのホストアプリケーション開発者は、最も単純なプラグイン形態を使う場合を除いて、Core FoundationのCFPlugIn不透明型の使用を検討すべきです。 CFPlugInはプラグインが持つ全ての基本機能を扱うため、新たなプラグインモデルの設計、実装、そしてテストから開発者を解放します。

Core FoundationのCFPlugInはMicrosoftのComponent Object Model (COM)と基本的な互換性があります。 このモデルにおいて、ホストアプリケーションは1つ以上のインタフェースからなる1つないし複数の型を定義します。 プラグインは、サポートするそれぞれの型の全てのインターフェスの全関数のみならず、その型のインスタンスを生成するための“ファクトリ関数”も実装します。 CFPlugInは、ユニークな識別型、インタフェース、ファクトリの名前とバージョンの競合の回避に、汎用一意識別子(UUID)を使う点においてCocoaより有利です。

CFPlugInの使用方法に関する情報は、Core FoundationプログラミングトピックのPlug-insをご覧ください。

どんな種類の拡張性もセキュリティ上の懸念をもたらします。 プラグインのコードはホストアプリケーションと同じアドレス空間で実行されるため、その空間への操作に対する理論的な制限が原則的に存在しません。

しかしながら、プラグインアーキテクチャの予想外の利用や悪用を未然に防ぐ、いくつかの事項が存在します:

  • ユーザーに対し、サードパーティ製プラグインのインストールと、それらが引き起こすかもしれない副作用について警告を出す。どのソフトがインストールされるかはユーザーによって本当に様々なので、確実に周知しなければならない。
  • プラグインによるアプリケーションコードとデータへの直接アクセスを制限する。アプリケーションコントローラオブジェクト、アプリケーションデータ、そして予想外の誤用や故意の悪用の可能性のある情報へのポインタは、決してプラグインに提供しない。

良い記述、善意のコードと悪い記述、悪意のコードを簡単に区別する方法は無いので、ユーザーにそれ以上の警告を出すことは出来ず、また、慎重に扱うべきアプリケーションデータをプラグインに直接見せてはなりません。 予期しない事態におけるアプリケーションの挙動は、プラグインアーキテクチャの実装次第で良い方向にも悪い方向にも向かいます。

  • translation/adc/cocoa/foundation/introduction_to_dynamically_loading_code/0600_plugins.txt
  • 最終更新: 2015-01-06 11:51
  • (外部編集)