ole32: Added a test showing OLE storage's IStorage/IStream refcounting
[wine] / dlls / ole32 / hglobalstream.c
1 /*
2  * HGLOBAL Stream implementation
3  *
4  * This file contains the implementation of the stream interface
5  * for streams contained supported by an HGLOBAL pointer.
6  *
7  * Copyright 1999 Francis Beaudet
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24 #include "config.h"
25
26 #include <assert.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <string.h>
31
32 #define COBJMACROS
33 #define NONAMELESSUNION
34 #define NONAMELESSSTRUCT
35
36 #include "windef.h"
37 #include "winbase.h"
38 #include "winuser.h"
39 #include "objbase.h"
40 #include "ole2.h"
41 #include "winerror.h"
42 #include "winreg.h"
43 #include "winternl.h"
44
45 #include "wine/debug.h"
46
47 WINE_DEFAULT_DEBUG_CHANNEL(storage);
48
49 /****************************************************************************
50  * HGLOBALStreamImpl definition.
51  *
52  * This class imlements the IStream inteface and represents a stream
53  * supported by an HGLOBAL pointer.
54  */
55 struct HGLOBALStreamImpl
56 {
57   const IStreamVtbl *lpVtbl;   /* Needs to be the first item in the struct
58                           * since we want to cast this in an IStream pointer */
59
60   /*
61    * Reference count
62    */
63   LONG               ref;
64
65   /*
66    * Support for the stream
67    */
68   HGLOBAL supportHandle;
69
70   /*
71    * This flag is TRUE if the HGLOBAL is destroyed when the stream
72    * is finally released.
73    */
74   BOOL    deleteOnRelease;
75
76   /*
77    * Helper variable that contains the size of the stream
78    */
79   ULARGE_INTEGER     streamSize;
80
81   /*
82    * This is the current position of the cursor in the stream
83    */
84   ULARGE_INTEGER     currentPosition;
85 };
86
87 typedef struct HGLOBALStreamImpl HGLOBALStreamImpl;
88
89 /***
90  * This is the destructor of the HGLOBALStreamImpl class.
91  *
92  * This method will clean-up all the resources used-up by the given HGLOBALStreamImpl
93  * class. The pointer passed-in to this function will be freed and will not
94  * be valid anymore.
95  */
96 static void HGLOBALStreamImpl_Destroy(HGLOBALStreamImpl* This)
97 {
98   TRACE("(%p)\n", This);
99
100   /*
101    * Release the HGlobal if the constructor asked for that.
102    */
103   if (This->deleteOnRelease)
104   {
105     GlobalFree(This->supportHandle);
106     This->supportHandle=0;
107   }
108
109   /*
110    * Finally, free the memory used-up by the class.
111    */
112   HeapFree(GetProcessHeap(), 0, This);
113 }
114
115 /***
116  * This implements the IUnknown method AddRef for this
117  * class
118  */
119 static ULONG WINAPI HGLOBALStreamImpl_AddRef(
120                 IStream* iface)
121 {
122   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
123   return InterlockedIncrement(&This->ref);
124 }
125
126 /***
127  * This implements the IUnknown method QueryInterface for this
128  * class
129  */
130 static HRESULT WINAPI HGLOBALStreamImpl_QueryInterface(
131                   IStream*     iface,
132                   REFIID         riid,        /* [in] */
133                   void**         ppvObject)   /* [iid_is][out] */
134 {
135   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
136
137   /*
138    * Perform a sanity check on the parameters.
139    */
140   if (ppvObject==0)
141     return E_INVALIDARG;
142
143   /*
144    * Initialize the return parameter.
145    */
146   *ppvObject = 0;
147
148   /*
149    * Compare the riid with the interface IDs implemented by this object.
150    */
151   if (memcmp(&IID_IUnknown, riid, sizeof(IID_IUnknown)) == 0)
152   {
153     *ppvObject = (IStream*)This;
154   }
155   else if (memcmp(&IID_IStream, riid, sizeof(IID_IStream)) == 0)
156   {
157     *ppvObject = (IStream*)This;
158   }
159
160   /*
161    * Check that we obtained an interface.
162    */
163   if ((*ppvObject)==0)
164     return E_NOINTERFACE;
165
166   /*
167    * Query Interface always increases the reference count by one when it is
168    * successful
169    */
170   HGLOBALStreamImpl_AddRef(iface);
171
172   return S_OK;
173 }
174
175 /***
176  * This implements the IUnknown method Release for this
177  * class
178  */
179 static ULONG WINAPI HGLOBALStreamImpl_Release(
180                 IStream* iface)
181 {
182   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
183   ULONG newRef;
184
185   newRef = InterlockedDecrement(&This->ref);
186
187   /*
188    * If the reference count goes down to 0, perform suicide.
189    */
190   if (newRef==0)
191   {
192     HGLOBALStreamImpl_Destroy(This);
193   }
194
195   return newRef;
196 }
197
198 /***
199  * This method is part of the ISequentialStream interface.
200  *
201  * If reads a block of information from the stream at the current
202  * position. It then moves the current position at the end of the
203  * read block
204  *
205  * See the documentation of ISequentialStream for more info.
206  */
207 static HRESULT WINAPI HGLOBALStreamImpl_Read(
208                   IStream*     iface,
209                   void*          pv,        /* [length_is][size_is][out] */
210                   ULONG          cb,        /* [in] */
211                   ULONG*         pcbRead)   /* [out] */
212 {
213   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
214
215   void* supportBuffer;
216   ULONG bytesReadBuffer;
217   ULONG bytesToReadFromBuffer;
218
219   TRACE("(%p, %p, %ld, %p)\n", iface,
220         pv, cb, pcbRead);
221
222   /*
223    * If the caller is not interested in the nubmer of bytes read,
224    * we use another buffer to avoid "if" statements in the code.
225    */
226   if (pcbRead==0)
227     pcbRead = &bytesReadBuffer;
228
229   /*
230    * Using the known size of the stream, calculate the number of bytes
231    * to read from the block chain
232    */
233   bytesToReadFromBuffer = min( This->streamSize.u.LowPart - This->currentPosition.u.LowPart, cb);
234
235   /*
236    * Lock the buffer in position and copy the data.
237    */
238   supportBuffer = GlobalLock(This->supportHandle);
239
240   memcpy(pv, (char *) supportBuffer+This->currentPosition.u.LowPart, bytesToReadFromBuffer);
241
242   /*
243    * Move the current position to the new position
244    */
245   This->currentPosition.u.LowPart+=bytesToReadFromBuffer;
246
247   /*
248    * Return the number of bytes read.
249    */
250   *pcbRead = bytesToReadFromBuffer;
251
252   /*
253    * Cleanup
254    */
255   GlobalUnlock(This->supportHandle);
256
257   /*
258    * The function returns S_OK if the buffer was filled completely
259    * it returns S_FALSE if the end of the stream is reached before the
260    * buffer is filled
261    */
262   if(*pcbRead == cb)
263     return S_OK;
264
265   return S_FALSE;
266 }
267
268 /***
269  * This method is part of the ISequentialStream interface.
270  *
271  * It writes a block of information to the stream at the current
272  * position. It then moves the current position at the end of the
273  * written block. If the stream is too small to fit the block,
274  * the stream is grown to fit.
275  *
276  * See the documentation of ISequentialStream for more info.
277  */
278 static HRESULT WINAPI HGLOBALStreamImpl_Write(
279                   IStream*     iface,
280                   const void*    pv,          /* [size_is][in] */
281                   ULONG          cb,          /* [in] */
282                   ULONG*         pcbWritten)  /* [out] */
283 {
284   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
285
286   void*          supportBuffer;
287   ULARGE_INTEGER newSize;
288   ULONG          bytesWritten = 0;
289
290   TRACE("(%p, %p, %ld, %p)\n", iface,
291         pv, cb, pcbWritten);
292
293   /*
294    * If the caller is not interested in the number of bytes written,
295    * we use another buffer to avoid "if" statements in the code.
296    */
297   if (pcbWritten == 0)
298     pcbWritten = &bytesWritten;
299
300   if (cb == 0)
301   {
302     return S_OK;
303   }
304   else
305   {
306     newSize.u.HighPart = 0;
307     newSize.u.LowPart = This->currentPosition.u.LowPart + cb;
308   }
309
310   /*
311    * Verify if we need to grow the stream
312    */
313   if (newSize.u.LowPart > This->streamSize.u.LowPart)
314   {
315     /* grow stream */
316     HRESULT hr = IStream_SetSize(iface, newSize);
317     if (FAILED(hr))
318     {
319       ERR("IStream_SetSize failed with error 0x%08lx\n", hr);
320       return hr;
321     }
322   }
323
324   /*
325    * Lock the buffer in position and copy the data.
326    */
327   supportBuffer = GlobalLock(This->supportHandle);
328
329   memcpy((char *) supportBuffer+This->currentPosition.u.LowPart, pv, cb);
330
331   /*
332    * Move the current position to the new position
333    */
334   This->currentPosition.u.LowPart+=cb;
335
336   /*
337    * Return the number of bytes read.
338    */
339   *pcbWritten = cb;
340
341   /*
342    * Cleanup
343    */
344   GlobalUnlock(This->supportHandle);
345
346   return S_OK;
347 }
348
349 /***
350  * This method is part of the IStream interface.
351  *
352  * It will move the current stream pointer according to the parameters
353  * given.
354  *
355  * See the documentation of IStream for more info.
356  */
357 static HRESULT WINAPI HGLOBALStreamImpl_Seek(
358                   IStream*      iface,
359                   LARGE_INTEGER   dlibMove,         /* [in] */
360                   DWORD           dwOrigin,         /* [in] */
361                   ULARGE_INTEGER* plibNewPosition) /* [out] */
362 {
363   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
364
365   ULARGE_INTEGER newPosition;
366
367   TRACE("(%p, %lx%08lx, %ld, %p)\n", iface, dlibMove.u.HighPart,
368         dlibMove.u.LowPart, dwOrigin, plibNewPosition);
369
370   /*
371    * The file pointer is moved depending on the given "function"
372    * parameter.
373    */
374   switch (dwOrigin)
375   {
376     case STREAM_SEEK_SET:
377       newPosition.u.HighPart = 0;
378       newPosition.u.LowPart = 0;
379       break;
380     case STREAM_SEEK_CUR:
381       newPosition = This->currentPosition;
382       break;
383     case STREAM_SEEK_END:
384       newPosition = This->streamSize;
385       break;
386     default:
387       return STG_E_INVALIDFUNCTION;
388   }
389
390   /*
391    * Move the actual file pointer
392    * If the file pointer ends-up after the end of the stream, the next Write operation will
393    * make the file larger. This is how it is documented.
394    */
395   if (dlibMove.QuadPart < 0 && newPosition.QuadPart < -dlibMove.QuadPart) return STG_E_INVALIDFUNCTION;
396
397   newPosition.QuadPart = RtlLargeIntegerAdd(newPosition.QuadPart, dlibMove.QuadPart);
398
399   if (plibNewPosition) *plibNewPosition = newPosition;
400   This->currentPosition = newPosition;
401
402   return S_OK;
403 }
404
405 /***
406  * This method is part of the IStream interface.
407  *
408  * It will change the size of a stream.
409  *
410  * TODO: Switch from small blocks to big blocks and vice versa.
411  *
412  * See the documentation of IStream for more info.
413  */
414 static HRESULT WINAPI HGLOBALStreamImpl_SetSize(
415                                      IStream*      iface,
416                                      ULARGE_INTEGER  libNewSize)   /* [in] */
417 {
418   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
419   HGLOBAL supportHandle;
420
421   TRACE("(%p, %ld)\n", iface, libNewSize.u.LowPart);
422
423   /*
424    * As documented.
425    */
426   if (libNewSize.u.HighPart != 0)
427     return STG_E_INVALIDFUNCTION;
428
429   if (This->streamSize.u.LowPart == libNewSize.u.LowPart)
430     return S_OK;
431
432   /*
433    * Re allocate the HGlobal to fit the new size of the stream.
434    */
435   supportHandle = GlobalReAlloc(This->supportHandle, libNewSize.u.LowPart, 0);
436
437   if (supportHandle == 0)
438     return STG_E_MEDIUMFULL;
439
440   This->supportHandle = supportHandle;
441   This->streamSize.u.LowPart = libNewSize.u.LowPart;
442
443   return S_OK;
444 }
445
446 /***
447  * This method is part of the IStream interface.
448  *
449  * It will copy the 'cb' Bytes to 'pstm' IStream.
450  *
451  * See the documentation of IStream for more info.
452  */
453 static HRESULT WINAPI HGLOBALStreamImpl_CopyTo(
454                                     IStream*      iface,
455                                     IStream*      pstm,         /* [unique][in] */
456                                     ULARGE_INTEGER  cb,           /* [in] */
457                                     ULARGE_INTEGER* pcbRead,      /* [out] */
458                                     ULARGE_INTEGER* pcbWritten)   /* [out] */
459 {
460   HRESULT        hr = S_OK;
461   BYTE           tmpBuffer[128];
462   ULONG          bytesRead, bytesWritten, copySize;
463   ULARGE_INTEGER totalBytesRead;
464   ULARGE_INTEGER totalBytesWritten;
465
466   TRACE("(%p, %p, %ld, %p, %p)\n", iface, pstm,
467         cb.u.LowPart, pcbRead, pcbWritten);
468
469   /*
470    * Sanity check
471    */
472   if ( pstm == 0 )
473     return STG_E_INVALIDPOINTER;
474
475   totalBytesRead.u.LowPart = totalBytesRead.u.HighPart = 0;
476   totalBytesWritten.u.LowPart = totalBytesWritten.u.HighPart = 0;
477
478   /*
479    * use stack to store data temporarly
480    * there is surely more performant way of doing it, for now this basic
481    * implementation will do the job
482    */
483   while ( cb.u.LowPart > 0 )
484   {
485     if ( cb.u.LowPart >= 128 )
486       copySize = 128;
487     else
488       copySize = cb.u.LowPart;
489
490     IStream_Read(iface, tmpBuffer, copySize, &bytesRead);
491
492     totalBytesRead.u.LowPart += bytesRead;
493
494     IStream_Write(pstm, tmpBuffer, bytesRead, &bytesWritten);
495
496     totalBytesWritten.u.LowPart += bytesWritten;
497
498     /*
499      * Check that read & write operations were succesfull
500      */
501     if (bytesRead != bytesWritten)
502     {
503       hr = STG_E_MEDIUMFULL;
504       break;
505     }
506
507     if (bytesRead!=copySize)
508       cb.u.LowPart = 0;
509     else
510       cb.u.LowPart -= bytesRead;
511   }
512
513   /*
514    * Update number of bytes read and written
515    */
516   if (pcbRead)
517   {
518     pcbRead->u.LowPart = totalBytesRead.u.LowPart;
519     pcbRead->u.HighPart = totalBytesRead.u.HighPart;
520   }
521
522   if (pcbWritten)
523   {
524     pcbWritten->u.LowPart = totalBytesWritten.u.LowPart;
525     pcbWritten->u.HighPart = totalBytesWritten.u.HighPart;
526   }
527   return hr;
528 }
529
530 /***
531  * This method is part of the IStream interface.
532  *
533  * For streams supported by HGLOBALS, this function does nothing.
534  * This is what the documentation tells us.
535  *
536  * See the documentation of IStream for more info.
537  */
538 static HRESULT WINAPI HGLOBALStreamImpl_Commit(
539                   IStream*      iface,
540                   DWORD         grfCommitFlags)  /* [in] */
541 {
542   return S_OK;
543 }
544
545 /***
546  * This method is part of the IStream interface.
547  *
548  * For streams supported by HGLOBALS, this function does nothing.
549  * This is what the documentation tells us.
550  *
551  * See the documentation of IStream for more info.
552  */
553 static HRESULT WINAPI HGLOBALStreamImpl_Revert(
554                   IStream* iface)
555 {
556   return S_OK;
557 }
558
559 /***
560  * This method is part of the IStream interface.
561  *
562  * For streams supported by HGLOBALS, this function does nothing.
563  * This is what the documentation tells us.
564  *
565  * See the documentation of IStream for more info.
566  */
567 static HRESULT WINAPI HGLOBALStreamImpl_LockRegion(
568                   IStream*       iface,
569                   ULARGE_INTEGER libOffset,   /* [in] */
570                   ULARGE_INTEGER cb,          /* [in] */
571                   DWORD          dwLockType)  /* [in] */
572 {
573   return S_OK;
574 }
575
576 /*
577  * This method is part of the IStream interface.
578  *
579  * For streams supported by HGLOBALS, this function does nothing.
580  * This is what the documentation tells us.
581  *
582  * See the documentation of IStream for more info.
583  */
584 static HRESULT WINAPI HGLOBALStreamImpl_UnlockRegion(
585                   IStream*       iface,
586                   ULARGE_INTEGER libOffset,   /* [in] */
587                   ULARGE_INTEGER cb,          /* [in] */
588                   DWORD          dwLockType)  /* [in] */
589 {
590   return S_OK;
591 }
592
593 /***
594  * This method is part of the IStream interface.
595  *
596  * This method returns information about the current
597  * stream.
598  *
599  * See the documentation of IStream for more info.
600  */
601 static HRESULT WINAPI HGLOBALStreamImpl_Stat(
602                   IStream*     iface,
603                   STATSTG*     pstatstg,     /* [out] */
604                   DWORD        grfStatFlag)  /* [in] */
605 {
606   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
607
608   memset(pstatstg, 0, sizeof(STATSTG));
609
610   pstatstg->pwcsName = NULL;
611   pstatstg->type     = STGTY_STREAM;
612   pstatstg->cbSize   = This->streamSize;
613
614   return S_OK;
615 }
616
617 static HRESULT WINAPI HGLOBALStreamImpl_Clone(
618                   IStream*     iface,
619                   IStream**    ppstm) /* [out] */
620 {
621   ULARGE_INTEGER dummy;
622   LARGE_INTEGER offset;
623   HRESULT hr;
624   HGLOBALStreamImpl* const This=(HGLOBALStreamImpl*)iface;
625   TRACE(" Cloning %p (deleteOnRelease=%d seek position=%ld)\n",iface,This->deleteOnRelease,(long)This->currentPosition.QuadPart);
626   hr=CreateStreamOnHGlobal(This->supportHandle, FALSE, ppstm);
627   if(FAILED(hr))
628     return hr;
629   offset.QuadPart=(LONGLONG)This->currentPosition.QuadPart;
630   HGLOBALStreamImpl_Seek(*ppstm,offset,STREAM_SEEK_SET,&dummy);
631   return S_OK;
632 }
633
634 /*
635  * Virtual function table for the HGLOBALStreamImpl class.
636  */
637 static const IStreamVtbl HGLOBALStreamImpl_Vtbl =
638 {
639     HGLOBALStreamImpl_QueryInterface,
640     HGLOBALStreamImpl_AddRef,
641     HGLOBALStreamImpl_Release,
642     HGLOBALStreamImpl_Read,
643     HGLOBALStreamImpl_Write,
644     HGLOBALStreamImpl_Seek,
645     HGLOBALStreamImpl_SetSize,
646     HGLOBALStreamImpl_CopyTo,
647     HGLOBALStreamImpl_Commit,
648     HGLOBALStreamImpl_Revert,
649     HGLOBALStreamImpl_LockRegion,
650     HGLOBALStreamImpl_UnlockRegion,
651     HGLOBALStreamImpl_Stat,
652     HGLOBALStreamImpl_Clone
653 };
654
655 /******************************************************************************
656 ** HGLOBALStreamImpl implementation
657 */
658
659 /***
660  * This is the constructor for the HGLOBALStreamImpl class.
661  *
662  * Params:
663  *    hGlobal          - Handle that will support the stream. can be NULL.
664  *    fDeleteOnRelease - Flag set to TRUE if the HGLOBAL will be released
665  *                       when the IStream object is destroyed.
666  */
667 HGLOBALStreamImpl* HGLOBALStreamImpl_Construct(
668                 HGLOBAL  hGlobal,
669                 BOOL     fDeleteOnRelease)
670 {
671   HGLOBALStreamImpl* newStream;
672
673   newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(HGLOBALStreamImpl));
674
675   if (newStream!=0)
676   {
677     /*
678      * Set-up the virtual function table and reference count.
679      */
680     newStream->lpVtbl = &HGLOBALStreamImpl_Vtbl;
681     newStream->ref    = 0;
682
683     /*
684      * Initialize the support.
685      */
686     newStream->supportHandle = hGlobal;
687     newStream->deleteOnRelease = fDeleteOnRelease;
688
689     /*
690      * This method will allocate a handle if one is not supplied.
691      */
692     if (!newStream->supportHandle)
693     {
694       newStream->supportHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD |
695                                              GMEM_SHARE, 0);
696     }
697
698     /*
699      * Start the stream at the beginning.
700      */
701     newStream->currentPosition.u.HighPart = 0;
702     newStream->currentPosition.u.LowPart = 0;
703
704     /*
705      * Initialize the size of the stream to the size of the handle.
706      */
707     newStream->streamSize.u.HighPart = 0;
708     newStream->streamSize.u.LowPart  = GlobalSize(newStream->supportHandle);
709   }
710
711   return newStream;
712 }
713
714
715 /***********************************************************************
716  *           CreateStreamOnHGlobal     [OLE32.@]
717  */
718 HRESULT WINAPI CreateStreamOnHGlobal(
719                 HGLOBAL   hGlobal,
720                 BOOL      fDeleteOnRelease,
721                 LPSTREAM* ppstm)
722 {
723   HGLOBALStreamImpl* newStream;
724
725   newStream = HGLOBALStreamImpl_Construct(hGlobal,
726                                           fDeleteOnRelease);
727
728   if (newStream!=NULL)
729   {
730     return IUnknown_QueryInterface((IUnknown*)newStream,
731                                    &IID_IStream,
732                                    (void**)ppstm);
733   }
734
735   return E_OUTOFMEMORY;
736 }
737
738 /***********************************************************************
739  *           GetHGlobalFromStream     [OLE32.@]
740  */
741 HRESULT WINAPI GetHGlobalFromStream(IStream* pstm, HGLOBAL* phglobal)
742 {
743   HGLOBALStreamImpl* pStream;
744
745   if (pstm == NULL)
746     return E_INVALIDARG;
747
748   pStream = (HGLOBALStreamImpl*) pstm;
749
750   /*
751    * Verify that the stream object was created with CreateStreamOnHGlobal.
752    */
753   if (pStream->lpVtbl == &HGLOBALStreamImpl_Vtbl)
754     *phglobal = pStream->supportHandle;
755   else
756   {
757     *phglobal = 0;
758     return E_INVALIDARG;
759   }
760
761   return S_OK;
762 }