BREWのメモリ管理(3)

なぜ、前回のプログラムでエラーが発生したのかというと、

void* operator new( size_t size ){
    return CApplet::getMemoryManager()->operator_new( size );
}

void operator delete( void* ptr ){
    CApplet::getMemoryManager()->operator_delete( ptr );
}

この部分で、CAppletに依存している部分があるからです。


自分の場合、IModuleとIAppletについては、AEEModGen.cやAEEAppGen.cを使わずに自分で実装しているので、CAppletが完成する前にnewを使っているのです。
そのため、CApplet::getApplet()を使用しても、不正なポインタしか返さないため、newをすると、アクセスバイオレーションを起こしてしまいます。


これをどうするか、しばらく考えてみました。
まず思いついたのが、CAppletが完成するまでは、newを使用せずにMALLOCで確保する方法。
CAppletを生成するまでにMALLOCで確保したメモリが、FREEで確実に解放されるという保証さえ出来ればいけると思います。
そして、CAppletを生成するまでにMALLOCで確保する物は、IModuleを実装したクラス(以下CModule)、IAppletを実装したクラス(以下CApplet)、IDisplayインスタンスぐらいなので、メインフレーム側では、わざとやらない限り解放することはないと思います。
なので、この方法でうまくいくと思います。


……しかし、自分は火の中に飛び込んでしまいました(;´Д`)


なんか、IDisplayインスタンスは仕方ないとして、CModuleやCAppletがoperator new以外で確保されていることが、気持ち悪く感じたのです。
なので、何とかCModuleやCAppletも何とかしてoperator newで確保できないかと考えてみました。


CModuleの生成は、BREWのエントリーポイントであるAEEMod_Loadが呼び出す、AEEStaticMod_Newで行われています。
つまり、ここでCModuleを生成するより前にMemoryManagerを生成すればいいのです。
そして、CModuleを解放した後に、このMemoryManagerを解放すればいいのです。


しかし、そこで問題が発生します。


このMemoryManagerを指しているポインタをどこに保持しておくか、という問題です。
BREWグローバル変数が使えないため、このポインタをどこにも保持できないのです。
一応、自己書き換えをすれば出来るのですが、それは最終手段です。
もし、今後メモリ管理が厳しくなり、メモリ保護が掛けられたりすると、非常に困るからです。


いろいろと考えていると、ふと思いつきました。
CModuleの領域にくっつけてやろう、と。
つまり、CModuleを生成するときに、memory poolからCModuleの領域+MemoryManagerポインタの領域を確保して、余分な領域にMemoryManagerへのポインタを入れてやればいいのです。
そして、CAppletを生成するときも、IModuleへのポインタが渡されるので、そこからMemoryManagerへのポインタを取得すれば、CAppletもmemory poolから生成することが出来ます。


あと、もしかしたらMALLOC()で確保したメモリでnewをしたい場合があると思うので、それ用のtemplateも作っておきます。


これをプログラムにまとめると、

// placement new
void* operator new( size_t size , void* ptr ){
    return ptr;
}

template< class T >
class MemoryAllocator{
public:
    // 引数なし
    static T* create(){
        void* p = MALLOC( sizeof( T ) );
        if( p == null ) return null;
        return new( p ) T;
    }
    // 引数ひとつ
    template< class V1 >
    static T* create( V1 v1 ){
        void* p = MALLOC( sizeof( T ) );
        if( p == null ) return null;
        return new( p ) T( v1 );
    }
    // 引数ふたつ
    template< class V1 , class V2 >
    static T* create( V1 v1 , V2 v2 ){
        void* p = MALLOC( sizeof( T ) );
        if( p == null ) return null;
        return new( p ) T( v1 , v2 );
    }
    // 引数みっつ
    template< class V1 , class V2 , class V3 >
    static T* create( V1 v1 , V2 v2 , V3 v3 ){
        void* p = MALLOC( sizeof( T ) );
        if( p == null ) return null;
        return new( p ) T( v1 , v2 , v3 );
    }
    
    // 後は必要に応じて追加汁
    
    // 解放
    static void destroy( T* ptr ){
        if( ptr != null ){
            ptr->~T();
            FREE( ptr );
        }
    }
};
IMemoryManager* getMemoryManager( IModule* module ){
    return *(IMemoryManager**))*1(;
}

// CModule生成部分
int CModule::StaticMod_New(
        int16             nSize ,
        IShell*             pIShell ,
        void*             ph ,
        IModule**           ppMod ,
        PFNMODCREATEINST    pfnMC ,
        PFNFREEMODDATA      pfnMF
    ){

    ...     // ちょっとした処理

    IMemoryManager*     pMemoryManager;
    pMemoryManager = MemoryAllocator< IMemoryManager >::create();
    if( pMemoryManager == null )  return ENOMEMORY;

    if( pMemoryManager->init() != 0 ) return ENOMEMORY;

    uint32 memsize = sizeof( IMemoryManager* );
    uint32 modsize = sizeof( CModule );

    CModule* pModule =
        (CModule*)pMemoryManager->operator_new( memsize + modsize );
    if( pModule != null ){
        // placement new
        pModule = new( (void*)(((char*)pModule) + memsize) ) CModule;
        if( pModule->init( pIShell , pfnMC , pfnMF ) ){
            // メモリマネージャをIModuleにくっつける
            *(IMemoryManager**)(((char*)pModule) - memsize)
                = pMemoryManager;
            *ppMod = (IModule*)pModule;
            return SUCCESS;
        }
    }
    return ENOMEMORY;
}

// CModule解体部分
uint32 CModule::Release( IModule* pThis ){
    CModule* pModule = (CModule*)pThis;
    if ( --(pModule->_nRef) == 0 ){
        if( pModule->_pfnModFreeData ){
            pModule->_pfnModFreeData( pThis );
        }
        IMemoryManager* manager = getMemoryManager( pModule );
        
        // CModuleをoperator_deleteで解放
        pModule->~CModule();
        manager->operator_delete(
            (void*))*2( );
        
        // 最後はMemoryManagerを解放して終わり
        MemoryAllocator< IMemoryManager >::destroy( manager );
        
        return 0;
    }
    return pModule->_nRef;
}
// CApplet生成部分
extern "C" int AEEClsCreateInstance(
        AEECLSID    ClsId ,
        IShell*     pIShell ,
        IModule*    pIModule ,
        void**        ppObj
    ){

    ...     // ちょっとした処理

    CApplet* pApplet =
        getMemoryManager( pIModule )->operator_new( sizeof( CApplet ) );
    if( pApplet == null ) return ENOMEMORY;
    // placement new
    pApplet = new( pApplet ) CApplet( pIModule , pIShell );

    ...     // ちょっとした処理
}

// CApplet解体部分
uint32 CApplet::Release( IApplet* pThis ){
    CApplet* pApplet = (CApplet*)pThis;
    if ( --(pApplet->_nRef) == 0 )
    {
        // デストラクタを呼び出して、operator_deleteで解放
        IModule* pModule = pApplet->_pIModule;
        pApplet->~CApplet();
        getMemoryManager( pModule )->operator_delete( pApplet );

        //CAppletが解放された為、これ以降、deleteを使用してはいけない

        return 0;
    }
    return pApplet->_nRef;
}

このようなります。
これで、CModuleとCAppletがmemory poolから確保されたメモリになり、メモリの確保が好きなように出来るようになります。


ただ、IShell_CreateInstanceによって生成されるインスタンスは、MemoryManagerから領域を取得するわけではないため、NULLチェックをする必要があります。
この問題については後日考えます。




で、最後に1つだけ。


これ、まだコンパイルしてません(;´Д`)


BREWで遊んでると、横から作業を割り振られたので、BREWを触る時間が無くなってしまったんですよ(;つД`)
うまくいったらまた報告します。

*1:char*)module - sizeof( IMemoryManager*

*2:char*)pModule - sizeof( IMemoryManager*