使用 IFilter 实现 TextReader 以提取各种文件内容
一个使用 IFilter 实现提取各种文件内容的解决方案。特别感谢 Eyal Post 及其文章《Using IFilter in C#》。
引言
IFilter
接口是 Microsoft 索引服务的一个重要组件。 本项目的目的是提供一种高性能且内存占用低的解决方案。 这将通过使用 TextReader
并操作 IFilter
的包装器来实现与托管代码的互操作。
本文面向具有托管环境中 COM 开发经验的用户,旨在创建一个可以使用 IFilter
实现提取各种文件内容的解决方案。
源代码包中的解决方案包含一个测试项目,其中包括单元测试和负载测试。 这些是使用更高版本的 Visual Studio 创建的。 如果您无法打开测试项目,您可以随时删除 IFilterTest 项目。
背景
IFilter
组件是一个进程内 COM 服务器,用于提取特定文件类型的文本和值。 过滤组件调用适合文件类型的 IFilter
组件。
可以为几乎任何选定的文件类型开发定制的 IFilter
组件。 索引服务提供的标准 IFilter
组件包括以下内容:
文件名 |
描述 |
mimefilt.dll |
过滤多用途 Internet 邮件扩展 (MIME) 文件。 |
nlhtml.dll |
过滤 HTML 3.0 或更早版本的文件。 |
offfilt.dll |
过滤 Microsoft Office 文件:Microsoft Word、Microsoft Excel 和 Microsoft PowerPoint®。 |
query.dll |
过滤纯文本文件(默认过滤器)和二进制文件(空过滤器)。 |
IFilter
接口扫描文档以查找文本和属性(也称为属性)。 它从这些文档中提取文本块,过滤掉嵌入的格式并保留有关文本位置的信息。 它还提取值块,这些值是整个文档或定义明确的文档部分的属性。 IFilter
为构建更高级别的应用程序(如文档索引器和独立于应用程序的查看器)奠定了基础。
主要类图
IFilter 接口和混合类
IFilter
代码
[ComImport, Guid(Constants.IFilterGUID),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
SuppressUnmanagedCodeSecurity, ComVisible(true), AutomationProxy(false)]
public interface IFilter
{
/// <summary>
/// The IFilter::Init method initializes a filtering session.
/// </summary>
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
IFilterReturnCodes Init(
//[in] Flag settings from the IFILTER_INIT enumeration for
// controlling text standardization, property output, embedding
// scope, and IFilter access patterns.
[MarshalAs(UnmanagedType.U4)]IFILTER_INIT grfFlags,
// [in] The size of the attributes array. When nonzero, cAttributes
// takes
// precedence over attributes specified in grfFlags. If no
// attribute flags
// are specified and cAttributes is zero, the default is given by
// the
// PSGUID_STORAGE storage property set, which contains the date and
// time
// of the last write to the file, size, and so on; and by the
// PID_STG_CONTENTS
// 'contents' property, which maps to the main contents of the
// file.
// For more information about properties and property sets, see
// Property Sets.
uint cAttributes,
//[in] Array of pointers to FULLPROPSPEC structures for the
// requested properties.
// When cAttributes is nonzero, only the properties in aAttributes
// are returned.
FULLPROPSPEC[] aAttributes,
// [out] Information about additional properties available to the
// caller; from the IFILTER_FLAGS enumeration.
out IFILTER_FLAGS pdwFlags);
/// <summary>
/// The IFilter::GetChunk method positions the filter at the beginning
/// of the next chunk,
/// or at the first chunk if this is the first call to the GetChunk
/// method, and returns a description of the current chunk.
/// </summary>
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
IFilterReturnCodes GetChunk(out STAT_CHUNK pStat);
/// <summary>
/// The IFilter::GetText method retrieves text (text-type properties)
/// from the current chunk,
/// which must have a CHUNKSTATE enumeration value of CHUNK_TEXT.
/// </summary>
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
IFilterReturnCodes GetText(
// [in/out] On entry, the size of awcBuffer array in wide/Unicode
// characters. On exit, the number of Unicode characters written to
// awcBuffer.
// Note that this value is not the number of bytes in the buffer.
ref uint pcwcBuffer,
// Text retrieved from the current chunk. Do not terminate the
// buffer with a character.
[Out]IntPtr awcBuffer);
/// <summary>
/// The IFilter::GetValue method retrieves a value (public
/// value-type property) from a chunk,
/// which must have a CHUNKSTATE enumeration value of CHUNK_VALUE.
/// </summary>
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
IFilterReturnCodes GetValue(
// Allocate the PROPVARIANT structure with CoTaskMemAlloc. Some
// PROPVARIANT
// structures contain pointers, which can be freed by calling the
// PropVariantClear function.
// It is up to the caller of the GetValue method to call the
// PropVariantClear method.
// ref IntPtr ppPropValue
// [MarshalAs(UnmanagedType.Struct)]
out PROPVARIANT PropVal);
/// <summary>
/// The IFilter::BindRegion method retrieves an interface representing
/// the specified portion of the object.
/// Currently reserved for future use.
/// </summary>
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
IFilterReturnCodes BindRegion(ref FILTERREGION origPos,ref Guid riid,
ref object ppunk);
}
混合类
public class MixedIFilterClass : IFilterClass, IDisposable
{
public override string TmpFilePath
{
get;
set;
}
public override Object InternalObj
{
get;
set;
}
//private MixedIFilterClass()
//{
// InternalPtr = Marshal.GetComInterfaceForObject(this, typeof(IFilter));
//}
~MixedIFilterClass()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if(null != InternalObj)
{
Marshal.ReleaseComObject(InternalObj);
InternalObj = null;
}
if (null != TmpFilePath)
try
{
File.Delete(TmpFilePath);
TmpFilePath = null;
}
catch { }
if (disposing)
GC.SuppressFinalize(this);
}
public void Dispose()
{
Dispose(true);
}
}
工作原理
需要两个步骤来展示流程的工作方式,它们是:
- 获取当前块
- 在块上调用
GetText()
步骤 1:获取当前块
如果到达最后一个块,请终止读取过程。
var returnCode = _filter.GetChunk(out chunk);
步骤 2:在块上调用 GetText()
根据从 GetChunk
方法获取的状态,在文本块上调用 GetText
方法。 读取到当前块结束标志时,重复步骤 1。
while (true)
{
if (remaining <= _topSize)
return;
bool useBuffer = !forceDirectlyWrite && remaining < BufferSize;
var size = BufferSize;
if (useBuffer)
size -= _topSize;
else
{
if (remaining < BufferSize)
size = (uint)remaining;
}
if (size < ResBufSize)
size = ResBufSize;
var handle = GCHandle.Alloc(useBuffer ? _buffer : array,
GCHandleType.Pinned);
var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(
useBuffer ? _buffer : array, useBuffer ? (int)_topSize : offset);
IFilterReturnCodes returnCode;
try
{
#if DEBUG
Trace.Write(size);
#endif
returnCode = _filter.GetText(ref size, ptr);
#if DEBUG
Trace.WriteLine("->"+size);
#endif
}
finally
{
handle.Free();
}
if(returnCode != IFilterReturnCodes.FILTER_E_NO_TEXT)
{
if (useBuffer)
_topSize += size;
else
{
offset += (int)size;
remaining -= (int)size;
}
if(_topSize > BufferSize)
{
_resTopSize = _topSize - BufferSize;
_topSize = BufferSize;
}
}
if (returnCode == IFilterReturnCodes.FILTER_S_LAST_TEXT ||
returnCode == IFilterReturnCodes.FILTER_E_NO_MORE_TEXT ||
(returnCode == IFilterReturnCodes.FILTER_E_NO_TEXT && size != 0) ||
(null == FileName && IgnoreError && returnCode ==
IFilterReturnCodes.E_INVALIDARG))
{
_endOfCurrChunk = true;
if (remaining <= _topSize)
return;
break;
}
if(returnCode != IFilterReturnCodes.S_OK)
{
throw new Exception(
"a error occur when getting text by current filter",
new Exception(returnCode.ToString()));
}
}
Using the Code
以下代码仅使用文件名
var fileName = "";
using (var reader = new FilterReader(fileName))
{
reader.Init();
//
// write your code here;
//
}
此代码将指定文件和扩展名
using (var reader = new FilterReader(fileName, ".docx"))
{
reader.Init();
//
// write your code here;
//
}
using (var reader = new FilterReader(fileName, 0x1000))
{
reader.Init();
//
// write your code here;
//
}
以下代码显示了如何将 byte
数组传递到 FilterReader
中
byte[] bytes = null;
using (var reader = new FilterReader(bytes, ".docx"))
{
reader.Init();
//
// write your code here;
//
}
using (var reader = new FilterReader(bytes, ".docx", 0x1000))
{
reader.Init();
//
// write your code here;
//
}
using (var reader = new FilterReader(bytes))
{
reader.Init();
//
// write your code here;
//
}
参考
历史
- 2010 年 2 月 9 日 - 更新了下载文件。
- 2009 年 3 月 3 日 - v2.0:替换了版权信息并添加了 OLE FMTID、Windows 搜索架构和 OLE 属性接口,以满足更多需求。
- 2009 年 2 月 5 日 - v2.0:重构了一些短语(感谢 Sean Kenney 审阅了这篇文章)。
- 2009 年 1 月 7 日 - v2.0:添加了有关 Adobe PDF 过滤器的注释和小修改。
- 2008 年 12 月 9 日 - v2.0 [稳定版本]。
- 2008 年 11 月 24 日 - v1.0 [初始版本]。