2005年11月21日

C#とNTFSストリームの甘くない関係

毎度すっかり更新がおろそかになっています。

今回はすぐコードというわけではないですが、NTFSストリームを調べる方法の考察。

NT系で採用されているファイルシステムNTFSには、ストリームという概念があります。一つのファイルに対して複数の情報を持たせることができる仕組みです。ファイルのプロパティの概要タブで色々情報を書き込むことができますが、これはNTFSストリームを利用して実現されています。

ストリームへのアクセスは、[ファイル名]:[ストリーム名]で行います。例えば、hoge.txtにUserストリームを追加しそれにHongliangと書き込むには、コマンドプロンプトで

echo Hongliang> hoge.txt:User
と書くだけでも可能です。echoコマンドで出力されるHongliangをhoge.txt:Userというファイルにリダイレクトで書き出すわけですね。ただし、typeコマンドはNTFSストリームには対応していないようなので、より一般的にはWSHでFileSystemObjectを使用して読み書きするのが良いでしょう。普通にファイルのように扱えます。難点は、あるファイルにどんなストリームが存在するかを確認する手段がないっぽいことです。

APIプログラミングには、二通りのアクセス方法があるようです。一つはBackupRead/BackupWrite関数を使うやり方。もう一つはIPropertySetStorageというCOMインターフェイスを中心としたやり方です。

このうち、BackupRead系は、比較的単純です。ファイルに存在する全てのストリームが並べられ、それぞれのストリームにヘッダが用意され、プログラマはそのヘッダを利用してストリームを判断することになります。しかし、これを使った場合、先に挙げた「ファイルのプロパティの概要タブで色々書き込まれた情報」にはまともにアクセスできません。なぜならそれがバイナリデータになっているからです。

もう一つのIPropertySetStorageインターフェイス絡みの方にはもっと頭の痛い問題があります。何故かC#で定義したIPropertySetStorageのメソッドが巧く動かないのです。StgOpenStorageEx関数を使用してIPropertySetStorageを取得します。これは問題ありません。じゃあプロパティを列挙しようとEnumを呼ぶと、out IEnumSTATPROPSETSTGで定義した場合は「フラグが無効です(STG_E_INVALIDFLAG)」と言う例外が、またout objectの場合は「値が有効な範囲にありません」と言う例外が、unsafeでIntPtr*を指定した場合も「 が見つかりませんでした(STG_E_FILENOTFOUND)」という例外が投げられるんです。これには困り果てました。なにせ等価の(はずの)コードをC++で書くと問題なく成功するのですから。ん〜、なにかCOMインターフェイスの宣言に不味いところがあるんでしょうか……。PreserveSig属性をつけたらAccessViolationExceptionが出るようになるし。

さて、どうしようかな……素直にManaged C++で書くか?

その後色々と勉強して、不可能ではないことが分かりました。で、 記事に纏めた ので、そちらを参照してください。

public class NtfsStream {
  [DllImport("ole32.dll", CharSet=CharSet.Unicode)]
  public static extern int StgOpenStorageEx(
      [MarshalAs(UnmanagedType.LPWStr)] string fileName,
      STGM mode, int format, int reserved1, IntPtr options,
   IntPtr reserved2, ref Guid guid,
      [Out, MarshalAs(UnmanagedType.Interface)] out IPropertySetStorage
                             openedObject);

  [ComImport, Guid("0000013A-0000-0000-C000-000000000046"),
   InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface IPropertySetStorage {
    int Enum(
      [MarshalAs(UnmanagedType.Interface)] out IEnumSTATPROPSETSTG
                             propSetStg);
  }

  [ComImport, Guid("0000013B-0000-0000-C000-000000000046"),
   InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface IEnumSTATPROPSETSTG {
  }

  [Flags] public enum STGM {
    Read            = 0x00000000,
    Write           = 0x00000001,
    ReadWrite       = 0x00000002,

    DenyNone        = 0x00000040,
    DenyRead        = 0x00000030,
    DenyWrite       = 0x00000020,
    Exclusive       = 0x00000010,
    Priority        = 0x00040000,

    Create          = 0x00001000,
    Convert         = 0x00020000,
    FailIfThere     = 0x00000000,

    Direct          = 0x00000000,
    Transacted      = 0x00010000,

    NoScratch       = 0x00100000,
    NoSnapshot      = 0x00200000,

    Simple          = 0x08000000,
    DirectSwmr      = 0x00400000,
    DeleteOnRelease = 0x04000000,
  }

  [STAThread] public static void Main(string[] args) {
    Guid id = new Guid("0000013A-0000-0000-C000-000000000046");
    IPropertySetStorage pss;
    IEnumSTATPROPSETSTG stg;
    StgOpenStorageEx(args[0], STGM.ReadWrite | STGM.Exclusive, 3, 0, 
                Ptr.Zero, IntPtr.Zero, ref id, out pss);
    if (pss != null) {
      try {
        Console.WriteLine(pss.Enum(out stg));
      }
      finally {
        if (stg != null)
          Marshal.ReleaseComObject(stg);
        if (pss != null)
          Marshal.ReleaseComObject(pss);
      }
    }
  }
}

posted by Hongliang at 19:49| Comment(0) | TrackBack(1) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

リンクしました。
Excerpt: リンクしました。
Weblog: 中の技術日誌ブログ
Tracked: 2005-11-25 12:19

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。