📄 datacache.c
字号:
/*
* OLE 2 Data cache
*
* Copyright 1999 Francis Beaudet
* Copyright 2000 Abey George
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* NOTES:
* The OLE2 data cache supports a whole whack of
* interfaces including:
* IDataObject, IPersistStorage, IViewObject2,
* IOleCache2 and IOleCacheControl.
*
* Most of the implementation details are taken from: Inside OLE
* second edition by Kraig Brockschmidt,
*
* NOTES
* - This implementation of the datacache will let your application
* load documents that have embedded OLE objects in them and it will
* also retrieve the metafile representation of those objects.
* - This implementation of the datacache will also allow your
* application to save new documents with OLE objects in them.
* - The main thing that it doesn't do is allow you to activate
* or modify the OLE objects in any way.
* - I haven't found any good documentation on the real usage of
* the streams created by the data cache. In particular, How to
* determine what the XXX stands for in the stream name
* "\002OlePresXXX". It appears to just be a counter.
* - Also, I don't know the real content of the presentation stream
* header. I was able to figure-out where the extent of the object
* was stored and the aspect, but that's about it.
*/
#include <stdarg.h>
#include <string.h>
#define COBJMACROS
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "wine/unicode.h"
#include "ole2.h"
#include "wine/list.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(ole);
/****************************************************************************
* PresentationDataHeader
*
* This structure represents the header of the \002OlePresXXX stream in
* the OLE object strorage.
*/
typedef struct PresentationDataHeader
{
/* clipformat:
* - standard clipformat:
* DWORD length = 0xffffffff;
* DWORD cfFormat;
* - or custom clipformat:
* DWORD length;
* CHAR format_name[length]; (null-terminated)
*/
DWORD unknown3; /* 4, possibly TYMED_ISTREAM */
DVASPECT dvAspect;
DWORD lindex;
DWORD tymed;
DWORD unknown7; /* 0 */
DWORD dwObjectExtentX;
DWORD dwObjectExtentY;
DWORD dwSize;
} PresentationDataHeader;
typedef struct DataCacheEntry
{
struct list entry;
/* format of this entry */
FORMATETC fmtetc;
/* the clipboard format of the data */
CLIPFORMAT data_cf;
/* cached data */
STGMEDIUM stgmedium;
/*
* This storage pointer is set through a call to
* IPersistStorage_Load. This is where the visual
* representation of the object is stored.
*/
IStorage *storage;
/* connection ID */
DWORD id;
/* dirty flag */
BOOL dirty;
/* stream number (-1 if not set ) */
unsigned short stream_number;
} DataCacheEntry;
/****************************************************************************
* DataCache
*/
struct DataCache
{
/*
* List all interface VTables here
*/
const IDataObjectVtbl* lpVtbl;
const IUnknownVtbl* lpvtblNDIUnknown;
const IPersistStorageVtbl* lpvtblIPersistStorage;
const IViewObject2Vtbl* lpvtblIViewObject;
const IOleCache2Vtbl* lpvtblIOleCache2;
const IOleCacheControlVtbl* lpvtblIOleCacheControl;
/*
* Reference count of this object
*/
LONG ref;
/*
* IUnknown implementation of the outer object.
*/
IUnknown* outerUnknown;
/*
* The user of this object can setup ONE advise sink
* connection with the object. These parameters describe
* that connection.
*/
DWORD sinkAspects;
DWORD sinkAdviseFlag;
IAdviseSink* sinkInterface;
IStorage *presentationStorage;
/* list of cache entries */
struct list cache_list;
/* last id assigned to an entry */
DWORD last_cache_id;
/* dirty flag */
BOOL dirty;
};
typedef struct DataCache DataCache;
/*
* Here, I define utility macros to help with the casting of the
* "this" parameter.
* There is a version to accommodate all of the VTables implemented
* by this object.
*/
static inline DataCache *impl_from_IDataObject( IDataObject *iface )
{
return (DataCache *)((char*)iface - FIELD_OFFSET(DataCache, lpVtbl));
}
static inline DataCache *impl_from_NDIUnknown( IUnknown *iface )
{
return (DataCache *)((char*)iface - FIELD_OFFSET(DataCache, lpvtblNDIUnknown));
}
static inline DataCache *impl_from_IPersistStorage( IPersistStorage *iface )
{
return (DataCache *)((char*)iface - FIELD_OFFSET(DataCache, lpvtblIPersistStorage));
}
static inline DataCache *impl_from_IViewObject2( IViewObject2 *iface )
{
return (DataCache *)((char*)iface - FIELD_OFFSET(DataCache, lpvtblIViewObject));
}
static inline DataCache *impl_from_IOleCache2( IOleCache2 *iface )
{
return (DataCache *)((char*)iface - FIELD_OFFSET(DataCache, lpvtblIOleCache2));
}
static inline DataCache *impl_from_IOleCacheControl( IOleCacheControl *iface )
{
return (DataCache *)((char*)iface - FIELD_OFFSET(DataCache, lpvtblIOleCacheControl));
}
static const char * debugstr_formatetc(const FORMATETC *formatetc)
{
return wine_dbg_sprintf("{ cfFormat = 0x%x, ptd = %p, dwAspect = %d, lindex = %d, tymed = %d }",
formatetc->cfFormat, formatetc->ptd, formatetc->dwAspect,
formatetc->lindex, formatetc->tymed);
}
/*
* Prototypes for the methods of the DataCache class.
*/
static DataCache* DataCache_Construct(REFCLSID clsid,
LPUNKNOWN pUnkOuter);
static HRESULT DataCacheEntry_OpenPresStream(DataCacheEntry *This,
IStream **pStm);
static void DataCacheEntry_Destroy(DataCacheEntry *This)
{
list_remove(&This->entry);
if (This->storage)
IStorage_Release(This->storage);
HeapFree(GetProcessHeap(), 0, This->fmtetc.ptd);
ReleaseStgMedium(&This->stgmedium);
HeapFree(GetProcessHeap(), 0, This);
}
static void DataCache_Destroy(
DataCache* ptrToDestroy)
{
DataCacheEntry *cache_entry, *next_cache_entry;
TRACE("()\n");
if (ptrToDestroy->sinkInterface != NULL)
{
IAdviseSink_Release(ptrToDestroy->sinkInterface);
ptrToDestroy->sinkInterface = NULL;
}
LIST_FOR_EACH_ENTRY_SAFE(cache_entry, next_cache_entry, &ptrToDestroy->cache_list, DataCacheEntry, entry)
DataCacheEntry_Destroy(cache_entry);
/*
* Free the datacache pointer.
*/
HeapFree(GetProcessHeap(), 0, ptrToDestroy);
}
static DataCacheEntry *DataCache_GetEntryForFormatEtc(DataCache *This, const FORMATETC *formatetc)
{
DataCacheEntry *cache_entry;
LIST_FOR_EACH_ENTRY(cache_entry, &This->cache_list, DataCacheEntry, entry)
{
/* FIXME: also compare DVTARGETDEVICEs */
if ((!cache_entry->fmtetc.cfFormat || !formatetc->cfFormat || (formatetc->cfFormat == cache_entry->fmtetc.cfFormat)) &&
(formatetc->dwAspect == cache_entry->fmtetc.dwAspect) &&
(formatetc->lindex == cache_entry->fmtetc.lindex) &&
(!cache_entry->fmtetc.tymed || !formatetc->tymed || (formatetc->tymed == cache_entry->fmtetc.tymed)))
return cache_entry;
}
return NULL;
}
/* checks that the clipformat and tymed are valid and returns an error if they
* aren't and CACHE_S_NOTSUPPORTED if they are valid, but can't be rendered by
* DataCache_Draw */
static HRESULT check_valid_clipformat_and_tymed(CLIPFORMAT cfFormat, DWORD tymed)
{
if (!cfFormat || !tymed ||
(cfFormat == CF_METAFILEPICT && tymed == TYMED_MFPICT) ||
(cfFormat == CF_BITMAP && tymed == TYMED_GDI) ||
(cfFormat == CF_DIB && tymed == TYMED_HGLOBAL) ||
(cfFormat == CF_ENHMETAFILE && tymed == TYMED_ENHMF))
return S_OK;
else if (tymed == TYMED_HGLOBAL)
return CACHE_S_FORMATETC_NOTSUPPORTED;
else
{
WARN("invalid clipformat/tymed combination: %d/%d\n", cfFormat, tymed);
return DV_E_TYMED;
}
}
static HRESULT DataCache_CreateEntry(DataCache *This, const FORMATETC *formatetc, DataCacheEntry **cache_entry)
{
HRESULT hr;
hr = check_valid_clipformat_and_tymed(formatetc->cfFormat, formatetc->tymed);
if (FAILED(hr))
return hr;
if (hr == CACHE_S_FORMATETC_NOTSUPPORTED)
TRACE("creating unsupported format %d\n", formatetc->cfFormat);
*cache_entry = HeapAlloc(GetProcessHeap(), 0, sizeof(**cache_entry));
if (!*cache_entry)
return E_OUTOFMEMORY;
(*cache_entry)->fmtetc = *formatetc;
if (formatetc->ptd)
{
(*cache_entry)->fmtetc.ptd = HeapAlloc(GetProcessHeap(), 0, formatetc->ptd->tdSize);
memcpy((*cache_entry)->fmtetc.ptd, formatetc->ptd, formatetc->ptd->tdSize);
}
(*cache_entry)->stgmedium.tymed = TYMED_NULL;
(*cache_entry)->stgmedium.pUnkForRelease = NULL;
(*cache_entry)->storage = NULL;
(*cache_entry)->id = This->last_cache_id++;
(*cache_entry)->dirty = TRUE;
(*cache_entry)->stream_number = -1;
list_add_tail(&This->cache_list, &(*cache_entry)->entry);
return hr;
}
/************************************************************************
* DataCache_FireOnViewChange
*
* This method will fire an OnViewChange notification to the advise
* sink registered with the datacache.
*
* See IAdviseSink::OnViewChange for more details.
*/
static void DataCache_FireOnViewChange(
DataCache* this,
DWORD aspect,
LONG lindex)
{
TRACE("(%p, %x, %d)\n", this, aspect, lindex);
/*
* The sink supplies a filter when it registers
* we make sure we only send the notifications when that
* filter matches.
*/
if ((this->sinkAspects & aspect) != 0)
{
if (this->sinkInterface != NULL)
{
IAdviseSink_OnViewChange(this->sinkInterface,
aspect,
lindex);
/*
* Some sinks want to be unregistered automatically when
* the first notification goes out.
*/
if ( (this->sinkAdviseFlag & ADVF_ONLYONCE) != 0)
{
IAdviseSink_Release(this->sinkInterface);
this->sinkInterface = NULL;
this->sinkAspects = 0;
this->sinkAdviseFlag = 0;
}
}
}
}
/* Helper for DataCacheEntry_OpenPresStream */
static BOOL DataCache_IsPresentationStream(const STATSTG *elem)
{
/* The presentation streams have names of the form "\002OlePresXXX",
* where XXX goes from 000 to 999. */
static const WCHAR OlePres[] = { 2,'O','l','e','P','r','e','s' };
LPCWSTR name = elem->pwcsName;
return (elem->type == STGTY_STREAM)
&& (strlenW(name) == 11)
&& (strncmpW(name, OlePres, 8) == 0)
&& (name[8] >= '0') && (name[8] <= '9')
&& (name[9] >= '0') && (name[9] <= '9')
&& (name[10] >= '0') && (name[10] <= '9');
}
static HRESULT read_clipformat(IStream *stream, CLIPFORMAT *clipformat)
{
DWORD length;
HRESULT hr;
ULONG read;
*clipformat = 0;
hr = IStream_Read(stream, &length, sizeof(length), &read);
if (hr != S_OK || read != sizeof(length))
return DV_E_CLIPFORMAT;
if (length == -1)
{
DWORD cf;
hr = IStream_Read(stream, &cf, sizeof(cf), 0);
if (hr != S_OK || read != sizeof(cf))
return DV_E_CLIPFORMAT;
*clipformat = cf;
}
else
{
char *format_name = HeapAlloc(GetProcessHeap(), 0, length);
if (!format_name)
return E_OUTOFMEMORY;
hr = IStream_Read(stream, format_name, length, &read);
if (hr != S_OK || read != length || format_name[length - 1] != '\0')
{
HeapFree(GetProcessHeap(), 0, format_name);
return DV_E_CLIPFORMAT;
}
*clipformat = RegisterClipboardFormatA(format_name);
HeapFree(GetProcessHeap(), 0, format_name);
}
return S_OK;
}
static HRESULT write_clipformat(IStream *stream, CLIPFORMAT clipformat)
{
DWORD length;
HRESULT hr;
if (clipformat < 0xc000)
length = -1;
else
length = GetClipboardFormatNameA(clipformat, NULL, 0);
hr = IStream_Write(stream, &length, sizeof(length), NULL);
if (FAILED(hr))
return hr;
if (clipformat < 0xc000)
{
DWORD cf = clipformat;
hr = IStream_Write(stream, &cf, sizeof(cf), NULL);
}
else
{
char *format_name = HeapAlloc(GetProcessHeap(), 0, length);
if (!format_name)
return E_OUTOFMEMORY;
GetClipboardFormatNameA(clipformat, format_name, length);
hr = IStream_Write(stream, format_name, length, NULL);
HeapFree(GetProcessHeap(), 0, format_name);
}
return hr;
}
/************************************************************************
* DataCacheEntry_OpenPresStream
*
* This method will find the stream for the given presentation. It makes
* no attempt at fallback.
*
* Param:
* this - Pointer to the DataCache object
* drawAspect - The aspect of the object that we wish to draw.
* pStm - A returned stream. It points to the beginning of the
* - presentation data, including the header.
*
* Errors:
* S_OK The requested stream has been opened.
* OLE_E_BLANK The requested stream could not be found.
* Quite a few others I'm too lazy to map correctly.
*
* Notes:
* Algorithm: Scan the elements of the presentation storage, looking
* for presentation streams. For each presentation stream,
* load the header and check to see if the aspect matches.
*
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -