我可以使用内存Stream成功将图像保存到Access数据库表。我保存图像的列具有数据类型“OLEObject”。当我打开表格时,我在此图像列中看到“长二进制数据”值。问题是,当我在此数据库中创建任何报表时,我无法看到图像。
当我在MSPaint和Ctrl + A中打开图像文件然后按Ctrl + C复制所有图形然后将其粘贴到Access数据库列中时,它就成功粘贴了。现在将值显示为“位图图像”。当我双击图像列值时,它会在MSPaint中打开图像。并且在Report中也可以使用。我想知道如何使图像直接保存为“位图图像”类型?我正在使用VB .Net Framework 4.0,Visual Studio 2012。
答案 0 :(得分:2)
在我找到的任何文献中都没有记录MS Access OLE字段结构。
下面显示的类AccessOLEBitmapConverter
是基于How To Retrieve Bitmap from Access and Display It in Web Page源代码中引用的文章(System.Drawing.ImageConverter Class)的取证黑客攻击的结果方法GetBitmapStream
。
基于一些黑客行为,我发现该字段包含两个结构:1)标题结构,以及2)EmbeddedObject结构。经过多次黑客攻击后,我发现Presentation
结构的EmbeddedObject
字段可以用12个字节的序列替换,只要标题结构中的name
字段填充了六个字节的序列({33,0,255,255,255,255})。请参阅代码以获取更多信息。
Public Class AccessOLEBitmapConverter
Const nullByte As Byte = 0
Const OLEVersion As Int32 = 1281
Private Shared arrayInvalid As String = "Source array is invalid"
Public Shared Function ToByteArray(sourceBitmap As Bitmap) As Byte()
Dim ret As Byte() = Nothing
Using ms As New IO.MemoryStream(5000)
EmbeddedObjectHeader.Write(ms)
EmbeddedObject.Write(ms, sourceBitmap)
ret = ms.ToArray()
End Using
Return ret
End Function
Public Shared Function ToBitmap(bytes As Byte()) As Bitmap
Dim ret As Bitmap = Nothing
If bytes Is Nothing Then Throw New ArgumentNullException("Argument ""bytes"" cannot be null")
If bytes.Length < 14 Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Using ms As New IO.MemoryStream(bytes, False),
br As New IO.BinaryReader(ms, System.Text.Encoding.ASCII)
Dim signature As Int16 = br.ReadInt16()
If signature <> &H1C15 Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim headersize As Int16 = br.ReadInt16() ' 47
If bytes.Length < headersize Then Throw New ArgumentOutOfRangeException(arrayInvalid)
ms.Position = headersize
' Start ObjectHeader
If ms.Position = bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim OLEVersion As UInt32 = br.ReadUInt32
If ms.Position = bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim FormatID As UInt32 = br.ReadUInt32
If ms.Position = bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim ClassName As String = LengthPrefixedAnsiString.Read(br)
If ms.Position = bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim TopicName As String = LengthPrefixedAnsiString.Read(br)
If ms.Position = bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim ItemName As String = LengthPrefixedAnsiString.Read(br)
' End ObjectHeader
If ms.Position = bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim NativeDataSize As Int32 = br.ReadInt32
If (ms.Position + NativeDataSize) > bytes.Length Then Throw New ArgumentOutOfRangeException(arrayInvalid)
Dim NativeData As Byte() = br.ReadBytes(NativeDataSize)
Dim msImage As New IO.MemoryStream(NativeData)
ret = CType(Image.FromStream(msImage), Bitmap)
End Using
Return ret
End Function
Private Class EmbeddedObjectHeader
' ref: How To Retrieve Bitmap from Access and Display It in Web Page
' https://support.microsoft.com/en-us/kb/175261
Friend Shared Sub Write(ms As System.IO.MemoryStream)
Const signature As Int16 = &H1C15
Const headersize As Int16 = 47S
Const objectType As Int16 = 2S
Const nameLen As Int16 = 0S
Const classLen As Int16 = 13S
Const nameOffset As Int16 = 14S
Const classOffset As Int16 = 20S
Const classType As String = "Bitmap Image"
Const hdrClassName As String = "Paint.Picture"
Using bw As New IO.BinaryWriter(ms, System.Text.Encoding.ASCII, True)
With bw
.Write(signature)
.Write(headersize)
.Write(objectType)
.Write(nameLen)
.Write(classLen)
.Write(nameOffset)
.Write(classOffset)
' Even though this offset is declared as being for the 'name' field and
' the 'name' field always has a zero length, these six bytes must be present
' to allow the resultant byte array to be identified as a BitMap Image by Access
ms.Position = nameOffset
.Write(New Byte() {33, 0, 255, 255, 255, 255})
ms.Position = classOffset
.Write(classType.ToCharArray())
.Write(nullByte)
.Write(hdrClassName.ToCharArray())
.Write(nullByte)
End With
End Using
End Sub
End Class ' EmbeddedObjectHeader
Private Class EmbeddedObject
' ref: https://msdn.microsoft.com/en-us/library/dd942053.aspx
'Header (variable): This MUST be an ObjectHeader (section 2.2.4).
' The FormatID field of the Header MUST be set to 0x00000002.
'NativeDataSize (4 bytes): This MUST be set to the size of the NativeData field, in bytes.
'NativeData (variable): This must be an array of bytes that contains the native data.
'Presentation (variable): This MUST be a MetaFilePresentationObject (section 2.2.2.1),
' a BitmapPresentationObject (section 2.2.2.2), a DIBPresentationObject (section 2.2.2.3),
' a StandardClipboardFormatPresentationObject (section 2.2.3.2), or
' a RegisteredClipboardFormatPresentationObject (section 2.2.3.3).
Friend Shared Sub Write(ms As System.IO.Stream, sourceBitmap As Bitmap)
Using bw As New IO.BinaryWriter(ms, System.Text.Encoding.ASCII, True)
With bw
ObjectHeader.Write(ms)
' Determine and write the NativeDataSize and NativeData fields
Using imgStream As New IO.MemoryStream(20000)
sourceBitmap.Save(imgStream, System.Drawing.Imaging.ImageFormat.Bmp)
Dim NativeDataSize As Int32 = CInt(imgStream.Length)
.Write(NativeDataSize)
.Write(imgStream.ToArray)
End Using
' At this point the 'Presentation' variable should be written.
' However, Bitmap files copied from Windows Explorer and pasted into
' the MS Access OLE field have only 12 bytes written and this allows for
' a much smaller size to be stored as the 'Presentation' variable appears to
' duplicate the NativeData field. Adding the Bitmap via the 'Insert Object'
' dialog creates a storage nearly twice that of this method.
' The first 4 bytes correspond to the integer value for OLEVersion.
.Write(OLEVersion)
' The next 4 bytes are always zero.
.Write(0I)
' The next byte (position 8) appears variable and its value does not appear
' to impact using the 'BitMap Image' in Access. So write a zero.
.Write(nullByte)
' The final three bytes appear to be constant {173, 5, 254}
.Write(New Byte() {173, 5, 254})
.Flush()
End With
End Using
End Sub
End Class 'EmbeddedObject
Private Class ObjectHeader
Friend Shared Sub Write(ms As System.IO.Stream)
Const FormatID As Int32 = 2
Const ClassName As String = "PBrush"
Const TopicName As String = ""
Const ItemName As String = ""
Using bw As New IO.BinaryWriter(ms, System.Text.Encoding.ASCII, True)
With bw
.Write(OLEVersion)
.Write(FormatID)
LengthPrefixedAnsiString.Write(bw, ClassName)
LengthPrefixedAnsiString.Write(bw, TopicName)
LengthPrefixedAnsiString.Write(bw, ItemName)
End With
End Using
End Sub
End Class ' ObjectHeader
Private Class LengthPrefixedAnsiString
' ref : https://msdn.microsoft.com/en-us/library/dd942554.aspx
' This structure specifies a null-terminated American National Standards Institute (ANSI) character set string.
' Length (4 bytes): This MUST be set to the number of ANSI characters in the String field,
' including the terminating null character. Length MUST be set to 0x00000000 to indicate an empty string.
' String (variable): This MUST be a null-terminated ANSI string.
Const nullChar As Byte = 0
Friend Shared Function Read(br As IO.BinaryReader) As String
Dim ret As String = String.Empty
Dim length As Int32 = br.ReadInt32
If length > 0 Then
Dim chars As Char() = br.ReadChars(length)
ret = New String(chars)
End If
Return ret
End Function
Friend Shared Sub Write(bw As IO.BinaryWriter, val As String)
If val.Length = 0 Then
bw.Write(0I)
Else
bw.Write(val.Length + 1)
bw.Write(val.ToCharArray)
bw.Write(nullChar)
End If
End Sub
End Class 'LengthPrefixedAnsiString
End Class
使用示例:
' To obtain a Byte() to store a Bitmap to MS Access
Dim bm As Bitmap = CType(Image.FromFile("SomeBitmapFile.bmp"), Bitmap)
Dim bytesToStoreInAccess As Byte() = AccessOLEBitmapConverter.ToByteArray(bm)
' To retrieve a Bitmap from an Access Bitmap Image field.
Dim bytesFromBitmapImageField As Byte() ' set this equal to the field data
Dim bmRetrieved As Bitmap = AccessOLEBitmapConverter.ToBitmap(bytesFromBitmapImageField)
此代码已在Windows 10计算机上使用MS Access 2007成功测试,以将位图存储到Access OLE字段,以生成可在Access报告中显示的MS Access Bitmap Image
。