I can create the IPropertySetStorage
and IPropertyStorage
interfaces using known FMTIDs (FMTID_SummaryInformation
, FMTID_DocSummaryInformation
, FMTID_UserDefinedProperties
), but I cannot create my own FMTID file name. According to MSDN, the FMTID is the name of the stream that contains the properties within a set. Simply changing the stream's name would create a mismatch because IPropertySetStorage::Create(...)
and IPropertySetStorage::Open(...)
only uses the FMTID/GUID, which is internally translated to the file name. The common FMTIDs mentioned above are recognized, even though they would not translate to the appropriate file names if you were somehow able to force the algorithm to process them.
I worked out the problem, but I will still post the question since IStorage
, IPropertySetStorage
and IPropertyStorage
do not currently have much useful search hits on StackOverflow or the Internet with respect to how to create your own FMTIDs using text. The algorithm on MSDN is interesting, but it leaves out some critical details, which I hope will be explained here.
For a success(hr)
, these conditions must be met:
the file name must start with
\005
the file name must be 27 characters (this includes the
\005
)the other 26 characters must be
a-z
,A-Z
,0-5
the last character
fname[26]
must bea-h
orA-H
Why? According to the algorithm on MSDN, the GUID is broken down to 128bits + 2 bits, subdivided into 5 bits. The 5 bits allows 32 possible characters per every 5 bits. The algorithm also explains that 130bits / 5bits gives you 27 characters to generate a filename (critical note: 128bits / 5bits gives you 26.6 characters, the last character of the file name is limited). So, 00000
is 'a'
, 00001
is 'b'
, and so on. When the algorithm gets to the last character to process, there are 2 extra bits. If the last character is not 00xxx
then the algorithm fails. Thus, the last character must be (00000 = a
) to (00111 = h
). Somehow, internally, the algorithm and reverse algorithm can process upper and lower case letters so A-H
are also acceptable as the last character and a-z
, A-Z
and 0-5
are acceptable as the first 25 letters in the filename. In summation, the algorithm actually gives you 25 5-bit characters and a partial 3-bit character to work with for a FMTID file name.
To ensure your file name is acceptable for the algorithm, you can condition the fname like this:
HRESULT hr1 = S_OK;
HRESULT hr2 = S_OK;
FMTID my_fmtid1 = GUID_NULL;
FMTID my_fmtid2 = GUID_NULL;
DWORD grfMode_new = (STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE);
DWORD grfMode_open = (STGM_READWRITE | STGM_SHARE_EXCLUSIVE);
DWORD grfFlags = PROPSETFLAG_DEFAULT;
WCHAR fmtid_fname1[28] = L"\005abcdefghijklmnopqrstuvwxy0\0";
WCHAR fmtid_fname2[28] = L"\005QWERTYUIOPASDFGHJKLZXCVBNM\0";
// condition 1: the file name must start with \005
fmtid_fname1[0] = L'\005';
// condition 2a, 2b: the file name must be 27 characters (this includes the \005)
// I chose 'z' but the appended characters can be a-z, A-Z, 0-5
StringCbCatW(fmtid_fname1, sizeof(fmtid_fname1), L"zzzzzzzzzzzzzzzzzzzzzzzzzz");
// condition 2c: the last character fname[26] must be a-h or A-H
// I chose 'a' as my preferred last character
fmtid_fname1[26] = L'a';
// revisit condition 2a: the file name must be 27 characters (this includes the \005)
fmtid_fname[27] = 0;
// at minimum the fname will be something like L"\005zzzzzzzzzzzzzzzzzzzzzzzzza"
hr1 = PropStgNameToFmtId(fmtid_fname1, &my_fmtid1);
hr2 = PropStgNameToFmtId(fmtid_fname2, &my_fmtid2);
if (my_fmtid1 == GUID_NULL) { /* recondition_your_fname1_message */ }
if (my_fmtid2 == GUID_NULL) { /* recondition_your_fname2_message */ }
// According to MSDN: the clsid should be the same as the fmtid
hr1 = pPropSet->Create(my_fmtid1, &my_fmtid1, grfFlags, grfMode_new, &pPropStrgGoup1);
hr2 = pPropSet->Create(my_fmtid2, &my_fmtid2, grfFlags, grfMode_new, &pPropStrgGoup2);
...now you've created two property set files using your own home brew file name scheme!