FMO/Mutex

FMO/Mutex

参考:メモリオブジェクト(外部サイト)

現在のところ本仕様はWindowsの仕様に大きく依存しており、他OSでの実装を想定していません。
共有メモリ・Mutex自体は他OSにも存在するため、別途仕様を規定することが望ましいと思われます。
もし仕様が決定した場合は別途掲載を考えておりますので、Ukadoc Projectまでご連絡ください。

Mutex

ベースウェアが起動中であることを示すため、SSPは"ssp"、MateriaとCROWは"sakura"という名前のMutexを保持しています。
この名前つきMutexの存在を確認することで、起動中かどうかをローコストに判定できます。
Mutex自体の状態は決められておらず、シグナル状態か否かの確認は不要です。

存在確認のみのコード例(C++)

HANDLE hmutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"ssp");
if ( hmutex ) {
  // いる
  CloseHandle(hmutex);
}
else {
  //いない
}

FMO (File Mapping Object)

すべてのベースウェアは起動中に名前つきファイルマッピングオブジェクト(FMO)を保持しています。
このプロセス間共有メモリを読み取ることで、起動中のゴーストを比較的低コストで取得できます。
書き込み中の不完全な情報の読み取りを避けるため、排他制御はFMO用Mutexで行っています。あわせて確認してください。

FMOの名前と文字コード

Sakura : OS依存、日本語OS上はShift JIS
SakuraUnicode : UTF-8固定 [SSP 2.5.26以降~]

FMOのサイズ

最初の4バイト(0~3バイト目)は確保されているFMOのサイズを示します。
これは書き込まれている情報の長さではなく、あくまでもFMO自体の確保サイズを示す固定値です。
値は現在のところリトルエンディアンで0x00010000、つまり64KB固定になります。
他プログラムとの互換性確保のため、現在のところサイズ変更は考慮されていません。

データ本体

4バイト目以降はFMOのデータ本体になります。これは以下のフォーマットになります。
(32バイトの識別ID).(キー名)[\1]値[\r\n]
[\1]はバイト値1、[\r\n]はCR+LF(改行)になります。
このフォーマットを複数行繰り返す形になります。

データ終端

データ終端はC言語文字列と同じバイト値0になります。
ゆえに、データ本体で使用可能な最大サイズは65531バイト(65536 - 4 - 1)になります。
FMOに書き込む必要がある場合、サイズの限界を超えそうな場合は、不完全な情報の書き込みにならないよう留意してください。
サイズを超えそうな場合は1組分のデータを丸々書き込まないようにするのが望ましく、それが無理な場合でも1行の途中で終わらないようにしてください。

データ本体の例

ssp_fmo_header_00004468_000f0dea.path[\1]D:\ssp\
ssp_fmo_header_00004468_000f0dea.hwnd[\1]986602
ssp_fmo_header_00004468_000f0dea.name[\1]ラーシェ
ssp_fmo_header_00004468_000f0dea.keroname[\1]ティセ
ssp_fmo_header_00004468_000f0dea.sakura.surface[\1]0
ssp_fmo_header_00004468_000f0dea.kero.surface[\1]10
ssp_fmo_header_00004468_000f0dea.kerohwnd[\1]1052114
ssp_fmo_header_00004468_000f0dea.hwndlist[\1]986602,1052114
ssp_fmo_header_00004468_000f0dea.ghostpath[\1]D:\ssp\ghost\DE10_3001\
ssp_fmo_header_00004468_00120da6.path[\1]D:\ssp\
ssp_fmo_header_00004468_00120da6.hwnd[\1]1183142
ssp_fmo_header_00004468_00120da6.name[\1]Emily
ssp_fmo_header_00004468_00120da6.keroname[\1]Teddy
ssp_fmo_header_00004468_00120da6.sakura.surface[\1]20
ssp_fmo_header_00004468_00120da6.kero.surface[\1]10
ssp_fmo_header_00004468_00120da6.kerohwnd[\1]1117626
ssp_fmo_header_00004468_00120da6.hwndlist[\1]1183142,1117626,921002,2035340,658950
ssp_fmo_header_00004468_00120da6.ghostpath[\1]D:\ssp\ghost\emily4\
32バイトの識別ID

ゴースト1組分を示すユニークな識別IDです。少なくともFMO内では他と重複しない文字列を選ぶ必要があります。
何かユニークな情報を組み合わせてMD5ハッシュを取ったり、HWND(ウインドウハンドル)を組み合わせたりしている場合が多いです。
Materiaでの規格上は長さは規定されていませんが、互換性のため32バイト固定長とすべきです。

キー名・値

情報の種類を示すキーと情報本体です。以下の通りです。

path
起動しているベースウェアのルートフォルダのフルパス。
hwnd
メインとなるウインドウのウインドウハンドル。10進表記。
name
descript.txtのsakura.nameと同じもの。
keroname
descript.txtのkero.nameと同じもの。
sakura.surface
\0側で現在表示しているサーフィスID。10進表記。
kero.surface
\1側で現在表示しているサーフィスID。10進表記。
kerohwnd
\1側ウインドウのウインドウハンドル。10進表記。[SSPのみ]
hwndlist
現在使用しているウインドウハンドルをすべてカンマ区切りで列挙。10進表記。[SSPのみ]
ghostpath
起動中ゴーストのフルパス。[SSPのみ]

FMO用Mutex

FMO自体に排他制御の仕組みはないため、書き込み・読み取りの衝突を避けるために、別途Mutexを保持しています。
FMO名+"FMO"という名前になり、例えば以下のようになります。
FMO = Sakura : Mutex = SakuraFMO
FMO = SakuraUnicode : Mutex = SakuraUnicodeFMO
こちらはシグナル状態・非シグナル状態の判定が重要になります。
読み書きを行う際は、必ずWaitForSingleObjectや同等の待機関数を使い所有権を取得し、終わったらReleaseMutexで所有権を開放してください。

旧いベースウェア等でFMO用Mutexに対応していない場合があるので、Mutexの取得に失敗してもエラーにはしないでください。
ただしこの場合は、不完全な書き込み中のFMOを取得する可能性を考慮して、できるだけ安全に処理できるコードを書くよう留意してください。

FMOを読み書きするコード例(C++)

//ベースウェア等の保持すべきアプリは代わりにCreateMutexを使う
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,"SakuraFMO");

//FMO用Mutexに対応していないベースウェアもあるので、見つからなかったら単にスキップ
bool isWaitSuccess = true;

if ( hMutex ) {

    //INFINITEで待機すると永遠に待ち続けてGUIが止まるので適宜工夫すること
    DWORD result = WaitForSingleObject(hMutex,INFINITE);
    
    if ( result != WAIT_OBJECT_0 ) {
        isWaitSuccess = false;
    }
}

if ( isWaitSuccess ) {

    //保持すべきアプリは代わりにCreateMutexを使う
    HANDLE hFMO = OpenFileMapping(FILE_MAP_ALL_ACCESS,FALSE,"Sakura");

    if ( hFMO ) {

        char *pDataStart = static_cast<char*>(MapViewOfFile(hFMO,FILE_MAP_ALL_ACCESS,0,0,0));

        if ( pDataStart ) {

            //頭の4バイトはFMO最大サイズ。
            //文字列終端(C言語文字列のゼロ終端)とは異なるので注意。
            unsigned long length = *reinterpret_cast<unsigned long*>(pDataStart);

            char *pData = pDataStart;
            pData += 4;

            //****************************************
            //ここでpDataとlengthをつかってなにかやる
            //****************************************

            //MapViewOfFileの解除
            UnmapViewOfFile(pDataStart);
        }
            
        //FMOハンドルを開放
        //ベースウェア等の保持すべきアプリは開放せず持ち続けること
        CloseHandle(hFMO);
    }
}

if ( hMutex ) {
    if ( isWaitSuccess ) {
    
        //WaitForSingleObjectでMutexが非シグナル状態になるので、シグナル状態に戻す(Release)
        ReleaseMutex(hMutex);
    }

    //最後にMutexのハンドルも要らないので開放
    //ベースウェア等の保持すべきアプリは開放せず持ち続けること
    CloseHandle(hMutex);
}