Converting an OLE Image Object from MS Access for use in .NET
I'm working on redeveloping an Access based system into c#.net, however when MS went from office 2003 to office 2007 they removed the picture editor within access - which meant that previously stored pictures would no longer display in the system. The guys at the company did a hack that basically saved the images with VBA using excel in the background (I can get more information if you need it) but basically it meant the access image controls could still be used (Object bound frames).
Howeve开发者_高级运维r, I now have the problem of trying to display these in .NET applications, and after countless days of trying different ways of manipulating the byte array I'm close to giving up. I have tried at least 8 different suggested solutions and each one ends with a 'Parameter not recognised' exception when doing Image.fromStream(). Below is the code which has got me the closest so far:
private void imageExtractTest()
{
LogOnDataSetTableAdapters.QueriesTableAdapter qa =
new LogOnDataSetTableAdapters.QueriesTableAdapter();
object docO = qa.GetLogonImage();
if (docO == null || !(docO is byte[]))
{
return;
}
byte[] doc = (byte[])docO;
MemoryStream ms = new MemoryStream();
ms.Write(doc, 0, doc.Length);
int firstByte;
int secondByte;
ms.Seek(0, SeekOrigin.Begin);
firstByte = ms.ReadByte();
secondByte = ms.ReadByte();
if (firstByte != 0x15 && secondByte != 0x1C)
{
//ErrorResponse("Stored object is not an Access File.");
return;
}
int fileTypeLoc = 20; // begin of the file type
short offset; // end of the file type
byte[] buffer = new byte[2];
ms.Read(buffer, 0, 2);
offset = BitConverter.ToInt16(buffer, 0);
long seekTotal = 0;
seekTotal += offset;
string docType = String.Empty;
for (int i = fileTypeLoc; i < offset; i++)
{
docType += (char)doc[i];
}
//if I query docType now I get 'Picture\0\0'
// magic eight bytes 01 05 00 00 03 00 00 00
ms.Seek(seekTotal, SeekOrigin.Begin);
buffer = new byte[8];
ms.Read(buffer, 0, 8);
seekTotal += 8;
// Second offset to move to
buffer = new byte[4];
ms.Read(buffer, 0, 4);
seekTotal += 4;
long offset2 = BitConverter.ToInt32(buffer, 0);
seekTotal += offset2;
ms.Seek(seekTotal, SeekOrigin.Begin);
// eight empty bytes
buffer = new byte[8];
ms.Read(buffer, 0, 8);
seekTotal += 8;
// next n bytes are the length of the file
buffer = new byte[4];
ms.Read(buffer, 0, 4);
seekTotal += 4;
long fileByteLength = BitConverter.ToInt32(buffer, 0);
// next N bytes are the file
byte[] data = new byte[fileByteLength];
// store file bytes in data buffer
ms.Read(data, 0, Convert.ToInt32(fileByteLength));
MemoryStream imageStream = new MemoryStream(data);
Image test = Image.FromStream(imageStream);
}
This code was adapted from here, I didn't need the various doctypes identification as I'm only dealing with images, however the image type could be any number of things - jpg, bmp, gif, png etc.
I've also tried saving the outputted byte array but I've had no luck viewing that either. But when I point access to the database and get it to view it, everything is fine. Also the .NET Crystal Report designer is able to get these images some how - so they must be in there somewhere...
Has anyone got any ideas?
Marlon
Trying to retrieve an MS-access OLE image field from .NET is way more headache than it is worth. There is some good discussion and info about this topic in this post.
Ultimately, your best, and easiest, solution to be successful with this is to use your working viewing method to save these images as separate files, then import those files into the database as BLOB fields, rather than image fields. Then you can easily read them into .NET.
It is not C# code but here is an Delphi example of a way to solve this problem.
It uses the IOLEObject to draw whatever is stored instead of trying to read out the raw data. Steps:
- Read the Access Header in front of the OLE Object
- Read out the OLE1 stream
- Convert the OLE1 stream to an OLE2 IStorage Object
- Use OLELoad to "run" the OLE Ojbect.
- Call OLEDraw to draw out the image to a canvas of your choice.
In my case the following function worked. The data are stored by a VB6 application.
public static byte[] ConvertOleBytesToRawBytes(byte[] oleBytes)
{
// The default encoding is in my case - Western European (Windows), Code Page 1252
return Encoding.Convert(Encoding.Unicode, Encoding.Default, (byte[])oleBytes);
}
Try this KB http://support.microsoft.com/kb/317701 article from microsoft. It contains information on how to access the image blob from access and display that in winforms application.
I needed to do the exact same thing for some 1600 object of different extension types. In my case it was a legacy database that had literally been used for decades. Over the years many different types of files had been added via an OLE Object frame. Some of the items that looked like "Images" turned out to be Word documents with an embedded image... no telling what other file types were in there? All I know is that I researched and tried different extraction methods for more than a week. Even all of Steven Leban's extraction tools like OLEtoDisk, A2KExportOLEtoJPEG, and SaveOLEtoBitmap. Each of which would extract some images... but their was no one size fits all... it was a mess!
In the end I ended up performing an automated screen shot of each image via VBA using the method below. While this may not have been the most ideal it worked for every file type. However the screen shot method captures a screen shot of the full screen. Once I had all of them extracted I then had to use PhotoShop to do another automated process to batch crop all of the photos. Not ideal but it worked!
Private Sub CaptureAllImages()
On Error Resume Next
Me.RecordsetClone.MoveFirst
Do While Not Me.RecordsetClone.EOF
Me.Bookmark = Me.RecordsetClone.Bookmark
Call Pause(2)
Call SaveClip2Bit("C:\Users\agriggs\Desktop\Parts Images\MasterPart_" & Me.MasterPartNumber & ".bmp")
Me.RecordsetClone.MoveNext
Loop
End Sub
Public Function Pause(NumberOfSeconds As Variant)
On Error GoTo Error_GoTo
Dim PauseTime As Variant
Dim start As Variant
Dim Elapsed As Variant
PauseTime = NumberOfSeconds
start = Timer
Elapsed = 0
Do While Timer < start + PauseTime
Elapsed = Elapsed + 1
If Timer = 0 Then
' Crossing midnight
PauseTime = PauseTime - Elapsed
start = 0
Elapsed = 0
End If
DoEvents
Loop
Exit_GoTo:
On Error GoTo 0
Exit Function
Error_GoTo:
Debug.Print Err.Number, Err.Description, Erl
GoTo Exit_GoTo
End Function
I tried a couple different screen shot modules but found that the SaveClip2Bit worked the best. Lastly I added a common Pause function to insure the image got saved to disk before moving on to the next. As you can imagine 1600 images took a long time to extract but I can now but this project to rest!
精彩评论