メモ

戻る
Susieプラグイン関係。
  1. Susieプラグインをつくる


  2. プラグインのサイズを小さくする


  3. プラグインが返すLocalハンドルの扱い


  4. アルファブレンドの高速化


  5. スクリプトからプラグインを利用する


  6. C#, VB.NETからプラグインを利用する
その他
  1. アプリをVistaのUACに対応させよう

  2. Windows Vista環境変数一覧

  3. Visual C++ 2005 Express Editionを使い倒す -従来のWIN32 APIアプリの作成
    -WTLを使う


  4. フリーのコンパイラを使う


  5. DLL/プラグイン作成時の注意点


  6. 古いアプリケーションの表示をXPスタイルにする

プラグインのサイズを小さくする

* 部分的に、VC前提で書いてるところもあります。

Susieプラグインは*.spiという拡張子がになってますが、中身はただのDLLに過ぎません。
で、このDLLというやつをVC++で普通に作成してみると、何故かやたらとサイズが大きくなってしまうのです。
 何もしないようなDLLをつくっても、40KB超のファイルができてしまいます。
HDDの容量が100GBを越えるのが普通の時代になったとはいえ、無意味にファイルサイズが大きくなるのは、何か納得いかないものがあります。

エントリポイント

 MSDNで調べてみると、ファイルサイズが大きくなるのは、Cランタイムライブラリのスタートアップコードというのが原因らしいです。
ようは、C/C++のライブラリの初期化に必要なものらしいんですが、Susieプラグインをつくるくらいなら、 Cライブラリなしでも書けないことはありません。

そこで、このスタートアップコードというやつを省略してやることにします。
これで、プラグインのサイズを、10KB以下にすることができます。
やり方は簡単で、VCのプロジェクトのプロパティで、[リンカ] - [詳細] - [エントリポイント]で、DllMainをエントリポイントに指定するだけです。
 makefileなどを使う場合は、リンカオプションで、/ENTRY:"DllMain"とします。
BCCでも、エントリポイントさえ指定すれば、同じことができると思います。

 エントリポイントというのは、プログラムを起動したときに、一番初めに実行されるコードのことです。
デフォルトではユーザー定義のDllMain()ではなく、DllMainCRTStartup()というものが最初に呼ばれ、 その中でスタートアップコードを実行した後、ユーザー定義のDllMain()が呼ばれるようです。

スタートアップコードを省略すると、C/C++ライブラリが使えない以外にも、いくつかの制約が発生します。

逆に、一部のCライブラリは使用可能だったりします。

これら以外の文字列操作関数は、一切使えません。
strncpy, strcat, stricmpの代わりに、Win32 APIのlstrcpyn, lstrcat, lstrcmpiを使います。

* 実は、C/C++ライブラリに動的リンクするという方法で、C/C++ライブラリを使用しつつ、DLLのサイズを小さくするという方法があるんですが、 これをやると環境依存性が非常に大きくなってしまいます。
自分のPCでは動いても、他の人のPCではまったく動かないということになりかねないので、あまりお勧めできません。
* VC .NETだと、いくつかプロジェクトの設定を変えないと、うまくビルドできませんでした。
  • [C/C++] - [全般] - [デバッグ情報の形式] を [無効]に (プロジェクトの規定値から継承)
  • [C/C++] - [コード生成] - [C++の例外処理を有効にする] を [いいえ] に

operator new

 C++でプラグインを作成する際、newとdeleteが使えないというのは、けっこう致命的だったりします。
newという演算子には、2つの役目があります。
  1. ヒープにメモリを確保する
  2. コンストラクタを呼び出し、オブジェクトを初期化する

メモリを確保したいだけなら問題はありません。
newなんか使わなくても、HeapAlloc()なり、VirtualAlloc()なりを使えばいいのです。
問題は、2のコンストラクタの呼び出しです。
C++のポリモーフィズムを利用する場合、newは必須です。

 そもそも、なぜnewやdeleteが使えないのかというと、おそらく内部でCのmallocやfreeを呼んでいるからでしょう。
 そこで、operator new、deleteをオーバーロードしてやることにします。
グローバルなoperator newでもいいですが、基底クラスのnewをオーバーロードした方がスマートでしょう。
 派生クラスのインスタンスをnewで作成しても、基底クラスのoperator newが呼び出されます。

class spiBase	// プラグインの基底クラス
{
public:
    // operatorのオーバーロード
    static void* operator new(size_t nSize)
    {
        // 例外処理しないから、これでいっか
        return HeapAlloc(GetProcessHeap(), 0, nSize);
    }

    static void operator delete(void *p)
    {
        if (p != NULL)
            HeapFree(GetProcessHeap(), 0, p);
    }
    // その他のメンバは省略
    ....
}

これで、こころおきなくnew, deleteが使えます。
コンストラクタ、デストラクタの呼び出しはしなくていいのか、と思うかもしれませんが、 operator new, deleteの役割は、あくまでメモリ管理だけです。

 ちなみに、初期化されていないメモリブロックを、コンストラクタを呼び出して初期化するための placement newなんてのもあったりします。

プラグインが返すLocalハンドルの扱い

 Susieプラグインを、作成するにしても、使うにしても避けることができないのが、GetFile()やGetPicture()の返す、 Localハンドルとの付き合いです。

メモリハンドルへのポインタを関数のパラメータとしてやり取りする、ハンドルをLocalLock()に渡してメモリブロックを取得するなど、 慣れない人にはかなり使いづらいのではないでしょうか。

 さらに厄介なのは、この手のハンドルオブジェクトに付き物の、エラー処理の問題です。
プラグイン側でもアプリケーション側でも、エラー時のファイルやメモリハンドルの後始末は必要で、 コードのあちこちでLocalFree()なんて呼び出していたら、バグの温床になるだけでなく、めんどくさくて仕方ありません。

解決方法としては、幾つかあると思います。

  1. 例外処理を行う
  2. ハンドル操作をカプセル化する

 1は、まとめて一箇所でエラー処理をすればいいので、けっこう楽になります。
Cでプラグインを作成する場合でも、人によってはgotoを使って無理やり関数の最後に飛ばし、まとめてエラー処理をしたりするみたいです。
 C++でやるんなら、やはりカプセル化してしまうのが一番楽でしょう。
デストラクタでメモリを解放するようにすれば、関数からreturnした段階で自動でメモリが解放されるので、 エラー処理が非常に楽になります。
メモリのLockやなんかも、一度書いてしまえば再利用できますし。

サンプル : Localハンドルの管理クラス


●プラグインを作成するときは、こんな感じ。

int GetPicture(LPSTR buf, long len, HANDLE *pHBInfo, HANDLE *pHBm, ...)
{
    // 必要な情報を取得する
    ...

    // メモリ確保
    LocalHeap lhInfo, lhImage;
    BITMAPINFO* pInfo = static_cast<BITMAPINFO*>(lhInfo.Alloc(InfoSize));
    BYTE* pImage = static_cast<BYTE*>(lhImage.Alloc(ImageSize));
    
    if (pInfo == NULL || pImage == NULL)
        return ErrCode;
    
    // pInfo, pImageに画像情報、画像データを入れる
    // エラーが発生したら、何もせずreturnすれば良し
    ...
    
    // 呼び出し側にLocalハンドルを返す
    if (/* 成功なら */){
        *pHBInfo = lhInfo.Detach();
        *pHBm = lhImage.Detach();
    }
    return ErrCode;
}

●プラグインを使用するときは、こんな感じ。

int CallGetPicture(LPSTR filename)
{
    // 対応プラグインを調べる
    ...
    
    // プラグインの呼び出し
    HLOCAL hInfo = NULL, hImage = NULL;    
    int ErrCode = GetPicture(filename, 0, &hInfo, &hImage, ...);
    if (/* エラー? */)
        return ErrCode;

    // 画像情報、画像の取得
    LocalHeap lhInfo, lhImage;
    BITMAPINFO* pInfo = static_cast<BITMAPINFO*>(lhInfo.Attach(hInfo));
    BYTE* pImage = static_cast<BYTE*>(lhImage.Attach(hImage));
    
    // 画像を表示するなり、ファイルに保存するなりする
    // エラーが発生したら、何もせずreturnすれば良し
    ...
    
    // Localハンドルは、自動で解放される
    return ErrCode;
}





戻る inserted by FC2 system