Managing Property SetsIPropertySetStorage Implementation Considerations

IPropertySetStorage 実装の考察

COM プロパティセットフォーマットを読み書きする IPropertySetStorage インタフェースの実装方法について考察する場合、いくつかの問題点があります。 以下のセクションでは、これらの考慮点について記載されています。

IStorage での名前

IPropertySetStorage インタフェースでは、プロパティセットはフォーマット識別子(FMTID)で区別されます。 IStorage インタフェースでは、プロパティセットは最長 32文字の NULL 終端 Unicode 文字列で命名されます。 相互に変換するには、FMTID と対応する NULL 終端 Unicode 文字列の対応を確立しなければなりません。

FMTID から文字列名にプロパティセットを変換する。

FMTID から対応する Unicode 文字列名に変換する場合、まず FMTID が以下の表に挙げられる既知の値である事を確認します。 そうならば、対応する既知の文字列名を使用します。

FMTID文字列名意味
F29F85E0-4FF9-1068-AB91-08002B27B3D9 "\005SummaryInformation" COM2 サマリ情報
D5CDD502-2E9C-101B-9397-08002B2CF9AE
D5CDD505-2E9C-101B-9397-08002B2CF9AE
"\005DocumentSummaryInformation" Office 文書サマリ情報とユーザ定義プロパティ

注意: DocumentSummaryInformationUserDefined プロパティセットは、それらが 2つのセクションを含んでいるという点で独特です。 他のどのプロパティセットも、複数のセクションを持つ事はできません。 プロパティセットの詳細は、構造化ストレージプロパティセットのシリアル化文書サマリ情報とユーザ定義プロパティセットで解説しています。 一つ目のセクションは COM の一部分として意義され、二つ目は Microsoft Office によって定義されています。

FMTID が既知の値でない場合、以下のアルゴリズムで文字列名を形成してください。

  1. 必要なら、FMTID をリトルエンディアンバイト順に変換します。
  2. FMTID の 128ビットを取得し、それをそれぞれのバイトが結合された長いビットの文字列と考えます。 128ビットの先頭ビット値は FMTID のメモリ内で最初のバイトの最下位ビットで、最後のビット値は FMTID のメモリ内で最後のバイトの最上位ビットです。 末尾に二つの 0ビットを追加し、この 128ビットを 130ビットに拡張します。
  3. 130ビットを 5ビット単位、26のグループに分割します。 それぞれのグループが、先頭に反転ビットを伴う整数値である事を考慮します。 たとえば、128ビットの先頭は、最初の 5ビットグループの最下位ビットで、128ビットの 5ビット目は、最初のグループの最上位ビットです。
  4. これらそれぞれの整数をインデックスとして、以下の 32文字の配列にマップしてください。 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345" これは、Unicode の大文字と数字のみを使用した 26文字のシーケンスとなります。 大文字小文字の区別をするかは選択できず、そのためそれぞれの文字はどのようなロケールでも一意となります。
  5. これらの 26文字列の前に "\005" の文字を結合し、最終的に全長 27文字を作成します。

FMTID からプロパティ文字列にマップする一例を以下のコードで示します。


#define CBIT_BYTE        8
#define CBIT_CHARMASK    5
#define CCH_MAP          (1 << CBIT_CHARMASK)    // 32
#define CHARMASK         (CCH_MAP - 1)           // 0x1f

CHAR awcMap[CCH_MAP + 1] = "abcdefghijklmnopqrstuvwxyz012345";

WCHAR MapChar( ULONG i )
{
	return (WCHAR)awcMap[i & CHARMASK];
}
 
VOID GuidToPropertyStringName( GUID* pguid, WCHAR awcname[] )
{
	BYTE* pb = (BYTE*)pguid;
	BYTE* pbEnd = pb + sizeof( *pguid );

	ULONG cbitRemain = CBIT_BYTE;
	WCHAR* pwc = awcname;

	*pwc++ = (WCHAR)0x0005;
	
	while( pb < pbEnd )
	{
		ULONG i = *pb >> (CBIT_BYTE - cbitRemain);
	
		if( cbitRemain >= CBIT_CHARMASK )
		{
			*pwc = MapChar( i );
	
			if( cbitRemain == CBIT_BYTE && *pwc >= L'a' && *pwc <= L'z' )
			{
				*pwc += (WCHAR)(L'A' - L'a');
			}
			
			pwc++;
            cbitRemain -= CBIT_CHARMASK;
	
			if( cbitRemain == 0 )
			{
				pb++;
				cbitRemain = CBIT_BYTE;
			}
		}
		else
		{
			if( ++pb < pbEnd )
			{
				i |= *pb << cbitRemain;
			}
	
			*pwc++ = MapChar( i );
			cbitRemain += CBIT_BYTE - CBIT_CHARMASK;
		}
	}
	
	*pwc = L'\0';
}

プロパティセットを文字列名から FMTID に変換する

プロパティ文字列名の GUID へのコンバータは、それらの大文字の部分が、小文字と同等に扱われなければなりません。

以下の例は、プロパティ文字列から FMTID へマップするための1つの方法を示します。


#define _INC_OLE
#define CBIT_CHARMASK 5
#define CBIT_BYTE     8
#define CBIT_GUID    (CBIT_BYTE * sizeof( GUID ))
#define CWC_PROPSET  (1 + (CBIT_GUID + CBIT_CHARMASK - 1) / CBIT_CHARMASK)
#define WC_PROPSET0  ((WCHAR)0x0005)
#define CCH_MAP      (1 << CBIT_CHARMASK)  // 32
#define CHARMASK     (CCH_MAP - 1)         // 0x1f
#define CALPHACHARS  ('z' - 'a' + 1)

CHAR awcMap[CCH_MAP + 1] = "abcdefghijklmnopqrstuvwxyz012345";
GUID guidSummary         = { 0xf29f85e0, 0x4ff9, 0x1068, { 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9 } };
GUID guidDocumentSummary = { 0xd5cdd502, 0x2e9c, 0x101b, { 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae } };

WCHAR wszSummary[]         = L"SummaryInformation";
WCHAR wszDocumentSummary[] = L"DocumentSummaryInformation";

__inline WCHAR MapChar( IN ULONG i )
{
	return (WCHAR)awcMap[i & CHARMASK];
}

ULONG PropertySetNameToGuid( IN ULONG cwcname, IN WCHAR const awcname[], OUT GUID* pguid )
{
	ULONG Status = ERROR_INVALID_PARAMETER;
	WCHAR const* pwc = awcname;
	
	if( pwc[0] == WC_PROPSET0 )
	{
		// cwcname には WC_PROPSET0 が含まれ、sizeof( wsz... ) には、末尾に L'\0' が含まれますが、
		// 比較するときは、WC_PROPSET0 と、末尾の L'\0' の双方とも、除外します。
		if( cwcname == sizeof( wszSummary ) / sizeof( WCHAR ) && wcsnicmp( &pwc[1], wszSummary, cwcname - 1 ) == 0 )
		{
			*pguid = guidSummary;
			return NO_ERROR;
		}
		if( cwcname == CWC_PROPSET )
		{
			ULONG cbit;
			BYTE* pb = (BYTE*)pguid - 1;

			ZeroMemory( pguid, sizeof( *pguid ) );

			for( cbit = 0; cbit < CBIT_GUID; cbit += CBIT_CHARMASK )
			{
				ULONG cbitUsed = cibt % CBIT_BYTE;
				ULONG cbitStored;
				WCHAR wc;
	
				if( cbitUsed == 0 )
				{
					pb++;
				}
	
				wc = *++pwc - L'A';		//大文字と仮定
	
				if( wc > CALPHACHARS )
				{
					wc += (WCHAR)(L'A' - L'a')	//小文字にしてみる。 
 
                    if (wc > CALPHACHARS)
                    {
                        wc += L'a' - L'0' + CALPHACHARS; //必ず桁合わせを行う。

						if( wc > CHARMASK )
						{
							goto fail;	//無効な文字。
						}
					}
				}

				*pb |= (BYTE)(wc << cbitUsed);
				cbitStored = min( CBIT_BYTE - cbitUsed, CBIT_CHARMASK );
	
				//変換されたビットが現在のビットに合致しない場合。
				if( cbitStored < CBIT_CHARMASK )
				{
					wc >>= CBIT_BYTE - cbitUsed;
	
					if( cbit + cbitStored == CBIT_GUID )
					{
						if( wc != 0 )
						{
							goto fail;	//余分なビット。
						}
						break;
					}
					pb++;
					*pb |= (BYTE)wc;
				}
			}
			Status = NO_ERROR
		}
	}
fail:
	return Status;
}

存在するプロパティセットを(IPropertySetStorage.Open() を使用して)オープンを試みたとき、(ルートの)FMTID は、 上記に示したように文字列が変換されます。 IStorage の1つの要素の名前が存在する場合、それが使用され、存在しない場合は、このオープンは失敗します。

新しいプロパティセットが作成されたとき、上記のマッピングは、文字列名を使用することが選択されます。

プロパティセットでのストレージとストリームオブジェクト

COM プロパティセットの IPropertySetStorage インタフェース を通して完全な内部操作を行うアプリケーションにとって適切なコントロールを提供するには、プログラマはプロパティセットが作成されるときに、ストレージまたはストリーム内のプロパティセットを指定する必要があります。 これは、PROPSETFLAG_NONSIMPLE 列挙値が IPropertySetStorage.Create() メソッドの grfFlags パラメータを通して指定されます。 完全な互換性を提供するアプリケーションによって構築されたプロパティセットを COMプロパティセットの IPropertySetStorage インタフェースを通して設定します。

PROPSETFLAG_NONSIMPLE フラグが設定された場合、プロパティセットはストレージオブジェクトに保存され、非シンプルプロパティの値をそこに書き込むことが出来ます。 非シンプル値には、VT_STORAGE, VT_STREAM, VT_STORED_OBJECT または VT_STREAMED_OBJECTVARTYPE が含まれます。 PROPVARIANT 構造体の VARTYPE 値とそれらの使用方法についての詳細を参照してください。

PROPSETFLAG_NONSIMPLE フラグが設定されなかった場合、シンプル値のみをプロパティセットに書き込むことが出来ます。

非シンプルプロパティセットのストレージオブジェクトでは、ストリームオブジェクトは Contents と名づけられ作成されます。 これは、プロパティセットのプライマリストリームで、全てのシンプルプロパティ値を保持します。 (ストリームとストレージの)非シンプルプロパティ値は、サブストリームやストレージとして、プロパティセットのメインストレージオブジェクトの下に保存されます。 このことは、これらの非シンプル値は、Contents ストリームの兄弟として保存されることを意味します。 兄弟ストリームとストレージの名前は、実装によって決定され、シンプル文字列プロパティが保存されるのと同様に、Contents ストリームに保存されます。

プロパティセットクラス識別子の設定

IPropertyStorage.SetClass が、構造化ストレージオブジェクトに保存された非シンプルプロパティストレージから呼び出されたとき、IStorage.SetClass() の呼び出しを通してストレージオブジェクトの CLSID と、COM プロパティセットに保存されているクラスタグ値の双方を設定します。 これは、構造化ストレージオブジェクト内の非シンプルプロパティストレージを呼び出された際に起こります。

これは、さまざまなツールによりよい相互作用となる一貫性と一様性を提供します。 プロパティストレージオブジェクトは、 IPropertySetStorage.Create()PROPSETFLAG_NONSIMPLE タグを設定して作際された際に、非シンプルとなります。

このストレージオブジェクトの CLSID は、IStorage::SetClass の呼び出しによって設定されます。

同期ポイント

プロパティセットが IStorage と同じオブジェクトとしてサポートされる場合、基底となるストレージとプロパティストレージ間で、同期したポイントであることが重要です。

このプロパティセットストレージは、バッファが IPropertyStorage::Commit メソッドを通してコミットされるまでの間、内部バッファにプロパティセットストリームを保持します。 これは、IPropertyStorage がトランザクションモードで開かれているかダイレクトモードで開かれているかにかかわらず、真です。

コードページと Unicode 文字列

IPropertySetStorage 別の実装としては、property ID 0 (プロパティ名辞書) に格納された、Unicode文字列を使用していない Unicode プロパティ名についてです。

Unicode は正式には、コードページ 1200 です。 Unicode 値をプロパティ名辞書に格納するには、IPropertySetStorage::Create に PROPSETFLAG_ANSI フラグが不足させることで、すべてのプロパティセット ( property ID 1 ) から 1200 のコードページ値を使用します。 このことで、格納されるすべてのプロパティの文字列の値対して Unicode の影響があることに注意してください。 すべてのコードページでは、VT_LPSTR で開始される文字列の値は、文字数ではなくバイト数です。 これは、初期バージョンでクライアントとの互換性を提供する際に必要です。

IPropertySetStorage の複合ファイル実装は、すべての新しいプロパティセットは、必ず Unicode (コードページ 1200) または現在のシステムの ANSI コードページで作成されます。 これは、IPropertySetStorage::Create の grfFlags パラメータの PROPSETFLAG_ANSI フラグの有無でコントロールされます。

Unicode でプロパティセットが 作成され開かれる場合。 IPropertySetStorage::Create の grfFlags パラメータに PROPSETFLAG_ANSI フラグを指定しないことで実装されます。 VT_LPSTR 値の使用を避け、代わりに VT_LPWSTR 値を使用します。 プロパティセットのコードページが Unicode の場合、VT_LPSTR 文字列値は、格納される際に Unicode に変換され、検索された際にマルチバイト文字列が返ります。

IPropertyStorage::Stat の呼び出しを通してとして PROPSETFLAG_ANSI フラグが設定された場合、基本となるコードページが Unicode であるかないかにかかわらず反映されます。 コードページを知るために、 Property ID 1 を明示的に読み取ることに注意してください。

IPropertyStorage::ReadMultiple を呼び出すことで、Property ID 1 にアクセスすることができます。 しかし、これは読み取り専用で、WriteMultiple による更新はできません。 さらに、DeleteMultiple で削除することもできません。

ディクショナリ

IPropertyStorage::WritePropertyNames は、Property ID 0 辞書を使用する実装です。 Property ID 0 は、IPropertyStorage::ReadMultiple または IPropertyStorage::WriteMultiple を通してアクセスすることはできません。

拡張子

オリジナルの COM プロパティセットフォーマットによって定義されたプロパティセット拡張子は、DocumentSummaryInformation と ユーザ定義プロパティセット以外は削除されてサポートされません。

プロパティセットのシリアル化

プロパティシリアル化フォーマットには2つのバリエーションがあります。 オリジナルとして、バージョン 0 フォーマットが定義されています。 詳細については、See Format Version を参照してください。 オリジナルのバージョンに Version 1 が拡張されます。 すべての version 0 プロパティセットは、version 1 プロパティセットで有効です。 シリアル化プロパティセットのヘッダのフォーマットバージョンフィールドに、バージョンが表記されています。

以下のアイテムは、バージョン0とバージョン1プロパティセットのシリアル化フォーマット間の違いについて記述しています。

追加として、SafeArrays は、プロパティセット内にシリアル化することができます。 配列要素において、PROPVARIANT の vt メンバーで、 OR オペレータを使用して VT_ARRAY ビットの組み合わせることで、SafeArray の存在を表します。 たとえば、4 バイト符号付き整数の SafeArray は、VT_ARRAY | VT_I4 となります。

以下の VARTYPE 値は、バージョン 0 プロパティセットではサポートされていませんが、 バージョン 1 ではサポートされています。

VT_I1VT_UI1VT_I2VT_UI2
VT_I4VT_UI4VT_INTVT_UINT
VT_R4VT_R8VT_CYVT_DATE
VT_BSTRVT_BOOLVT_DECIMALVT_ERROR
VT_VARIANT

VT_VARIANT データ型が指定される場合、PROPVARIANT 構造体を保持した SafeArray であることを表します。 これらの要素のための型は、入れ子で VT_VARIANT 型を含むことができない以外は、前述のリスト通りとなります。

註:この IPropertyStorage 実装では、新しい型に遭遇した際にエラーを返す場合に、即座に復帰することができます。

IPropertyStorage 実装では、標準として version 0 プロパティセットを使用して聖戦と管理を行うことが推奨されます。 呼び出し元が、続けて version 1 フォーマットを指定して特徴を要求した場合、プロパティセットのバージョンのみが更新されます。 たとえば、プロパティに VT_ARRAY 型が書き込まれたり、長いプロパティ名が書き込まれた場合、この実装では、プロパティセットが version 1 フォーマットに更新されます。 このガイドラインの例外の一つとしては、PROPSETFLAG_CASE_SENSITIVE 列挙値が IPropertySetStorage::Create の呼び出しで指定された場合です。 上記の場合、プロパティセットは version 1 プロパティセットとして生成されます。