Ole 和 Access 中嵌入的文件 - 第 2 部分
Ole 和 Access 中嵌入的文件
这个系列似乎一直受到延误的困扰。不幸的是,我最近没有太多时间。工作非常繁忙,而且我正在搬新家!所以请耐心等待。我会尽力尽可能频繁地发布。
在这一系列的上一篇文章中,我讨论了有关打开由 Access 存储的文件的一些理论背景。我们研究了不同的头部和 metafilepict 块。今天,是时候看一些代码了。
在深入代码之前,我想指出这部分代码是 Eduardo Morcillo 编写的库。没有它,我的代码将无法运行。
首要任务是拥有可以保存我的头部信息的 struct
。
internal struct PackageHeader
{
public short Signature;
public short HeaderSize;
public uint ObjectType;
public short FriendlyNameLen;
public short ClassNameLen;
public short FrNameOffset;
public short ClassNameOffset;
public int ObjectSize;
public string FriendlyName;
public string ClassName;
}
internal struct OleHeader
{
public uint OleVersion;
public uint Format;
public int ObjectTypeNameLen;
public string ObjectTypeName;
}
如您所见,我们有两个 struct
,它们包含有关包头和 OLE 头的信息,基于我们在上一篇文章中收集的信息。类型基于每个条目可以存储的字节数。
接下来的任务是定义我们在过程中需要的一些常量值。
private const int FixedPackageHeaderSize = 20;
private const int FixedOleHeaderSize = 12;
private const int MetaFileHeaderSize = 45;
private const int BufferSize = 1024;
private const string ContentsEntryName = "CONTENTS";
private const string WorkBookEntryName = "Workbook";
private const string MSPhotoFriendlyName = "MSPhotoEd.3\0";
如您所见,这里有一些数字用于头部固定大小,一个任意的缓冲区大小,以及一些我们需要用来识别我们处理的数据类型的字符串常量。
最后,我们还需要在类中一些 private
字段,可以为我们保存一些数据。
private System.IO.Stream _input;
private long _endOfHeaderPosition;
private int _dataLength;
private PackageHeader _packageHeader;
private OleHeader _oleHeader;
我们类的构造函数实际上有一个类型为 Stream
的参数,它是类的输入。从构造函数中,会调用一个 ReadHeader
方法。
private void ReadHeader()
{
if (_input.Position > 0 && _input.CanSeek)
{
_input.Seek(0, SeekOrigin.Begin);
}
byte[] fixedPackageHeaderData = new byte[FixedPackageHeaderSize];
_input.Read(fixedPackageHeaderData, 0, FixedPackageHeaderSize);
PackageHeader packageHeader = new PackageHeader();
packageHeader.Signature = CalcShortFromBytes(new byte[] { fixedPackageHeaderData[0],
fixedPackageHeaderData[1] });
packageHeader.HeaderSize = CalcShortFromBytes(new byte[] { fixedPackageHeaderData[2],
fixedPackageHeaderData[3] });
packageHeader.ObjectType = CalcUIntFromBytes(new byte[] { fixedPackageHeaderData[4],
fixedPackageHeaderData[5], fixedPackageHeaderData[6], fixedPackageHeaderData[7] });
packageHeader.FriendlyNameLen = CalcShortFromBytes(new byte[] {
fixedPackageHeaderData[8], fixedPackageHeaderData[9] });
packageHeader.ClassNameLen = CalcShortFromBytes(new byte[] { fixedPackageHeaderData[10],
fixedPackageHeaderData[11] });
packageHeader.FrNameOffset = CalcShortFromBytes(new byte[] { fixedPackageHeaderData[12],
fixedPackageHeaderData[13] });
packageHeader.ClassNameOffset = CalcShortFromBytes(new byte[] {
fixedPackageHeaderData[14], fixedPackageHeaderData[15] });
packageHeader.ObjectSize = CalcIntFromBytes(new byte[] { fixedPackageHeaderData[16],
fixedPackageHeaderData[17], fixedPackageHeaderData[18], fixedPackageHeaderData[19] });
byte[] friendlyNameData = new byte[packageHeader.FriendlyNameLen];
_input.Read(friendlyNameData, 0, packageHeader.FriendlyNameLen);
packageHeader.FriendlyName = Encoding.UTF8.GetString(friendlyNameData);
byte[] classNameData = new byte[packageHeader.ClassNameLen];
_input.Read(classNameData, 0, packageHeader.ClassNameLen);
packageHeader.ClassName = Encoding.UTF8.GetString(classNameData);
_packageHeader = packageHeader;
byte[] fixedOleHeaderData = new byte[FixedOleHeaderSize];
_input.Read(fixedOleHeaderData, 0, FixedOleHeaderSize);
OleHeader oleHeader = new OleHeader();
oleHeader.OleVersion = CalcUIntFromBytes(new byte[] { fixedOleHeaderData[0],
fixedOleHeaderData[1], fixedOleHeaderData[2], fixedOleHeaderData[3] });
oleHeader.Format = CalcUIntFromBytes(new byte[] { fixedOleHeaderData[4],
fixedOleHeaderData[5], fixedOleHeaderData[6], fixedOleHeaderData[7] });
oleHeader.ObjectTypeNameLen = CalcIntFromBytes(new byte[] { fixedOleHeaderData[8],
fixedOleHeaderData[9], fixedOleHeaderData[10], fixedOleHeaderData[11] });
byte[] objectTypeNameData = new byte[oleHeader.ObjectTypeNameLen];
_input.Read(objectTypeNameData, 0, oleHeader.ObjectTypeNameLen);
oleHeader.ObjectTypeName = Encoding.UTF8.GetString(objectTypeNameData);
_oleHeader = oleHeader;
for (int index = 0; index < 8; index++)
{
_input.ReadByte();
}
byte[] lengthData = new byte[4];
_input.Read(lengthData, 0, 4);
_dataLength = BitConverter.ToInt32(lengthData, 0);
_endOfHeaderPosition = _input.Position;
}
此方法读取头部并将其分解为条目。这使我们能够访问可变位并正确读取它们。它还为我们提供了一些重要信息,即数据块的长度和头部结束位置。
那么,让我们进入这个类的业务端,即它的 GetStrippedStream
方法。
public System.IO.Stream GetStrippedStream()
{
if (_input.Position != _endOfHeaderPosition && _input.CanSeek)
{
_input.Seek(_endOfHeaderPosition, SeekOrigin.Begin);
}
if (_packageHeader.ClassName.Equals(MSPhotoFriendlyName, StringComparison.OrdinalIgnoreCase))
{
_input.Seek(_dataLength + MetaFileHeaderSize, SeekOrigin.Current);
}
string tempFileName = Path.GetTempFileName();
FileStream tempFileStream = File.OpenWrite(tempFileName);
byte[] buffer = new byte[BufferSize];
int loadedBytes = _input.Read(buffer, 0, BufferSize);
while (loadedBytes > 0)
{
tempFileStream.Write(buffer, 0, loadedBytes);
loadedBytes = _input.Read(buffer, 0, BufferSize);
}
tempFileStream.Close();
System.IO.Stream outputStream;
bool isCompoundFile = Storage.IsCompoundStorageFile(tempFileName);
if (isCompoundFile)
{
Storage storage = new Storage(tempFileName);
Storage.StorageElementsCollection elements = storage.Elements();
// element.Name.Equals(WorkBookEntryName, StringComparison.OrdinalIgnoreCase)
var result = from StatStg element in elements
where (element.Name.Equals(ContentsEntryName, StringComparison.OrdinalIgnoreCase)
element.Name.Equals(WorkBookEntryName, StringComparison.OrdinalIgnoreCase))
&& element.Type == StatStg.ElementType.Stream
select element;
if (result.Any())
{
outputStream = storage.OpenStream(result.First().Name);
}
else
{
storage.Close();
outputStream = File.OpenRead(tempFileName);
}
}
else
{
outputStream = File.OpenRead(tempFileName);
}
return outputStream;
}
如您所见,我们首先确保根据头部信息将流的位置设置为正确的位置。然后我们将流写入临时文件。临时文件用于使用 Eduardo 的库。我们使用它来确定该文件是否实际上是结构化存储,如果是,则仅提取我们需要的流。如果这是一个 Microsoft Word 文档,那么它将不会具有我们在结构化存储中寻找的元素。在这种情况下,我们希望将完整的文件作为结果发送。如果这不是结构化存储,那么我们也希望发送完整的文件。
警告:代码可能表明它也适用于 Microsoft Excel,但不幸的是,事实并非如此。原因是,每当 Access 嵌入 Excel 文件时,它都会完全更改结构化存储,直到无法恢复原始文件。我已经设法从中提取数据,但尚未设法获得一个正常工作的 Excel 文件。如果有人能为我提供更多见解,我将非常感激。
下面,您可以找到包含完整类的 .cs 文件。
这完成了关于 Ole 的系列文章。希望您觉得它有帮助。请在下方留下任何评论和/或问题。我总是很高兴阅读和回复。