エンタープライズ:特集 2003/07/04 17:20:00 更新
C Magazine

C MAGAZINE 2002年8月号より転載
プログラムのレシピ――プログラミングの考え方・作り方 (13/13)

グラフィックエディタの製作(9)
基本機能の作成−プラグイン機能

 プラグインというのは、ソフトウェアの機能を拡張するモジュールのことです。 Windowsアプリケーションにプラグイン機能をつけるには、DLL(Dynamic Link Library)を使うのが簡単です。

プラグインのロードと実行

 DLLはアプリケーション本体とは別に作成して、アプリケーションの実行時に読み込むことができます。DLLを使うためには、DLLを作るとともに、DLLをロードする機能をアプリケーション本体に内蔵します。プラグインの場合には、ユーザがメニューからその機能を呼び出せるよう、メニュー項目も追加しなければなりません。

プログラム例

 C++BuilderのプログラムでDLLを使うときには、Win32 APIを使います。VCLに比べると使い方がちょっと難しいのですが、どんなDLLを使うときにもだいたい処理の内容は決まっているので、一度作ってしまえば応用がききます。

 DLLを使う側のアプリケーションの処理はList 18のようになります。主な内容は、

  • DLLのロード
  • DLLが含む関数へのポインタを取得
  • メニューにDLLの機能を呼び出すための項目を追加

です。

List 18 プラグインのロードと実行(UGEditorForm.cpp)
//============================================================= (1)
// DLL内関数へのポインタ型
typedef const char* (*TDLLGetNameProc)();
typedef void (*TDLLApplyProc)(Graphics::TBitmap*, TRect);
//============================================================= (2)
// フィルタプラグインを呼び出すためのメニュー項目
class TFilterPluginMenuItem : TMenuItem {
private:
    // DLLハンドルと、関数ポインタ
    HINSTANCE DLL;
    TDLLGetNameProc GetNameProc;
    TDLLApplyProc ApplyProc;
public:
    // コンストラクタ
    TFilterPluginMenuItem(TComponent* Owner, HINSTANCE dll)
    : TMenuItem(Owner) {
        //===================================================== (3)
        // 関数ポインタの取得
        DLL=dll;
        GetNameProc=(TDLLGetNameProc)GetProcAddress(dll, "_GetName");
        ApplyProc=(TDLLApplyProc)GetProcAddress(dll, "_Apply");
        //===================================================== (4)
        // イベントとキャプションの設定
        OnClick=ProcessOnClick;
        if (GetNameProc!=NULL) Caption=(*GetNameProc)();
            else Caption="フィルタ名不明";
        Enabled=(ApplyProc!=NULL);
    }
    // デストラクタ
    virtual __fastcall ~TFilterPluginMenuItem() {
        FreeLibrary(DLL);
    }
    // OnClickイベントに対する応答
    // フィルタを実行
    void __fastcall ProcessOnClick(TObject *Sender) {
        //===================================================== (5)
        // プラグインのApply関数を実行し、画面を再描画する
        (*ApplyProc)(
            GEditorForm-> GetBitmap(), 
            GEditorForm-> GetSelectedRect());
        GEditorForm-> RepaintSelectedRect();
    }
};
//============================================================= (6)
// フィルタプラグインをロードする
void TGEditorForm::LoadFilterPlugins() {
    HINSTANCE dll;
    TSearchRec file;
    // プログラムフォルダの"Filter_*.dll"を全てロードし、
    // 「フィルタ」メニューに項目を登録する
    AnsiString path=
        ExtractFilePath(Application-> ExeName)+"Filter_*.dll";
    if (FindFirst(path, faAnyFile, file)==0) {
        do {
            //================================================= (7)
            // DLLのロード
            dll=LoadLibrary(file.Name.c_str());
            if (dll!=NULL) {
                MFilter-> Add((TMenuItem*)
                    (new TFilterPluginMenuItem(MFilter, dll)));
            }
        } while (FindNext(file)==0);
    }
}

 まずDLLのロードを行うのはList 18-(6)です。ここではFindFirst関数とFindNext関数を使って、グラフィックエディタの実行ファイルがあるフォルダの中から、名前が「Filter_*.dll」にマッチするファイルをすべてロードします。DLLのロードにはWin32 APIのLoadLibrary関数を使います(List 18-(7))。 次に、DLL内部の関数へのポインタを取得します。関数ポインタの取得には、Win32 APIのGetProcAddress関数を使います(List 18-(3))。C++BuilderでDLLを作った場合、関数名の前に「_」(アンダースコア)がつくことに注意してください。たとえば「GetName」という関数名は「_GetName」になります。

 List 18-(1)は関数ポインタの宣言です。関数ポインタの宣言というのは、C言語の中でも苦手な人が多い文法です(私も含めて)。プラグイン本体(List 19、20)の関数宣言とよく見比べてみてください。

 List 18-(2)は、DLLの機能を呼び出すためのメニュー項目です。このメニュー項目をメニューに登録することによって、プラグインの機能をユーザが呼び出せるようにします。ポイントは、メニュー項目を選択したときのイベント処理を登録することと(List 18-(4))、プラグインの関数の呼び出し方法です(List 18-(5))。

プラグインの例

 今度はプラグイン本体を作ります。DLLを使ってプラグインを作るときには、プラグインのインタフェイスをしっかり決めておくことが大事です。今回は、次の2つの関数を用意することにしました。

●GetName関数

 フィルタの名称を返す関数です。メニューにフィルタを登録するために使います。

●Apply関数

 フィルタを適用する関数です。引数はビットマップとフィルタを適用する範囲です。プラグインの主な処理はApply関数に書きます。Apply関数はビットマップと適用範囲を受け取って、フィルタを適用します。


 簡単なフィルタの例として、色を反転する「反転フィルタ」(Fig. 17)と、絵をモノクロ化する「グレイスケールフィルタ」(Fig.18)を作ってみました。範囲を決めて、[フィルタ]メニューから適用するフィルタを選べば、フィルタを使うことができます。

Fig17

Fig. 17 反転フィルタ


Fig18

Fig. 18 グレイスケールフィルタ


プログラム例

 反転フィルタのプログラムはList 19、グレイスケールフィルタのプログラムはList20です。どちらもプログラムの構造はほとんど同じなので、主にList 19について説明しましょう。

List 19 反転プラグイン(Filter_Negative.cpp)
//============================================================= (1)
// フィルタの名称を返す関数
extern "C" __declspec(dllexport) const char* GetName();
const char* GetName() {
    return "色の反転(&N)";
};
//============================================================= (2)
// フィルタの本体
extern "C" __declspec(dllexport) 
    void Apply(Graphics::TBitmap *bitmap, TRect rect);
void Apply(Graphics::TBitmap *bitmap, TRect rect) {
    int *pixel;
    int x,y;
    //========================================================= (3)
    // ビットマップの形式を32ビットカラーに設定する
    bitmap-> PixelFormat=pf32bit;
    for (y=rect.top; y< rect.bottom; y++) {
        //===================================================== (4)
        // 画像の1行分のピクセルにアクセスする
        pixel=(int*)bitmap-> ScanLine[y];
        for (x=rect.left; x< rect.right; x++) {
            pixel[x]=~pixel[x];
        }
    }
    //========================================================= (5)
    /* Canvasを使ったバージョン(遅い)
    TCanvas *c=bitmap-> Canvas;
    TColor color;
    int x,y;
    for (y=rect.top; y< rect.bottom; y++) {
        for (x=rect.left; x< rect.right; x++) {
            color=canvas-> Pixels[x][y];
            color=(~color)&0xffffff;
            canvas-> Pixels[x][y]=color;
        }
    }
    */
}

List 20 グレイスケールプラグイン(Filter_Grayscale.cpp)
#include < vcl.h> 
#include < windows.h> 
#pragma hdrstop
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
    return 1;
}

 List 19-(1)はフィルタの名称を返すGetName関数です。DLLの関数を宣言するときには、「extren ”C”」や「__declspec(dllexport)」といった記法を使います。関数の内容自体は単純で、メニュー項目に登録するための文字列を返すだけです。 List 19-(2)はフィルタを適用するApply関数です。C++BuilderでDLLを作ると、DLLでもVCLが使えます。そのためアプリケーション本体とほぼ同じ要領でビットマップを操作することが可能です。反転フィルタの場合、単に選択範囲内のピクセルを順に読み出して反転するだけです。

 今回はフィルタでもVCLを使いましたが、Win32 APIを使って同様のフィルタを作ることもできます。その場合、ビットマップとしてはDIBを使うのがよいでしょう。

フィルタの高速化

 コメントアウトしたList 19-(5)は、TCanvasクラスのPixelsプロパティを使って反転フィルタを実現したものです。実際に動かすとすぐにわかるのですが、こちらは非常に動作が遅いです。

 高速化するためにはPixelsプロパティを使わず、List 19-(4)のようにTBitmapクラスのScanLineプロパティを使います。ScanLineプロパティはビットマップを直接操作するのに向いたプロパティで、ビットマップ上のピクセルを1行ずつ読み出すことができます。

 ScanLineプロパティを使うときには、List19-(3)のようにビットマップを読み出す形式を指定しておくのがよいでしょう。ここでは32ビットカラーに設定します。R、G、Bが各8ビットずつ、32ビットの下位24ビットに入ります。

 なおWin32 APIの場合には、DIBを使えば高速なフィルタを作ることができます。

C++Builderを使ったDLLの作り方

 たいていの開発環境でDLLを作ることができますが、ここではC++Builderを使って作る方法を説明しましょう。

 まず[ファイル]→[新規作成]→[そのほか]で新規作成ダイアログを開き、「DLLウィザード」を選択します(Fig. 19)。次のDLLウィザード(Fig. 20)では、作成するDLLのオプションを設定します。先のフィルタの例では、「ソースの種類」を「C++」にして、「VCLを使う」をチェックしました。

Fig19

Fig. 19 新規作成ダイアログで「DLLウィザード」を選択


Fig20

Fig. 20 DLLウィザード


 [OK]をクリックすると、List 21のようなDLLの雛型が生成されます。ここにList 19やList 20のようなプログラムを追加すればDLLのできあがりです。DLL向けにプロジェクトが生成されているので、通常のプログラムと同じように[Ctrl]+F9キーでコンパイルすれば、DLLファイルが得られます。

List 21 DLLウィザードが生成するDLLの雛型
#include < vcl.h> 
#include < windows.h> 
#pragma hdrstop
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
    return 1;
}

プロジェクトグループ

 今回のグラフィックエディタのように、アプリケーションとプラグインをいっしょに開発する場合には、「プロジェクトグループ」を使うと便利でしょう(Fig. 21)。プロジェクトグループはいくつかのプロジェクトをグループ化するものです。今回はグラフィックエディタ本体と2つのプラグインで、計3個のプロジェクトがあります。プロジェクトグループを使うとプロジェクト間をすばやく行き来できるので、とくにDLLの実行テストに便利です。

Fig21

Fig. 21 プロジェクトグループ


まとめ  今回のグラフィックエディタの制作は、これでひとまず完了です。あとはだんだん機能を充実させて完成度を上げていけばいいですね。改良のポイントとしては、たとえば次のようなものがあります。

●描画機能を追加する

 半透明ブラシとクローンブラシを作りましたが、同じ要領で別のブラシも作れます。ほかのグラフィックエディタやCG関連の書籍を参考にして、おもしろい描画機能を作ってみるといいでしょう。

●フィルタを増やす

 プラグイン機能を作りましたから、フィルタを増やすのは簡単です。反転フィルタやグレイスケールフィルタと同じ要領でDLLを作れば、簡単にフィルタを増やせます。

●ファイルの読み書きを強化する

 ビットマップ(.bmp)以外の形式も読み書きできるともっと便利になります。いろいろな画像ファイルの読み込みルーチンをWebなどで見つけてきて、組み込むのもいいですね。また「Susie」というソフトウェア向けにいろいろな画像読み書きプラグインが公開されているので、これに対応するのもよい方法です。


 ということで、「プログラムのレシピ」を特集でお届けしましたが、いかがでしたか? プログラミングは決して難しいものではありません。最初のうちはいろいろと試行錯誤する必要がありますが、案外すぐに慣れて、自分の思ったプログラムが作れるようになるものです。

 次回からは毎月、いろいろなアプリケーションの作り方を連載でお届けする予定です。「こんなアプリケーションの作り方が知りたい!」というご希望がありましたら、どしどし編集部までお寄せください。どうぞ次回からもお楽しみに!

コラム1
 C++Builderで作ったプログラムを配布するときには、必要なDLLやパッケージをいっしょに配布する必要があります。今回のグラフィックエディタの場合、以下の6つのファイルが必要です。
・C++BuilderインストールフォルダのBinサブフォルダ以下にある「borlndmm.dll」「cc3260mt.dll」
・WindowsインストールフォルダのSystem32サブフォルダ以下にある「rtl60.bpl」「rtl60.jpn」「vcl60.bpl」「vcl60.jpn」
 配布する際には、コンパイラやリンカのオプションも配布向けに設定したほうがいいでしょう。
 詳しい方法は次回からの連載で解説します。
関連記事
▼C MAGAZINE

関連リンク
▼ひぐぺん工房
▼「デスクトップマスコットを作ろう」紹介ページ
▼「デスクトップマスコットを作ろう」著者ページ

前のページ | 1 2 3 4 5 6 7 8 9 10 11 12 13 |      

[松浦健一郎(ひぐぺん工房),C MAGAZINE]

Copyright © ITmedia, Inc. All Rights Reserved.