如何以自动化的方式将OLEObject的内容保存为文件?

7

我正在向PowerPoint幻灯片中添加几种不同的文件类型,包括MP4、ZIP、SWF和其他格式。我不是想要播放这些文件类型;然而,我想在PowerPoint演示文稿内部存储和检索文件。

以下是我成功存储这些不同文件类型的方法:

currentSlide.Shapes.AddOLEObject(0, 0, -1, -1, "", this.filePath);

我也可以通过右键单击对象 -> 选择编辑包 -> 文件 -> 保存内容来手动检索它们。
我想要复制或使用此功能将OLEObject的内容保存回其原始状态并保存到我指定的路径。我有一种方法找到该形状,我认为这个对象包含了内容:slideShape.OLEFormat.Object,现在我不知道该怎么做。有什么建议吗?谢谢。
哦,我正在使用office 2010,如果有任何区别请告诉我。

既然您正在使用Office 2010,那么可以安全地假设您的幻灯片是PPTX格式的吗? - M.Babcock
我认为你不能直接从Powerpoint的内存演示文稿和形状中获取它,因为Powerpoint的OLEFormat在对象被包装/存储为Package(20年前的旧OLE 1事物)时存在缺陷(即使在纯COM /自动化中,而不仅仅是.NET)。你能否从文件中获取它,而不是从内存中获取? - Simon Mourier
可能会起作用...你有什么建议吗? - Parris
我想说的是,如果有一种方法可以将文件加载到PowerPoint中,那么我不明白为什么检索它们会如此困难。 - Parris
1
npoi 能满足你的需求吗? - sga101
3个回答

3
因此,我知道现在有点晚了;但是,我知道其他人一定也有类似的问题。
我联系了微软关于这个问题,他们花了大约一个月左右的时间,但最终给了我一个答案。我有一个解决方案的压缩包。它相当详尽,下面可以看到纯源代码。
首先是一些说明:
1)创建一个 PowerPoint 演示文稿并嵌入一些文件(视频、mp3、zip 等)
2)将文件保存到以下位置:C:\Temp\Temp.pptx
3)关闭文件
4)在“C:\Temp”下创建一个名为“GeneratedFiles”的文件夹
5)打开应用程序并运行它。
所以唯一的缺点就是 PPTX 文件必须关闭。话虽如此,人们可能能够将打开的 PPTX 文件复制到其他地方,然后从该位置提取文件,而不是从实际正在使用的 PPTX 中提取。然后只需删除临时文件即可。我们会看到的。这就是这个解决方案所能达到的最接近的程度。
没有更多的拖延:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO.Packaging;
using System.Runtime.InteropServices;
using System.IO;

namespace ExtractOLEPowerPoint
{
class Program
{

    #region IEnumSTATSTG
    [ComImport]
    [Guid("0000000d-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IEnumSTATSTG
    {
        // The user needs to allocate an STATSTG array whose size is celt.
        [PreserveSig]
        uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt, out uint pceltFetched);

        void Skip(uint celt);

        void Reset();

        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumSTATSTG Clone();
    }
    #endregion

    //#region IStream

    //[ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    //public interface IStream
    //{
    //    void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, uint cb, out uint pcbRead);
    //    void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, uint cb, out uint pcbWritten);
    //    void Seek(long dlibMove, uint dwOrigin, out long plibNewPosition);
    //    void SetSize(long libNewSize);
    //    void CopyTo(IStream pstm, long cb, out long pcbRead, out long pcbWritten);
    //    void Commit(uint grfCommitFlags);
    //    void Revert();
    //    void LockRegion(long libOffset, long cb, uint dwLockType);
    //    void UnlockRegion(long libOffset, long cb, uint dwLockType);
    //    void Stat(out STATSTG pstatstg, uint grfStatFlag);
    //    void Clone(out IStream ppstm);
    //}

    //#endregion

    #region STATFLAG

    [Flags]
    public enum STATFLAG : uint
    {
        STATFLAG_DEFAULT = 0,
        STATFLAG_NONAME = 1,
        STATFLAG_NOOPEN = 2
    }

    #endregion

    #region IStorage
    [ComImport]
    [Guid("0000000b-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IStorage
    {
        void CreateStream(
            /* [string][in] */ string pwcsName,
            /* [in] */ uint grfMode,
            /* [in] */ uint reserved1,
            /* [in] */ uint reserved2,
            /* [out] */ out System.Runtime.InteropServices.ComTypes.IStream ppstm);
        void OpenStream(
            /* [string][in] */ string pwcsName,
            /* [unique][in] */ IntPtr reserved1,
            /* [in] */ uint grfMode,
            /* [in] */ uint reserved2,
            /* [out] */ out System.Runtime.InteropServices.ComTypes.IStream ppstm);

        void CreateStorage(
            /* [string][in] */ string pwcsName,
            /* [in] */ uint grfMode,
            /* [in] */ uint reserved1,
            /* [in] */ uint reserved2,
            /* [out] */ out IStorage ppstg);

        void OpenStorage(
            /* [string][unique][in] */ string pwcsName,
            /* [unique][in] */ IStorage pstgPriority,
            /* [in] */ uint grfMode,
            /* [unique][in] */ IntPtr snbExclude,
            /* [in] */ uint reserved,
            /* [out] */ out IStorage ppstg);

        void CopyTo(
            /* [in] */ uint ciidExclude,
            /* [size_is][unique][in] */ Guid rgiidExclude, // should this be an array?
            /* [unique][in] */ IntPtr snbExclude,
            /* [unique][in] */ IStorage pstgDest);

        void MoveElementTo(
            /* [string][in] */ string pwcsName,
            /* [unique][in] */ IStorage pstgDest,
            /* [string][in] */ string pwcsNewName,
            /* [in] */ uint grfFlags);

        void Commit(
            /* [in] */ uint grfCommitFlags);

        void Revert();

        void EnumElements(
            /* [in] */ uint reserved1,
            /* [size_is][unique][in] */ IntPtr reserved2,
            /* [in] */ uint reserved3,
            /* [out] */ out IEnumSTATSTG ppenum);

        void DestroyElement(
            /* [string][in] */ string pwcsName);

        void RenameElement(
            /* [string][in] */ string pwcsOldName,
            /* [string][in] */ string pwcsNewName);

        void SetElementTimes(
            /* [string][unique][in] */ string pwcsName,
            /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pctime,
            /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME patime,
            /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pmtime);

        void SetClass(
            /* [in] */ Guid clsid);

        void SetStateBits(
            /* [in] */ uint grfStateBits,
            /* [in] */ uint grfMask);

        void Stat(
            /* [out] */ out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg,
            /* [in] */ uint grfStatFlag);
    }

    #endregion

    #region STGM
    [Flags]
    public enum STGM : int
    {
        DIRECT           = 0x00000000,
        TRANSACTED       = 0x00010000,
        SIMPLE           = 0x08000000,
        READ             = 0x00000000,
        WRITE            = 0x00000001,
        READWRITE        = 0x00000002,
        SHARE_DENY_NONE  = 0x00000040,
        SHARE_DENY_READ  = 0x00000030,
        SHARE_DENY_WRITE = 0x00000020,
        SHARE_EXCLUSIVE  = 0x00000010,
        PRIORITY         = 0x00040000,
        DELETEONRELEASE  = 0x04000000,
        NOSCRATCH        = 0x00100000,
        CREATE           = 0x00001000,
        CONVERT          = 0x00020000,
        FAILIFTHERE      = 0x00000000,
        NOSNAPSHOT       = 0x00200000,
        DIRECT_SWMR      = 0x00400000,
    }

    #endregion

    #region StgIsStorageFile

    [DllImport("Ole32.dll")]
    static extern int StgIsStorageFile([MarshalAs(UnmanagedType.LPWStr)]string filename);

    #endregion

    #region StgOpenStorage

    [DllImport("Ole32.dll")]
    static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)]string pwcsName, IStorage pstgPriority, STGM grfmode, IntPtr snbExclude, uint researved, out IStorage ppstgOpen);

    #endregion

    static void Main(string[] args)
    {
        Package pkg = Package.Open("C:\\Temp\\Temp.pptx");
        foreach (PackagePart pkgprt in pkg.GetParts())
        {
            if(pkgprt.Uri.ToString().StartsWith("/ppt/embeddings/"))
            {
                System.IO.Stream strm = pkgprt.GetStream();
                byte[] buffer = new byte[strm.Length];
                strm.Read(buffer, 0, (int)strm.Length);
                strm.Close();

                // Create a temporary file
                string targetFile = "C:\\Temp\\GeneratedFiles\\" + pkgprt.Uri.ToString().Remove(0, "/ppt/embeddings/".Length);
                System.IO.File.WriteAllBytes(targetFile, buffer);

                // Extract the contents.
                IStorage Is;
                StgOpenStorage(targetFile, null, STGM.READWRITE | STGM.SHARE_EXCLUSIVE, IntPtr.Zero, 0, out Is);
                ProcessPackage(Is);

                // Need to release the IStorage object and call GC.Collect() to free the object
                Marshal.ReleaseComObject(Is);
                Is = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();

                // Delete the temporary binary file extracted
                File.Delete(targetFile);
            }
        }
    }
    static void ProcessPackage(IStorage pStg)
    {
        System.Runtime.InteropServices.ComTypes.IStream pStream;
        IEnumSTATSTG pEnumStatStg;
        uint numReturned;
        pStg.EnumElements(0, IntPtr.Zero, 0, out pEnumStatStg);
        System.Runtime.InteropServices.ComTypes.STATSTG[] ss = new System.Runtime.InteropServices.ComTypes.STATSTG[1];
        // Loop through the STATSTG structures in the storage.
        do
        {
            // Retrieve the STATSTG structure
            pEnumStatStg.Next(1, ss, out numReturned);
            if (numReturned != 0)
            {
                //System.Runtime.InteropServices.ComTypes.STATSTG statstm;
                byte[] bytT = new byte[4];
                // Check if the pwcsName contains "Ole10Native" stream which contain the actual embedded object
                if (ss[0].pwcsName.Contains("Ole10Native") == true)
                {
                    // Get the stream objectOpen the stream
                    pStg.OpenStream(ss[0].pwcsName, IntPtr.Zero, (uint)STGM.READ | (uint)STGM.SHARE_EXCLUSIVE, 0, out pStream);
                    //pStream.Stat(out statstm, (int) STATFLAG.STATFLAG_DEFAULT);

                    IntPtr position = IntPtr.Zero;
                    // File name starts from 7th Byte.
                    // Position the cursor to the 7th Byte.
                    pStream.Seek(6, 0, position);

                    IntPtr ulRead = new IntPtr();
                    char[] filename = new char[260];
                    int i;

                    // Read the File name of the embedded object
                    for (i = 0; i < 260; i++)
                    {
                        pStream.Read(bytT, 1, ulRead);
                        pStream.Seek(0, 1, position);
                        filename[i] = (char)bytT[0];
                        if (bytT[0] == 0)
                        {
                            break;
                        }
                    }
                    string path = new string(filename, 0, i);

                    // Next part is the source path of the embedded object.
                    // Length is unknown. Hence, loop through each byte to read the 0 terminated string
                    // Read the source path.
                    for (i = 0; i < 260; i++)
                    {
                        pStream.Read(bytT, 1, ulRead);
                        pStream.Seek(0, 1, position);
                        filename[i] = (char)bytT[0];
                        if (bytT[0] == 0)
                        {
                            break;
                        }
                    }
                    // Source File path
                    string fullpath = new string(filename, 0, i);

                    // Unknown 4 bytes
                    pStream.Seek(4, 1, position);

                    // Next 4 byte gives the length of the temporary file path 
                    // (Office uses a temporary location to copy the files before inserting to the document)
                    // The length is in little endian format. Hence conversion is needed
                    pStream.Read(bytT, 4, ulRead);
                    ulong dwSize, dwTemp;
                    dwSize = 0;
                    dwTemp = (ulong)bytT[3];
                    dwSize += (ulong)(bytT[3] << 24);
                    dwSize += (ulong)(bytT[2] << 16);
                    dwSize += (ulong)(bytT[1] << 8);
                    dwSize += bytT[0];

                    // Skip the temporary file path
                    pStream.Seek((long)dwSize, 1, position);

                    // Next four bytes gives the size of the actual data in little endian format.
                    // Convert the format.
                    pStream.Read(bytT, 4, ulRead);
                    dwTemp = 0;
                    dwSize = 0;
                    dwTemp = (ulong)bytT[3];
                    dwSize += (ulong)(bytT[3] << 24);
                    dwSize += (ulong)(bytT[2] << 16);
                    dwSize += (ulong)(bytT[1] << 8);
                    dwSize += (ulong)bytT[0];

                    // Read the actual file content 
                    byte[] byData = new byte[dwSize];
                    pStream.Read(byData, (int)dwSize, ulRead);

                    // Create the file
                    System.IO.BinaryWriter bWriter = new System.IO.BinaryWriter(System.IO.File.Open("C:\\temp\\GeneratedFiles\\" + path, System.IO.FileMode.Create));
                    bWriter.Write(byData);
                    bWriter.Close();
                }
            }
        }
        while (numReturned > 0);
    }
}
}

2
对象slideShape.OLEFormat.Object应该支持IPersistFileIPersistStream。如果支持,您可以轻松地导致它写入您选择的文件。为此,您需要使用OLE类型库使VB能够理解IPersistFile接口。
从此页面下载OLELIB.TLB,安装它,并将引用添加到您的PPTX中: IPersistFile::Save文档: 示例用法:
Dim oPersist as IPersistFile
Set oPersist = slideShape.OLEFormat.Object
' Zero means save a copy as
oPersist.Save(sFileName, 0)

根本问题在于当对象为Package时,OLEFormat.Object会抛出异常。对此你无能为力,这是底层自动化实现的一个旧问题。 - Simon Mourier
你知道我能否在C#中使用这个吗? - Parris
我需要把积分给某人。我会把它们给你。只是因为这样更短,更简洁!我也听说过这个解决方案,但从未找到任何代码。 - Parris
1
在C#中也应该可以正常工作,但您不需要TLB。 此外,正如Simon指出的那样,这对于包文件无效(我刚试过)。 - Ben
MP4 是否被认为是一个包文件? - Parris
如果有人有这个问题,是的,MP4是一个封装文件。它可以使用JPG,但对于单个文件而言它是无用的。微软表示他们已经找到解决方法了。很快会回复大家。 - Parris

1

您可以通过从.NET或C#中调用PowerShell来实现此操作。

function Export-MediaFromPptx($pptxFile) 
{
    [void] [System.Reflection.Assembly]::LoadFrom("C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll")
    $pkg = [System.IO.Packaging.Package]::Open($pptxFile)

    $mediaDirectory = [System.IO.Path]::GetDirectoryName($pptxFile) + "\" + [System.IO.Path]::GetFileName($pptxFile) + "_media"
    md $mediaDirectory | out-null

    foreach ($mediaPart in ($pkg.GetParts() | where { $_.Uri.ToString().StartsWith("/ppt/media/") })) {
      $sourceStream = $mediaPart.GetStream()
      [byte[]]$buffer = new-object byte[] $sourceStream.Length
      [void]$sourceStream.Read($buffer, 0, $sourceStream.Length)
      $sourceStream.Close()
      $targetFileName = $mediaDirectory + "\" + $mediaPart.Uri.ToString().Remove(0, "/ppt/media/".Length)
      [System.IO.File]::WriteAllBytes($targetFileName, $buffer)
    }

    $pkg.Close()
}

上述脚本取自文章

这个文章展示了如何从.NET中调用它。

这个文章展示了如何从c#中调用它。

希望这能有所帮助。


这个可以工作,但如果对象是一个Package,则提取的是OLE 1格式文件(复合存储),因此只完成了一半的工作。 - Simon Mourier
我提取完后需要进行什么操作? - Parris
我认为这个是正确的,我正在向Microsoft支持寻求帮助,将OLE 1格式转换为标准格式。或者我正在尝试让他们为我提供一个补丁,或告诉我它是不可能的。 - Parris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接