index > Visual Studio Tools for Office > Saving OLEObject content to a file

Saving OLEObject content to a file

Greetings, colleagues

I have spent quite some time on trying to solve the following problem: I get the OLEObject from the Excel worksheet, e.g.

Excel.OLEObject myOLEObject = (Excel.OLEObject) currentSheet.OLEObjects(1);

I need to to save this myOLEObject object back to file on hard drive (in my Excel 2003 managed code extension).

The only thing I know about the OLE object is that it was created in Excel 2003 through "Insert -> Object -> Create from file". So the exact data type of this OLE object is unknown. Casting it to Bitmap or Word document does not solve the problem. I.e. it may be a bitmap image, a word document, etc. including a generic binary file of unknown format.

Manually an Excel 2003 user can click on this embedded object and then in the context menu choose "Package Object -> Edit Package -> File -> Save content" to save the embedded object back to file. That is why I was hoping there must be some Save() method so that I could simply write, say, myOLEObject.Save("C:\myObject.bin"). But there is no Save() method for OLEObject. I also tried to use Windows clipboard to retrieve object from the clipboard:

myOLEObject.Copy();

IDataObject iData = Clipboard.GetDataObject();

Object myObj = (Object) iData.GetData("Office Drawing Shape Format");

But then I can only save myObj to a file using binary formatter, which saves additional file header and footer together with the object content. This causes another problem of parsing the stored file format (and I could not find the format description either):

if (iData.GetDataPresent("Office Drawing Shape Format"))

{

FileStream fstr = new FileStream("./FileFromExcel.bin", FileMode.Create);

BinaryFormatter myFormatter = new BinaryFormatter();

myFormatter.Serialize(fstr, myObj);

fstr.Close();

}

I could equally use "Embedded Object" as data format to get data from clipboard, but the result is almost the same.

I would be very grateful if anyone who faced similar problem would give me advice / a hint how to solve my problem. Again clipboard use may not be necessary, but I just don't know how to get object data from OLEObject (i.e. to which class should I cast OLEObject.object, right?) and then to save it to file by means of FileStream.

Many thanks for your help,

Anton




Tonioant
Tonioant

Did you try getting a hold of object's IPersistStorage interface, or IPersistStream or IPersistFile?

Usually persistable OLE objects implement one (or more) of the above interfaces.




Misha (VSTO team is hiring. We would like to hear from you http://tinyurl.com/ZQGW2)
Misha Shneerson - MSFT

Misha, spasibo.

I tried this before but could use neither of the above interfaces. It would be great to know how to do it properly since I am not very strong in low level interfaces. I tried the following:

System.Runtime.InteropServices.UCOMIPersistFile myPersistableObject = (System.Runtime.InteropServices.UCOMIPersistFile) myOLEObject;

But I always get the invalid cast exception. Same thing happens if I try to cast to UCOMIStream or UCOMIMoniker interfaces. I also tried to use OLEObject.Object property, but it does not return any object or value - looks like it is not suitable for a generic file kept as embedded objects in a Excel sheet.

Would appreciate any help or a correct sample how to cast to IPersistFile interfaces (including which object to cast to it).

If I can't solve it "gracefully" I will have to parse the binary data stream (of a data format whose description I could not find) to cut the file body.

Misha, thanks for your attempt anyway,

Anton




Tonioant
Anton Rapoport

As I planned I solved this problem in a "terrible" way. Since I do not know how to solve it better, I have decided to publish my solution - maybe it will be helpful for those who face similar issue or maybe somebody will share his experience and suggest a more elegant approach.

So the initial conditions are: I have an Excel workbook which has a generic file embedded as OLE object (packaged object) into its worksheet. I need to develop a C# code in a VSTO project to save this embedded file to the hard disk. I will have one little simplification here - though the embedded file is generic, I will know its extension (e.g. "zip" or "bin") in my code. For easier explanation I will use "zip" extension.

My "terrible" solution in a nutshell:

1) Since I cannot use OLEObject.Object property (I could not find how to deal with it or how to cast it to some persistable interface) copy the OLE object to the clipboard ( OLEObject.Copy() )

2) Get the clipboard data and to the object cast to IDataObject interface

3) Get the object data from the IDataObject object specifying "Native" format (on which I could not find description).

4) Create a memory stream from the just obtained "Native" format object data.

5) Parse the obtained memory stream and determine the stream position where the actual file content starts and ends.

6) Extract and copy the actual file content from memory stream to filestream (thus saving the ZIP file to the disk)

Here is the code which I develop to implement the above solution:

using Microsoft.Office.Interop.Excel;

/* ... */

private void CopyEmbeddedFileToDisk(string fileLocation)
{

  /* Some code */

  try
  {

   /*
    * Retrieves OLE Object from worksheet
    */
   OLEObject embeddedOLEObject = (OLEObject) internalWorkSheet.OLEObjects(1);
  
   /*
    * Copies data from Excel OLE Object to clipboard and retrieves it back as object
    */
   embeddedOLEObject.Copy();
   IDataObject iData = Clipboard.GetDataObject();
   string objType = "Native";
   Object objToSave = iData.GetData(objType);
   Clipboard.SetDataObject("");
  
   /*
    * Creates stream to process object data.
    */
   MemoryStream objStream = (MemoryStream) objToSave;
   long sizeOfMyObj = objStream.Length;
  
   /*
    * Specifies embedded file extension, which is stored in object stream "header" 3 times in filenames
    */
   string fileExtension = ".ZIP";

   /*
    * Finds 3 occurrences of fileExtension. The "Native" format has a content header, which
    * contains the filename 3 times: one pure filename and then 2 filenames with full path.
    * After last filename there are 5 extra bytes and then the actual file content starts. 
    * The actual file content ends 2 bytes before the "Native" format data ends 
    */
   long startOffset = 5L;
   long endOffset = 2L;
   long foundPosition = 0;
   for (int occurrence = 1; occurrence <= 3; occurrence++)
   {
    foundPosition = FindInStream(fileExtension, false, foundPosition, objStream) + fileExtension.Length;
    if (foundPosition == -1)
     break;
   }

   /*
    * If the specified string was found 3 times, then goes on and extracts the actual file content
    */
   if (foundPosition != -1)
   {
 
    /*
     * Position where actual file content starts.
     */
    long fileContentStartPosition = foundPosition + startOffset;
   
    /*
     * Copying file content from the object stream to the filestream
     */
    byte b;
    FileStream fileStream = new
    FileStream(fileLocation, FileMode.Create);
    objStream.Seek(fileContentStartPosition, SeekOrigin.Begin);
    for (long i = 1; i <= sizeOfMyObj - fileContentStartPosition - endOffset; i++)
    {
     b = (byte) objStream.ReadByte();
     fileStream.WriteByte(b);
    }
    objStream.Flush();
    objStream = null;
    fileStream.Flush();
    fileStream.Close();
    File.SetAttributes(fileLocation, FileAttributes.Normal);
   }
  }

  catch (Exception error)
  {
   /* some code */ MessageBox.Show("Error: " + error.Message + "\n\n" + error.StackTrace.ToString());
  }
                                                                                 
 }
 
 /// <summary>
 ///  Finds a string of characters in a stream.
 /// </summary>
 /// <param name="textToFind">
 ///  String to find, e.g. ".ZIP"
 /// </param>
 /// <param name="caseSensitive">
 ///  Specifies whether the search will be case sensitive (true) or not (false)
 /// </param>
 /// <param name="startPos">
 ///  Position in stream to start search from
 /// </param>
 /// <param name="sourceStream">
 ///  Source stream where the search will be performed
 /// </param>
 /// <returns>
 ///  Position in stream where the search string is found. Or (-1) if the string is not found
 /// </returns>
 private long FindInStream (string textToFind, bool caseSensitive, long startPos, MemoryStream sourceStream)
 {
  /*
   * Converts sought string to an array of chars and gets the array length
   */
  char[] charsToFind = textToFind.ToCharArray();
  long textToFindLength = textToFind.Length;

  /*
   * Converts source stream to an array of bytes and gets the array length
   */
  byte[] bufArray = sourceStream.ToArray();
  long bufLength = bufArray.Length;
  
  /*
   * Performs string search in the source stream
   */
  for (long x = startPos; x < bufLength; x++)
  {
   bool firstByteFound;
   /*
    * Finds out whether the first byte of the string is found in the stream
    */
   if (caseSensitive)
   {
    firstByteFound = (bufArray[x] == (byte) charsToFind[0]);
   }
   else
   {
    firstByteFound = (bufArray[x] == (byte) (charsToFind[0].ToString().ToLower())[0]) ||
     (bufArray[x] == (byte) (charsToFind[0].ToString().ToUpper())[0]);
   }
       
   if (firstByteFound)
   {
    long y;

    /*
     * Finds out whether all next bytes of the string follow the first found byte in the stream
     */
    for (y = 1; y < textToFindLength; y++)
    {
     if ((x + y) >= bufLength)
      break;
     bool nextByteFound;
     if (caseSensitive)
     {
      nextByteFound = (bufArray[x + y] != (byte) charsToFind[ y]);
     }
     else
     {
      nextByteFound = (bufArray [x + y] != (byte) (charsToFind[ y].ToString().ToLower())[0] &&
       bufArray [x + y] != (byte) (charsToFind[ y].ToString().ToUpper())[0]);
     }
     if (nextByteFound)
      break;
    }
    if (y == textToFindLength)
    {
     sourceStream.Flush();
     return (x);
    }
   }
  }
  sourceStream.Flush();
  return (-1);
 }

Sorry for any formatting problems in the code above. I would be happy to use the OLEObject.Object property, so if anybody can show me a code sample that makes use of OLEObject.Object, please share your experience.

I also would like to add that I don't like the solution I proposed above - I am not sure what this "Native" clipboard format is, whether it is same on all platforms and in all locales. I guessed about the "Native" format structure by just comparing the Native format data with the original data (original file). So my approach is at least not that serious. It is also very resource consuming - transferring potentially huge data to clipboard and streams (in case the embedded file is very big). But I could not find how to cope with this problem better.

Glad if my solution helps anybody else,

Anton 




Tonioant
Anton Rapoport

Hi Anton

Have you tried asking in the Excel.Programming newsgroup whether anyone there knows how to do this? Don't mention .NET or anything like that. Just ask if anyone knows how to automate that command to save a generic embedded object.

I'm guessing this is the Windows Packager. It must have an OLE server interface, or Excel couldn't use it. Question is: can you get to it through the Excel OM. Some of the Excel programming folks are pretty well-versed in VB-lore and the Windows API. One have them may have figured it out - and if they give you the VB-COM info, you may be able to run with that...




-- Cindy Meister (Word MVP)
Cindy Meister

Cindy, thank you for your reply.

Frankly saying my solution is just an attempt to somehow solve the situation with my current level of knowledge (i.e. I would not admit that the initial problem is solved now). So thanks a lot for giving me a piece of advice.

Just please correct me: When you recommended to visit the Excel.Programming newsgroup did you actually mean http://www.microsoft.com/office/community/en-us/default.mspx?dg=microsoft.public.excel.programming&lang=en&cr=US ? If not, please tell me where this newsgroup lives.

And I admit that you are right - the embedded OLE object can be manually saved in Excel by some "Object Packager" which is a separate window and on which I also could not find much information. It has a pretty valuable capability to save package content to a file - i.e. what I am trying to perform from my C#.NET code.

thanks and nice day,

Anton




Tonioant
Anton Rapoport

Hi Anton

Yes, that's the group I mean :-) Just don't mention C# to them, there: they're "allergic" to .NET.




-- Cindy Meister (Word MVP)
Cindy Meister

Thanks, Cindy.

I will try not to annoy VBA guys with .NET questions. I have not asked my question there yet, but I have an additional comment on the "non-standard" solution I proposed. The solution worked correctly on both Windows XP Pro sp2 and on Windows 2000 sp4. So this strange "Native" clipboard format somehow works well. Of course the solution I proposed is not pure OLE object saving solution, it is rather a way to extract a file from clipboard and save it to disk, provided the file was originally copied to clipboard. So it has nothing to do with this forum at all :-)

Anton




Tonioant
Anton Rapoport

An update from the Excel.Programming forum:

The only suggestion I got from the Excel.Programming guys was similar to my own "invention" I described earlier - the approach was to copy OLE object to clipboard and to retrieve it back from clipboard and then to cut the file content:

http://www.microsoft.com/office/community/en-us/default.mspx?dg=microsoft.public.excel.programming&tid=59d62e82-86fb-4f49-9e77-b1740cd1948a&cat=en-us-excel&lang=en&cr=US&sloc=en-us&m=1&p=1&mid=b71faa1b-f121-43c7-b6df-704f56d5221b

If no more replies follow, I will stop my investigation of the problem.

Anton




Tonioant
Anton Rapoport
reply 9

You can use google to search for other answers

 

More Articles

• Add definition functionality to Word
• How to figure out what my problem is...
• Action Pane disappears
• Is it possible to move an InlineShape around the document?
• Office Word 2003 custom property problem
• Starting Excel with C#
• How to create and use Word object of office 2003 in VB.NET 2005 o...
• deleting table inside a Word XmlNode deletes part of the node wit...
• VSTO Outlook 2007 Form Region Question
• Information Bridge Framework
Bookmark and Share
Welcome to Bokebb   New Update  
 

New Articles

• VSTO as a web-based report generator - i
• How to read data from external applicati
• Customizing Outlook programatically
• VSTO MSDN Library
• drop of outlook messages or attachments,
• word document revisions
• deleting CustomDocumentProperties
• Inspectors doing there own thing?
• Problem in Charts
• Blank row in Microsoft Excel
• adding column heading from a list with V
• Does Microsoft Office InfoPath 2003 Tool
• Setting data source programmatically for
• regarding actionspane
• Null values displayed as 0 in OWC Pivot

Hot Articles

• How to span columns selected in owc11 sp
• VSTO Advice: Need your design advice for
• Application level addin
• Printing a MS Access report
• Calling Macro from VSTO
• How do I deploy other apps w/ Outlook So
• VSTO Outlook AddIn and ASP Page
• show excel chart on a form in excel 2000.
• adding data in a new added sheet in exce
• Visual Studio 2005 for Office over VS St
• Bookmarks in VSTO
• Problem using ServerDocument.AddCustomiz
• Integrated Windows authentication and MS
• Mapping CustomXML data into ContentContr
• Infopath TaskPane Addin

Recommend Articles

• Import external data in Excel w/ Visual
• c# automation - copy excel table to a po
• Drag & Drop + VBA Marco Question
• Excel question
• Grouping columns in Pivot Table
• How to Add a new Menu-entry in MSProject?
• VSTO 2005 standard form question + upgra
• VSTO & MS Access
• How to hide print dialog while printing
• VSTO Properties Settings are the same as
• Any update on the shim for COM Add-in su
• Can a program created for outlook 2003 r
• VSTO Solution is not running on client's
• better to store VSTO docs on hard drive
• Daily wise resource allocation in MS Pro