NTFS多流文件、结构化存储和摘要属性集合

本文介绍了一种利用NTFS文件系统的多流特性进行文档管理的方法,这种方法能够方便地记录和检索文件摘要信息,同时探讨了如何通过编程手段读取和修改这些摘要数据。

原文地址:http://www.cnblogs.com/neoragex2002/archive/2007/07/25/831393.html

Question:首先是一个简单的应用问题。我曾经看过无数的文献。其实看文献无非就是为了归类和总结,如果看了不做总结,便相当于白看。可是做总结是个令人烦恼的过程。写纸上吧,不太好整理;以注释形式写在pdf里一并保存吧,那要查的时候还得一个个pdf打开了去找,多麻烦;用ReferenceManager或者是excel吧,那在复制文件的时候,我还得记得顺带copy一份xls或索引数据文件呢,同时还得在可能使用的每台机器上都装上烦人的索引管理软件,这简直令人如鲠在喉。有啥现成的整理文档的好办法没有?

Answer:用Windows自己提供的 NTFS文件属性页来进行文档整理最简单,这是我个人的一点小经验:)不管是何种类型的文件,都可以直接使用"文件属性"中的摘要页来记录注释,填起来也 方便快捷,同时也便于查看(用"详细信息"方式查看,然后右键勾选一下显示字段即可);而且,如果使用的是NTFS的话,这些注释是跟着文件跑的,即,在 copy/cut文件的同时,这些信息也将被同时复制/剪切,这样便省事多了!

summary.jpg

Question:OK,这个方法够简单。它有什么使用限制没有?还有,这些摘要数据究竟是存储在哪里的呢?它们不是保存在文件正文当中吗?或者,它们仅只是些文件的扩展属性而已?

Answer:唯一的限制就是这些添加了注释的文件必须保存在NTFS文件系统中。因为这些摘要信息是以NTFS文件的多流(Alternate Stream) 形式存储的,其并不保存在文件正文当中。这里简单介绍一下多流概念,它是NTFS文件系统的一个高级特性;在传统的文件系统中(如FAT32),文件的内 容被抽象成为一个单一的数据流及其相应的读写指针,其好处是足够简单,但坏处便是不利于单一文件的读写共享,因为多个组件可能需要同时读写同一份文件,这 一问题其实很普遍,打个比方,有个doc文件,里面保存了文字内容和一个visio对象,用word打开它,然后双击visio图并开始编辑,实际上,此 时有两个组件在同时访问这个doc文件,一个是word,一个是visio,为了读取doc中的文字内容及visio对象内容,必须考虑其单一文件指针的 正确定位,否则就会乱套,因为此时文字数据和visio对象数据是存放在同一个数据流当中的。但在NTFS中,这事便好办了,因M$对NTFS中的文件数 据流概念进行了扩充,一份NTFS文件可以包含"多个"数据流,而每个数据流不仅可以单独命名,而且还拥有自己的指针和安全配置,甚至还可以进行独立的压 缩保存,这相当于一个NTFS文件里面又可以保存着多个通常意义上的子文件。这下我们保存、共享读取这个doc便方便了,直接将文字内容和visio对象 分别存为同一份文件的不同流即可,连读写同步的问题也解决了。

其实,上面这个例子便是COM中一个关于结构化存储(Structured Storage)和复合文档(Compound Documents) 的典型示例,而其技术动机正是为了解决单一复杂文档的读写共享问题,而且,这里我连其在NTFS系统中的底层实现也一并解释了:即,采用NTFS所提供的 多数据流特性,可以极大简化结构化存储及其复合文档技术的实现;其实在很大程度上,正是因为结构化存储技术提出了文件读写共享的需求,M$才专门在 NTFS中引入了多流特性的。

再回到我们的问题上来,便不难找到答案了:在NTFS文件系统中,额外的文件摘要信息均将以"串行化"(这个是通俗的比喻)的方式保存在文件的另外 一个数据流中,但并不跟其文件的正文处于同一个数据流。当我们将多流文件copy至FAT32文件系统上时,windows将会提示流数据丢失,因为 FAT32只支持单一的文件数据流,它只能保存一份文件正文的copy。说到这里,其实用FAT32(比方说U盘)的筒子们也不必过于担心,因为称手的WinRar已经考虑了多流拷贝丢失的情况:看下面的压缩选项,在勾选状态下,NTFS上被压缩文件的多流信息将会一并压缩到rar文件中。不过,当然,如果想读取多流的话,还是得将文件解压缩到NTFS盘上才行。

winrar.jpg

以下是一个查看NTFS文件多流信息的示例程序。

None.gif #include  " stdafx.h "
None.gif
None.gif #define  WIN32_LEAN_AND_MEAN
None.gif
None.gif#include  < windows.h >
None.gif#include  < ole2.h >
None.gif#include  < assert.h >
None.gif
None.gifWCHAR *  StreamType[] =
ExpandedBlockStart.gif {
InBlock.gif    L"Data",        L"ExternalData",
InBlock.gif    L"SecurityData",L"AlternateData",
InBlock.gif    L"Link",        L"PropertyData",
InBlock.gif    L"ObjectID",    L"ReparseData",
InBlock.gif    L"SparseDock",
ExpandedBlockEnd.gif}
;
None.gif
None.gif int  _tmain( int  argc, _TCHAR *  argv[])
ExpandedBlockStart.gif {
InBlock.gif    BOOL ret;
InBlock.gif
InBlock.gif    if (argc!=2) return -1;
InBlock.gif
InBlock.gif    wprintf(L"NTFS Streams of <<%s>>\n",argv[1]);
InBlock.gif    wprintf(L"------------------------------------------------------------------------\n");
InBlock.gif    
InBlock.gif    HANDLE hFile=CreateFile(argv[1],
InBlock.gif        GENERIC_READ,
InBlock.gif        FILE_SHARE_READ,
InBlock.gif        NULL,
InBlock.gif        OPEN_EXISTING,
InBlock.gif        FILE_ATTRIBUTE_NORMAL,
InBlock.gif        NULL);
InBlock.gif    assert(hFile != INVALID_HANDLE_VALUE);
InBlock.gif    
InBlock.gif    BYTE bBuf[4096];
InBlock.gif    WIN32_STREAM_ID* pwsi=(WIN32_STREAM_ID*)bBuf;
InBlock.gif
InBlock.gif    DWORD dwReaded;
InBlock.gif    LPVOID lpContext=0; //important!       
InBlock.gif
InBlock.gif    for (;;)
ExpandedSubBlockStart.gif    {
InBlock.gif        ZeroMemory(bBuf,4096);
InBlock.gif        ret=BackupRead(hFile,bBuf,20,&dwReaded,FALSE,TRUE,&lpContext);
InBlock.gif        assert(ret!=0);
InBlock.gif
InBlock.gif        if (dwReaded>0) 
ExpandedSubBlockStart.gif        {
InBlock.gif            WCHAR wszStreamName[MAX_PATH]; 
InBlock.gif            ZeroMemory(wszStreamName,MAX_PATH*2);
InBlock.gif
InBlock.gif            if (pwsi->dwStreamNameSize!=0)
InBlock.gif                BackupRead(hFile, 
InBlock.gif                    (LPBYTE) wszStreamName, 
InBlock.gif                    pwsi->dwStreamNameSize, 
InBlock.gif                    &dwReaded, 
InBlock.gif                    FALSE, TRUE, &lpContext);                
InBlock.gif            else
InBlock.gif                wsprintf(wszStreamName,L"(unnamed)");
InBlock.gif            
InBlock.gif            wprintf(L"\tName: [%s]\n",wszStreamName);
InBlock.gif            wprintf(L"\tType: %s\n", StreamType[pwsi->dwStreamId-1]);
InBlock.gif            wprintf(L"\tLength: %d\n",pwsi->Size);        
InBlock.gif            wprintf(L"------------------------------------------------------------------------\n");
InBlock.gif
InBlock.gif            DWORD lo,hi;    
InBlock.gif            BackupSeek(hFile,pwsi->Size.LowPart,pwsi->Size.HighPart,&lo,&hi,&lpContext);
ExpandedSubBlockEnd.gif        }
 else
InBlock.gif            break;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    ret=CloseHandle(hFile);
InBlock.gif    assert(ret!=0);
InBlock.gif
InBlock.gif    return 0;
ExpandedBlockEnd.gif}

Question:hmm…明白了...那我是不是直接分析pdf/doc文件多流的二进制格式便能获取和修改所有的"文件摘要"信息了?

Answer:理论上是可以的,不过…嘿嘿…这个 二进制格式的分析可没那么容易搞定… 其实,这个编程读取/修改的问题也不一定非要从NTFS多流这一底层的通用机制来着手不可,因为线索我们已经有了:结构化存储。COM提供了专门的结构化 存储函数和接口来让我们规范化地访问这些摘要数据。这些信息在COM眼中,只是复合文档的若干Properties而已,而且,这些Properties 还被划分成为了若干独立的PropertySet。我们可以通过调用StgOpenStorageEx函数来打开一个文件,并获取一个IPropertySetStorage接口,再通过该接口来对一个固定的属性集来进行读写访问(比方说,像摘要、关键字这些属性便保存在一个名为SummaryInformation的属性集中);同时,还可以对属性集进行自定义扩展,在其中保存任何自己所感兴趣的属性信息。

接下来,便是乏味的COM编程了…COM这个东西其实挺矛盾的,一方面,长久以来,大量厂家在其基础之上逐步实现了无数的类库及现成的、极有价值的 功能,而另一方面,其开发、应用起来实在是太烦冗,尤其是在托管环境中的使用,如果显式P/Invoke的话,一大堆声明、定义简直可以淹死人。不过,办 法总是有的…这里我偷了个懒,用C++/CLI中的C++ Interop(Implicit Pinvoke)实现了一个简单的控制台示例,哪位筒子有兴趣的话,还可以将其封装一下,当做个托管模块来重复利用呢…

None.gif #include  " stdafx.h "
None.gif#include  < ole2.h >
None.gif
None.gif using   namespace  System;
None.gif using   namespace  System::Runtime::InteropServices;
None.gif
None.gif static   const  GUID FMTID_SummaryInformation  =  
ExpandedBlockStart.gif { 0xF29F85E0, 0x4FF9, 0x1068, { 0xAB, 0x91, 0x08, 0x00,0x2b, 0x27, 0xb3, 0xd9 } } ;
None.gif
None.gif static   const  GUID FMTID_DocSummaryInformation  =  
ExpandedBlockStart.gif { 0xD5CDD502, 0x2E9C, 0x101B, { 0x93, 0x97, 0x08, 0x00,0x2b, 0x2c, 0xf9, 0xae } } ;
None.gif
None.gif static   const  GUID IID_IPropertySetStorage  =  
ExpandedBlockStart.gif { 0x0000013A, 0x0000, 0x0000, { 0xc0, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x46 } } ;
None.gif
None.gif const  CHAR *  SummaryPropertyName[] =
ExpandedBlockStart.gif {
InBlock.gif    "",                             "",
InBlock.gif    "Title",                        "Subject",
InBlock.gif    "Author",                       "Keywords",
InBlock.gif    "Comments",                     "Template",
InBlock.gif    "Last Saved By",                "Revision Number",
InBlock.gif    "Total Editing Time",           "Last Printed",
InBlock.gif    "Create Time//Date",            "Last saved Time//Date",
InBlock.gif    "Number of Pages",              "Number of Words",
InBlock.gif    "Number of Characters",         "Thumbnail",
InBlock.gif    "Name of Creating Application", "Security"
ExpandedBlockEnd.gif}
;
None.gif
None.gif void  PrintAllSummaryInformation(IPropertySetStorage  * pPropSetStg)
ExpandedBlockStart.gif {
InBlock.gif    HRESULT hr = S_OK;
InBlock.gif    IPropertyStorage *pPropStg = NULL;
InBlock.gif
InBlock.gif    hr=pPropSetStg->Open(FMTID_SummaryInformation, STGM_READ|STGM_SHARE_EXCLUSIVE, &pPropStg );
InBlock.gif    if (FAILED(hr)) throw gcnew Exception(L"IPropertySetStorage::Open失败");
InBlock.gif
InBlock.gif    PROPVARIANT propvarRead[5];
InBlock.gif    PROPSPEC propspec[5]; 
InBlock.gif
InBlock.gif    for (int i=0;i<5;i++) propspec[i].ulKind = PRSPEC_PROPID;
InBlock.gif    propspec[0].propid = PIDSI_TITLE;
InBlock.gif    propspec[1].propid = PIDSI_SUBJECT;
InBlock.gif    propspec[2].propid = PIDSI_AUTHOR;
InBlock.gif    propspec[3].propid = PIDSI_KEYWORDS;
InBlock.gif    propspec[4].propid = PIDSI_COMMENTS;
InBlock.gif
InBlock.gif    hr=pPropStg->ReadMultiple(5, propspec, propvarRead);
InBlock.gif    if (FAILED(hr)) throw gcnew Exception(L"IPropertyStorage::ReadMultiple失败");
InBlock.gif    if (S_FALSE==hr) throw gcnew Exception(L"所查询的所有属性值均不存在");
InBlock.gif
InBlock.gif    for (int i=0;i<5;i++)
InBlock.gif        if (propvarRead[i].vt==VT_LPSTR)
ExpandedSubBlockStart.gif        {
InBlock.gif            String^ msg=String::Format("{0}: {1}",
InBlock.gif                Marshal::PtrToStringAnsi(IntPtr((void*)SummaryPropertyName[propspec[i].propid])),
InBlock.gif                Marshal::PtrToStringAnsi(IntPtr(propvarRead[i].pszVal)));
InBlock.gif            Console::WriteLine(msg);
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif    for (int i=0;i<5;i++)
InBlock.gif        PropVariantClear(propvarRead+i);
InBlock.gif
InBlock.gif    if (pPropStg) pPropStg->Release();
InBlock.gif    pPropStg = NULL;
ExpandedBlockEnd.gif}

None.gif
None.gif int  main(array < System::String  ^>   ^ args)
ExpandedBlockStart.gif {
InBlock.gif    HRESULT hr = S_OK;
InBlock.gif    IPropertySetStorage *pPropSetStg = NULL;
InBlock.gif
InBlock.gif    if (args->Length!=1) return -1;
InBlock.gif    IntPtr pFileName=Marshal::StringToBSTR(args[0]);
InBlock.gif
ExpandedSubBlockStart.gif    try {
InBlock.gif        hr = StgOpenStorageEx(
InBlock.gif            (const WCHAR *)pFileName.ToPointer(),
InBlock.gif            STGM_READ|STGM_SHARE_DENY_WRITE,
InBlock.gif            STGFMT_ANY,
InBlock.gif            0, NULL, NULL, 
InBlock.gif            IID_IPropertySetStorage,
InBlock.gif            reinterpret_cast<void**>(&pPropSetStg) 
InBlock.gif        );
InBlock.gif        if (FAILED(hr)) throw gcnew Exception(L"StgOpenStorageEx失败");
InBlock.gif
InBlock.gif        PrintAllSummaryInformation(pPropSetStg);
ExpandedSubBlockStart.gif    }
 catch (Exception^ pError) {
InBlock.gif        Console::WriteLine(pError->ToString());
ExpandedSubBlockEnd.gif    }
 
InBlock.gif
InBlock.gif    Marshal::FreeBSTR(pFileName);
InBlock.gif
InBlock.gif    if (pPropSetStg) pPropSetStg->Release();
InBlock.gif    pPropSetStg=NULL;
InBlock.gif
InBlock.gif    return 0;
ExpandedBlockEnd.gif}

转载于:https://www.cnblogs.com/Altab/archive/2011/02/12/1952492.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值