IStream, IPicture and ADODB.Stream
|
|
Thread rating:  |
Alexander Mueller - 25 Nov 2007 13:45 GMT Hi
from what I have read from the docs, IPicture implements IPersistentStream and thus IStream, with its Read/Write methods. I want to save a StdPicture-object into a MSSQL-Server- BLOB, with as little memory-copying as possible. If it is possible, what i don't believe, the best solution is to somehow make the buffer location of the ADODB-Stream point to the buffer of the IPicture-object, which will probaly not work because both have ther own buffer which cannot be 'redirected' in a pointer operation.
Does anyone know, if the ADODB.Stream-object also implements the OLE-IStream interface and if there is way to directly write the Picture data into a ADODB-Strwam object.
Something like: oAdodbStream.Write oPictureStream.Read()
I need to know how i can call 'Read' from the StdPicture-object. In C++ QueryInterface should do the trick, but I don't have a defintion of IStream in VB, nor do I know how to call QueryInterface in VB.
Is there a way to do this or at least to do some kind of memcopy from the Picture to the ADOB.Stream without file/disc-Access?
Any hints very much appreciated.
MfG, Alex
Sorry for x-posting to m.p.vb.winapi m.p.vb.winapi.graphics m.p.vb.com m.p.vb.general.discussion
Fup goes to m.p.vb.com
Schmidt - 25 Nov 2007 18:41 GMT > I want to save a StdPicture-object into a MSSQL-Server- > BLOB, with as little memory-copying as possible. Why, is something like this not fast enough?
Public Sub SavePicture(SP as StdPicture, Fld as ADODB.Field) Dim PB as New PropertyBag PB.WriteProperty "StdPic", SP Fld.Value = PB.Content End Sub
Olaf
Alexander Mueller - 25 Nov 2007 22:55 GMT Schmidt schrieb:
Hi Olaf
thx for your reply
>> I want to save a StdPicture-object into a MSSQL-Server- >> BLOB, with as little memory-copying as possible. [quoted text clipped - 6 lines] > Fld.Value = PB.Content > End Sub I only tested it the other way around, reading the bytes of a blob into a stdpicture with a propbag, and it didn't work, for unknown reasons.
'did not work for me...: Public Function BytesToPicture(Bytes() As Byte) As StdPicture
With New PropertyBag .Contents = Bytes Set BytesToPicture = .ReadProperty("StdPic") End With
End Function
So i didn't test the solution you proposed. But i will give a try now, its pretty straight forward. Results will follow tomorrow.
MfG, Alex
Schmidt - 26 Nov 2007 02:41 GMT > I only tested it the other way around, reading the > bytes of a blob into a stdpicture with a propbag, > and it didn't work, for unknown reasons. The reasons are, that you will have to convert this "original content" into "bytes of a known format" that *you* define in a first step. e.g. the Image-bytes inside the Blobs of Northwind-DB have to be converted without using VBs PropertyBag. After that conversion (whatever it takes, to bring this bytes into a form wich is useful with VBs Image-Objects) you are free to define your own "format". Be it the Property-Bag-approach, or be it another approach using DIB-Bytes (bot may be coupled with your own Byte-Array-compression - e.g. using zlib) or saving the bytes directly in a known Image-Format with losless compression (e.g. *.gif or *.png) or with lossy compression (e.g. *.jpg). It's all up to you.
In my dhSQLite-Samples I've included a method - how to convert the Nwind.mdb-Image-Blobs to StdPicture in a first step using temporary Bitmap-Files, but with a bit more effort you could also convert them without using the disk.
In short: simply create your own Byte-Format for your Image-Blobs. Don't try to be compatible to a "mysterious MS-Image-Blob-standard". Losless compression for your own Image-Byte-Format is also a good idea, because it helps, to keep the Blob-Content the DB will have to manage as low as possible and this would also help, to fill up the appropriate Recordsets in a DB-request more quickly (not to mention their transfer-times over a network).
Olaf
Alexander Mueller - 26 Nov 2007 14:04 GMT Schmidt schrieb:
Hi Olaf
thx again for your most valuable reply.
> "Alexander Mueller" <millerax@hotmail.com> schrieb: > >> I only tested it the other way around, reading the >> bytes of a blob into a stdpicture with a propbag, >> and it didn't work, for unknown reasons.
> The reasons are, that you will have to convert this > "original content" into "bytes of a known format" > that *you* define in a first step. The byte-format that i defined is actually a 1:1 image of the picture-file as saved on disk. No conversion / compression / reinterpretation in between. I read a file in binary mode and AppendChunk it to the blob and that's it. Seems The PropertyBag-approach expects different kind of data.
> e.g. the Image-bytes inside the Blobs of Northwind-DB > have to be converted without using VBs PropertyBag. [quoted text clipped - 7 lines] > compression (e.g. *.gif or *.png) or with lossy compression > (e.g. *.jpg). It's all up to you. Sounds intersting and also a bit complicated. Compression is of course worth looking at when it comes to saving large amounts of bin-data. However in first approach i'd prefer to save and load the data "as is", e.g. exactly the same way as saved on disk.
> In my dhSQLite-Samples I've included a method - how > to convert the Nwind.mdb-Image-Blobs to StdPicture > in a first step using temporary Bitmap-Files, but with > a bit more effort you could also convert them without > using the disk. I already managed this using OLE-API calls CreateStreamOnHGlobal and OleLoadPicture see news:<47477bcf$0$13112$9b4e6d93@newsspool2.arcor-online.net>
What i am looking for is the way back. CreateStreamOnHGlobal receives the adress of a byte-array and creates a IStream-object out of it. OleCreatePicture receives a IStream and makes it a StdPicture.
I need something that retrieves an IStream from a StdPicture and then gets the bytes from the IStream. Or directly the bytes from StdPicture, in the same format and alignment, as if the they were saved to disk.
> In short: simply create your own Byte-Format for your > Image-Blobs. Don't try to be compatible to a "mysterious > MS-Image-Blob-standard". How do I create such a Byte-Format?
> Losless compression for your own Image-Byte-Format is also > a good idea, because it helps, to keep the Blob-Content the > DB will have to manage as low as possible and this would > also help, to fill up the appropriate Recordsets in a DB-request > more quickly (not to mention their transfer-times over a network). Surely lossless compression sound like a very good idea, but any client that reads the blob must know the compression algorithm / codec used and be able to decode it. Anyway i am not the one that decides, I'll tell it my boss, replication is also an important feature of his software, and reducing bandwidth needs is always welcome, so may be he's interested in.
Thx for your reply again
MfG, Alex
Alexander Mueller - 26 Nov 2007 19:24 GMT Schmidt schrieb:
>> I only tested it the other way around, reading the >> bytes of a blob into a stdpicture with a propbag, >> and it didn't work, for unknown reasons.
> The reasons are, that you will have to convert this > "original content" into "bytes of a known format" > that *you* define in a first step. Sorry for not directly replying to your proposals. I have another question related to this subject and I think you know the answer since I've seen and been using your implementations of IEnumVariant. The question is:
How can I implement a COM-Interface with VB?
Do I have to declare a Type that has the same layout as the VTable of the interface and then link the methods to the types members using AdressOf?
Back to the IStream-stuff: The first scenario was not actually correct, IPicture does not inherit from IStream, but it allows to be saved into an IStream by its SaveAsFile-method, where File actually means IStream-object. The C-syteld Signature is:
HRESULT SaveAsFile( IStream * pstream, //Pointer to stream where picture writes its data BOOL fSaveMemCopy , //Indicates whether to save the picture in memory LONG* pcbSize //Receives a pointer to the number of bytes written //to stream );
The help also says:
"IPicture::SaveAsFile
Saves the picture's data into a stream in the same format that it would save itself into a file. Bitmaps use the BMP file format, metafiles the WMF format, and icons the ICO format."
So this stream-data will have the same byte-format, as when writing the picture to a file with VB SavePicture (which is proably a wrapper around SaveAsFile i write into the blob when saving a file.
And IStream has a Read method that returns the data:
HRESULT Read( [out] void* pv, //pointer to the buffer stream data is read into. [in] ULONG cb, [out] ULONG* pcbRead );
So all i have to do is to either implement IStream or find an existing implementation. Do you or anyone else know if there is an default OLE-implementation in one of the OLE-DLLs?
MfG, Alex
Schmidt - 03 Dec 2007 22:18 GMT > How can I implement a COM-Interface with VB? > > Do I have to declare a Type that has the same layout as > the VTable of the interface and then link the methods > to the types members using AdressOf? No, the Type in LightWeight-COM-Objects is only needed to store a *pointer* to the VTable (together with Private Var-stuff), not the VTable-Entries itself
Finally time to get "The Curland-Book" I'd say. ;-)
> Back to the IStream-stuff: >... > So all i have to do is to either implement IStream or find an > existing implementation. Do you or anyone else know if there is > an default OLE-implementation in one of the OLE-DLLs? Here's an example, wich is based on code of Eduardo Morcillo (just google for his website) and wich also depends on his great OleLib.tlb (wich BTW also contains the IStream-Interface).
The Code demonstrates, what is basically going on under the hood of VBs PropertyBag - and it shows you how to use the OleLib-defined Types (e.g. IStream).
'***Into a form Option Explicit
Private Sub Form_Load() Dim B() As Byte 'let's serialize a Test-(Std-)Picture (loaded from disk) B = WriteObject(LoadPicture("c:\sometest.bmp")) Debug.Print "Obj serialized into: " & UBound(B) + 1 & " Bytes."
'now the opposite direction Set Me.Picture = ReadObject(B) End Sub
Public Function WriteObject(ByVal Obj As Object) As Byte() Dim CLSID As UUID, oPSI As IPersistStreamInit, oPS As IPersistStream Dim oStrm As olelib.IStream, Stat As STATSTG, B() As Byte, Bytes& If Obj Is Nothing Then WriteClassStm oStrm, CLSID ' Write the empty CLSID Else On Error Resume Next Set oStrm = CreateStreamOnHGlobal(0, True) If Err Then On Error GoTo 0 Err.Raise vbObjectError, , "Cannot create Stream-Object" End If Set oPS = Obj 'Query for IPersistStream If Err.Number = 0 Then oPS.GetClassID CLSID WriteClassStm oStrm, CLSID oPS.Save oStrm, 0 Else Err.Clear Set oPSI = Obj 'Query for IPersistStreamInit If Err.Number = 0 Then oPSI.GetClassID CLSID WriteClassStm oStrm, CLSID oPSI.Save oStrm, 0 Else On Error GoTo 0 Err.Raise 5, , "Oject class isn't persistable: " & TypeName(Obj) End If End If End If
If Err Then On Error GoTo 0 Err.Raise vbObjectError Or 1, , "The object can't be saved." Else oStrm.Stat Stat Bytes = Stat.cbSize * 10000 If Bytes > 0 Then ReDim B(0 To Bytes - 1) oStrm.Seek 0, 0 oStrm.Read B(0), Bytes WriteObject = B End If End If End Function
Public Function ReadObject(B() As Byte) As Object Dim CLSID As UUID, IID_Null As UUID, IID_IUnknown As UUID Dim oPSI As IPersistStreamInit, oPS As IPersistStream, lRes As Long Dim Bytes&, IU As olelib.IUnknown, oStrm As olelib.IStream On Error Resume Next Bytes = UBound(B) - LBound(B) + 1 If Bytes = 0 Then Err.Clear: Exit Function
Set oStrm = CreateStreamOnHGlobal(0, True) If Err Then On Error GoTo 0 Err.Raise vbObjectError, , "Cannot create Stream-Object" End If oStrm.Write B(LBound(B)), Bytes oStrm.Seek 0, 0 ReadClassStm oStrm, CLSID ' Read the CLSID
If IsEqualGUID(CLSID, IID_Null) Then Exit Function
IID_IUnknown.Data4(0) = &HC0: IID_IUnknown.Data4(7) = &H46 'init lRes = CoCreateInstance(CLSID, Nothing, CLSCTX_INPROC_HANDLER Or _ CLSCTX_INPROC_SERVER Or CLSCTX_LOCAL_SERVER Or CLSCTX_REMOTE_SERVER, _ IID_IUnknown, IU)
If lRes = S_OK Then Set oPS = IU ' Query for IPersistStream If Err.Number = 0 Then oPS.Load oStrm Else Err.Clear Set oPSI = IU ' Query for IPersistStreamInit If Err.Number = 0 Then oPSI.Load oStrm 'Load the object data End If End If Set ReadObject = IU Else Err.Raise 429 End If End Function
Olaf
Alexander Mueller - 14 Dec 2007 00:50 GMT Schmidt schrieb:
>> How can I implement a COM-Interface with VB? >> [quoted text clipped - 20 lines] > the hood of VBs PropertyBag - and it shows you how to > use the OleLib-defined Types (e.g. IStream). Hi Olaf
You are my hero! Thank you so much for your code. It works great with bmp jpg and gif.
A problem I had to solve on my own is that I need the bytes returned to fully equal the bytes of the pic-file read into a byte-array (because that is the way all pix are saved in our DB..)
The byte-array returned by WriteObject is somewhat different (slightly more bytes then the org-file and different data) so if I write them to file, the file is not a valid pic.
I finally got the code that i originally thought of to work that uses IPicture.SaveToFile (which actually saves into a Stream not a file)
Here's my code (based on your example, also using emorcillos olelib.tlb)
Thx once again, Alex
Private Sub Form_Load() Dim B() As Byte
Dim f%
'let's serialize a Test-(Std-)Picture (loaded from disk) B = WriteObjectFB(LoadPicture("D:\Media\Bilder\Deix\weekly23.bmp")) Debug.Print "Obj serialized into: " & UBound(B) + 1 & " Bytes."
'Create TestFile to check the byte format f = FreeFile Open "C:\testcopy.bmp" For Binary Access Write As #f Put #f, , B Close #f
'now the opposite direction Set Me.Picture = ReadObjectFB(B)
End Sub
'FB == FileBytes (same binary format as file on disk) Public Function WriteObjectFB(oIPic As IPicture) As Byte()
Dim oStrm As olelib.IStream, B() As Byte, Bytes&
If oIPic Is Nothing Then On Error GoTo 0 Err.Raise vbObjectError, , "Picture is empty" End If
On Error Resume Next Set oStrm = CreateStreamOnHGlobal(hGlobal, True)
If Err Then On Error GoTo 0 Err.Raise vbObjectError, , "Cannot create Stream-object" End If
oIPic.SaveAsFile ByVal ObjPtr(oStrm), True, Bytes
If Err.Number = 0 Then
If Bytes > 0 Then ReDim B(0 To Bytes - 1) oStrm.Seek 0, 0 oStrm.Read B(0), Bytes WriteObjectFB = B End If
Else
On Error GoTo 0 Err.Raise 5, , "Cannot save picture to stream"
End If
End Function
Public Function ReadObjectFB(PictureData() As Byte) As StdPicture
Dim IID_IPicture As UUID Dim oStream As IStream
Call CLSIDFromString(IIDSTR_IPicture, IID_IPicture)
'CreateStreamOnHGlobal from oletlb Set oStream = CreateStreamOnHGlobal(VarPtr(PictureData(0)), 1) If Not oStream Is Nothing Then
' OLE IPicture-Objekt erstellen Set ReadObjectFB = _ OleLoadPicture(oStream, 0, 0, IID_IPicture)
Else
On Error GoTo 0 Err.Raise vbObjectError, , _ "Cannot create stream-object from picture-data" End If
End Function
> '***Into a form > Option Explicit [quoted text clipped - 97 lines] > > Olaf Schmidt - 14 Dec 2007 13:35 GMT > Thank you so much for your code. > It works great with bmp jpg and gif. Glad you found your own way from it - and as said, the credits should go to Eduardo Morcillo, my example was mainly based on his PropertyBag-Demo-Code.
Olaf
|
|
|