65.9K
CodeProject 正在变化。 阅读更多。
Home

Ole 和 Access 中嵌入的文件 - 第 2 部分

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009 年 8 月 18 日

CPOL

3分钟阅读

viewsIcon

13254

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 的系列文章。希望您觉得它有帮助。请在下方留下任何评论和/或问题。我总是很高兴阅读和回复。

© . All rights reserved.