今回は、C#とNTFSストリームの甘くない関係 のアップデートを行います。ただし、IPropertSetStorage を使ったアクセスは自由度が低いと感じたため、ITaskScheduler みたいなきっちりしたコードではなく、飽くまで「こうすれば動く」程度にとどめたいと思います。
以前の記事では COM オブジェクトが例外出しまくりで何でさ、で終了でしたが、ちょっと COM の知識が増えた今では何が問題だったのかは明らかです。大雑把に言って、COM オブジェクトは IUnknown または IDispatch から派生しますが、IDispatch は名前でメソッドを検索するのに対し、IUnknown は 先頭からの宣言順で検索します。つまり、以前書いたコードの IPropertySetStorage は宣言が Enum メソッドしかないため、IUnknown 的にはこれは Create メソッド相当なんですね。当然引数が全く違うのでメソッドは失敗していたってわけです。
メソッドの宣言順が大事なのであって宣言の正当性はわりとどうでも良いので、以前書いた IPropertySetStorage インターフェイスは以下のように書き換えれば Enum が成功するようになります。
[ComImport, Guid("0000013A-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertySetStorage {
int Create();
int Open();
int Delete();
[PreserveSig] int Enum(
[MarshalAs(UnmanagedType.Interface)]
out IEnumSTATPROPSETSTG propSetStg);
}
}冒頭に述べた「自由度が低い」と言うのは何か。IPropertySetStorage はあるストリームの中に複数のデータを書き込む為のインターフェイスであるというのがわかりやすいですが、次のような問題が見つかりました。
- IPropsertySetStorage で書き込んだ内容は IPropsertySetStorage を使わないとまともに読めない。
- ストリーム名を任意には設定できない。ストリームの特定には GUID を使用するので厄介である。
- IPropsertySetStorage で作成したストリームには独自の命名規則か何かがあるようで、それに従っていないストリームは IPropsertySetStorage から認識されない。
解決できる問題もあるかも知れませんが、私には敢えて解決を探すほどのものとも思えませんでした。ごく普通のファイルとして扱えた方が嬉しいですし、それならむしろ CreateFile API 関数とかを使って開いた方が色々便利ですので。
それでもファイルのプロパティの概要タブに出てくる各項目を取得設定するというのが魅力的な操作と言うのも分かります。この部分に関しては、Microsoft が配布している Microsoft Developer Support OLE File Property Reader 2.0(Dsofile.dll)という COM が存在し、これを使えば簡単にアクセスできるようになります。私自身はいじっていないのですが、Interop.Dsofile.dll を見る限り、基本的な二つのストリームの他にカスタムストリームも扱えるようです。
そう言うわけで深入りする気が無くなってしまったので、ファイルのプロパティの「タイトル」に値を設定して読み込む、と言うだけのサンプルを作って終了と言うことにします。
using System; using System.Runtime.InteropServices; [ComImport, Guid("0000013A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IPropertySetStorage { int Create(); [PreserveSig] int Open( [MarshalAs(UnmanagedType.Struct), In] ref Guid formatId, [MarshalAs(UnmanagedType.I4)] int mode, [MarshalAs(UnmanagedType.Interface)] out IPropertyStorage storage); int Delete(); int Enum(); } [ComImport, Guid("00000138-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IPropertyStorage { [PreserveSig] int ReadMultiple( [MarshalAs(UnmanagedType.I4)] int count, [MarshalAs(UnmanagedType.LPArray), In] PropSpec[] specs, [MarshalAs(UnmanagedType.LPArray), Out] PropVariant[] variant); [PreserveSig] int WriteMultiple( [MarshalAs(UnmanagedType.I4)] int count, [MarshalAs(UnmanagedType.LPArray), In] PropSpec[] specs, [MarshalAs(UnmanagedType.LPArray), In] PropVariant[] variants, [MarshalAs(UnmanagedType.I4)] int nameFirstId); int DeleteMultiple(); int ReadPropertyNames(); int WritePropertyNames(); int DeletePropertyNames(); int Commit(); int Revert(); int Enum(); int SetTimes(); int SetClass(); int Stat(); } public struct PropSpec { public int Kind; public int Id; } public struct PropVariant { public short Type; public short Reserved1, Reserved2, Reserved3; public long Value; } public class Entry { [DllImport("ole32.dll", CharSet=CharSet.Unicode)] public static extern int StgOpenStorageEx( string name, int mode, int format, int attributes, IntPtr options, IntPtr reserved, [In] ref Guid iid, out IPropertySetStorage opened); [DllImport("ole32.dll")] private static extern int PropVariantClear([In] ref PropVariant variant); [STAThread] public static void Main() { string file = @"c:\hoge.txt"; // IPropertySetStorage の GUID Guid guid = new Guid("0000013A-0000-0000-C000-000000000046"); IPropertySetStorage storageSet; int result; // IPropsertySetStorage を作成。 // 0x12 は読み書き許可・後続のオープン禁止。 // 3 はファイルを開く指示(NTFS のファイル以外の場合失敗)。 result = StgOpenStorageEx(file, 0x12, 3, 0, IntPtr.Zero, IntPtr.Zero, ref guid, out storageSet); // 失敗していたら例外を投げる。成功していたら何も無し。 Marshal.ThrowExceptionForHR(result); try { IPropertyStorage storage; // FMTID_SummaryInformation の GUID Guid fmt = new Guid("f29f85e0-4ff9-1068-ab91-08002b27b3d9"); // SummaryInformation のストリームを取得。 // 0x12 は読み書き許可・後続のオープン禁止。 result = storageSet.Open(ref fmt, 0x12, out storage); Marshal.ThrowExceptionForHR(result); try { // 読み書きする項目の指定 // Kind は Id が文字列か数値かを表す(1 = 数値)。 // Id は項目の ID(2 = SummaryInformation ならタイトル)。 PropSpec[] specs = new PropSpec[1]; specs[0].Kind = 1; specs[0].Id = 2; // 書き込む内容 // Type は Value の種類。VarEnum と一致するが、こっちは short。 // SummaryInformation では文字列は LPSTR(ANSI)が標準らしい。 PropVariant[] variants = new PropVariant[1]; variants[0].Type = (short)VarEnum.VT_LPSTR; IntPtr ptr = Marshal.StringToCoTaskMemAnsi("タイト"); try { variants[0].Value = ptr.ToInt64(); // 書き込み。 // 1 は書き込む数。一回で纏めて複数項目を書き込める。 // 第四引数は項目を文字列で指定した場合に影響するっぽい。 result = storage.WriteMultiple(1, specs, variants, 0); Marshal.ThrowExceptionForHR(result); } finally { Marshal.FreeCoTaskMem(ptr); } // 読み込み。 // 読み込む項目 specs は書き込んだときに使ったのをそのまま流用。 // variants に読み込まれる。 result = storage.ReadMultiple(1, specs, variants); Marshal.ThrowExceptionForHR(result); try { // 読み込んだ PropVariant のポインタから文字列を取得。 // 書き込み直後で同じ項目だから ANSI 文字列に決め撃ちだが、 // 普通は PropVariant.Type で細かく場合分けしないといけない。 ptr = new IntPtr(variants[0].Value); Console.WriteLine(Marshal.PtrToStringAnsi(ptr)); } finally { // 使い終わった PropVariant は解放。 for (int i = 0; i < variants.Length; i++) PropVariantClear(ref variants[i]); } } finally { if (storage != null) Marshal.ReleaseComObject(storage); } } finally { if (storageSet != null) Marshal.ReleaseComObject(storageSet); } } }


……と確認してみたところ、SummaryProperties の書き込み可のプロパティは全部の半分ぐらいですが、null だったやつにも問題なく書き込めましたよ?
CustomProperties の方も問題なく Add できるようですし。
手動で書き込んでからでないと、
dsofile.dllを使うにしても、このAPIを使うしても
コードで、設定できません
なにか打開策はありますか?
[PreserveSig] int Create(
[MarshalAs(UnmanagedType.Struct), In] ref Guid formatId,
[MarshalAs(UnmanagedType.Struct), In] ref Guid clsId,
[MarshalAs(UnmanagedType.I4)] int flags,
[MarshalAs(UnmanagedType.I4)] int mode,
[MarshalAs(UnmanagedType.Interface)] out IPropertyStorage storage);
IPropertySetStorage::Create はこんな感じなので、IPropertySetStorage::Open で 0x80030002 が返ってきた時に
storageSet.Create(ref fmt, ref fmt, 0x0, 0x1012, out storage);
を呼び出せば何とかなるかと思わなくもないです。
本気でやりたいなら C++ の利用をお薦めします。
C# では VARIANT の扱いが面倒すぎます。
createを、用いてやってみましたが、
今度は保護されているメモリにアクセスするなとの
ことでした。OSではなくアプリケーションでは、
本来ふみこんではいけない領域なのかもしれないです
C++を勉強してCOM参照でやってみます
どうもありがとうございました