1426 lines
32 KiB
C++
1426 lines
32 KiB
C++
/*
|
|
* UTIL.C
|
|
*
|
|
* Purpose:
|
|
* Implementation of various useful utility functions
|
|
*
|
|
* Author:
|
|
* alexgo (4/25/95)
|
|
*
|
|
* Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved.
|
|
*/
|
|
|
|
#include "_common.h"
|
|
#include "_rtfconv.h"
|
|
|
|
ASSERTDATA
|
|
|
|
// Author revision color table
|
|
const COLORREF rgcrRevisions[] =
|
|
{
|
|
RGB(0, 0, 255),
|
|
RGB(0, 128, 0),
|
|
RGB(255, 0, 0),
|
|
RGB(0, 128, 128),
|
|
RGB(128, 0, 128),
|
|
RGB(0, 0, 128),
|
|
RGB(128, 0, 0),
|
|
RGB(255, 0, 255)
|
|
};
|
|
|
|
#if REVMASK != 7
|
|
#pragma message ("WARNING, Revision mask not equal to table!");
|
|
#endif
|
|
|
|
|
|
/*
|
|
* DuplicateHGlobal
|
|
*
|
|
* Purpose:
|
|
* duplicates the passed in hglobal
|
|
*/
|
|
|
|
HGLOBAL DuplicateHGlobal( HGLOBAL hglobal )
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSUTIL, TRCSCOPEINTERN, "DuplicateHGlobal");
|
|
|
|
UINT flags;
|
|
DWORD size;
|
|
HGLOBAL hNew;
|
|
BYTE * pSrc;
|
|
BYTE * pDest;
|
|
|
|
if( hglobal == NULL )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
flags = GlobalFlags(hglobal);
|
|
size = GlobalSize(hglobal);
|
|
hNew = GlobalAlloc(flags, size);
|
|
|
|
if( hNew )
|
|
{
|
|
pDest = (BYTE *)GlobalLock(hNew);
|
|
pSrc = (BYTE *)GlobalLock(hglobal);
|
|
|
|
if( pDest == NULL || pSrc == NULL )
|
|
{
|
|
GlobalUnlock(hNew);
|
|
GlobalUnlock(hglobal);
|
|
GlobalFree(hNew);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(pDest, pSrc, size);
|
|
|
|
GlobalUnlock(hNew);
|
|
GlobalUnlock(hglobal);
|
|
}
|
|
|
|
return hNew;
|
|
}
|
|
|
|
/*
|
|
* CountMatchingBits (*pA, *pB, n)
|
|
*
|
|
* @mfunc
|
|
* Count matching bit fields
|
|
*
|
|
* @comm
|
|
* This is used to help decide how good the match is between
|
|
* code page bit fields. Mainly for KB/font switching support.
|
|
*
|
|
* Author:
|
|
* Jon Matousek
|
|
*/
|
|
INT CountMatchingBits(
|
|
const DWORD *pA, //@parm Array A to be matched
|
|
const DWORD *pB, //@parm Array B to be matched
|
|
INT n) //@parm # DWORDs to be matched
|
|
{
|
|
TRACEBEGIN(TRCSUBSYSUTIL, TRCSCOPEINTERN, "CountMatchingBits");
|
|
//0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
static INT bitCount[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
|
|
INT c = 0; // Bit count to return
|
|
DWORD matchBits; // Next DWORD match
|
|
|
|
while(n--)
|
|
{
|
|
//matchBits = ~(*pA++ ^ *pB++); // 1 and 0's
|
|
matchBits = *pA++ & *pB++; // 1 only
|
|
for( ; matchBits; matchBits >>= 4) // Early out
|
|
c += bitCount[matchBits & 15];
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/*
|
|
* mysqrt(num)
|
|
*
|
|
* @func
|
|
* Pass in a number, and it will very simply calculate its integer
|
|
* square root. This isn't fast but it can be helpful.
|
|
*
|
|
*/
|
|
int mysqrt(int num)
|
|
{
|
|
for (int pow = 1;pow * pow < num; pow++)
|
|
{
|
|
}
|
|
return pow - 1;
|
|
}
|
|
|
|
/*
|
|
* FindPrimeLessThan(num)
|
|
*
|
|
* @func
|
|
* Pass in a number, and it will return the greatest prime number less
|
|
* than num using a simple Sieve of Eratosthenes.
|
|
*
|
|
* This is suitable for hashing functions.
|
|
* If you are worried about space, make this use bits.
|
|
* Also, we shouldn't waste our time with even numbers.
|
|
*
|
|
* @rdesc
|
|
* prime number, or 0 if couldn't alloc mem.
|
|
*/
|
|
int FindPrimeLessThan(int num)
|
|
{
|
|
BYTE *pb = new BYTE[num];
|
|
if (!pb)
|
|
return 0;
|
|
|
|
int squareroot = mysqrt(num);
|
|
|
|
//Mark all non-prime numbers.
|
|
for (int i = 2; i <= squareroot;)
|
|
{
|
|
for (int j = i * 2; j < num; j += i)
|
|
pb[j] = 1; //Mark as composite
|
|
|
|
i++;
|
|
while (pb[i] == 1) //Find next prime factor
|
|
i++;
|
|
}
|
|
|
|
//Find first prime walking backwards
|
|
for (i = num - 1; i >= 0 ;i--)
|
|
{
|
|
if (pb[i] == 0)
|
|
break;
|
|
}
|
|
|
|
delete[]pb;
|
|
return i;
|
|
}
|
|
|
|
|
|
//
|
|
// Object Stabilization classes
|
|
//
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::CSafeRefCount
|
|
//
|
|
// Synopsis: constructor for the safe ref count class
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: none
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CSafeRefCount::CSafeRefCount()
|
|
{
|
|
m_cRefs = 0;
|
|
m_cNest = 0;
|
|
m_fInDelete = FALSE;
|
|
m_fForceZombie = FALSE;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::CSafeRefCount (virtual)
|
|
//
|
|
// Synopsis:
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
CSafeRefCount::~CSafeRefCount()
|
|
{
|
|
Assert(m_cRefs == 0 && m_cNest == 0 && m_fInDelete == TRUE);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::SafeAddRef
|
|
//
|
|
// Synopsis: increments the reference count on the object
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: ULONG -- the reference count after the increment
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm: increments the reference count.
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
ULONG CSafeRefCount::SafeAddRef()
|
|
{
|
|
m_cRefs++;
|
|
|
|
//AssertSz(m_fInDelete == FALSE, "AddRef called on deleted object!");
|
|
|
|
// this *could* be really bad. If we are deleting the object,
|
|
// it means that during the destructor, somebody made an outgoing
|
|
// call eventually ended up with another addref to ourselves
|
|
// (even though all pointers to us had been 'Released').
|
|
//
|
|
// this is usually caused by code like the following:
|
|
// m_pFoo->Release();
|
|
// m_pFoo = NULL;
|
|
//
|
|
// If the the Release may cause Foo to be deleted, which may cause
|
|
// the object to get re-entered during Foo's destructor. However,
|
|
// 'this' object has not yet set m_pFoo to NULL, so it may
|
|
// try to continue to use m_pFoo.
|
|
//
|
|
// However, the May '94 aggregation rules REQUIRE this behaviour
|
|
// In your destructor, you have to addref the outer unknown before
|
|
// releasing cached interface pointers on your aggregatee. We
|
|
// can't put an assert here because we do this all the time now.
|
|
//
|
|
|
|
return m_cRefs;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::SafeRelease
|
|
//
|
|
// Synopsis: decrements the reference count on the object
|
|
//
|
|
// Effects: May delete the object!
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: ULONG -- the reference count after decrement
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm: decrements the reference count. If the reference count
|
|
// is zero AND the nest count is zero AND we are not currently
|
|
// trying to delete our object, then it is safe to delete.
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
ULONG CSafeRefCount::SafeRelease()
|
|
{
|
|
ULONG cRefs;
|
|
|
|
if( m_cRefs > 0 )
|
|
{
|
|
cRefs = --m_cRefs;
|
|
|
|
if( m_cRefs == 0 && m_cNest == 0 && m_fInDelete == FALSE )
|
|
{
|
|
m_fInDelete = TRUE;
|
|
delete this;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// somebody is releasing a non-addrefed pointer!!
|
|
AssertSz(0, "Release called on a non-addref'ed pointer!\n");
|
|
|
|
cRefs = 0;
|
|
}
|
|
|
|
return cRefs;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::IncrementNestCount
|
|
//
|
|
// Synopsis: increments the nesting count of the object
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: ULONG; the nesting count after increment
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes: The nesting count is the count of how many times an
|
|
// an object has been re-entered. For example, suppose
|
|
// somebody calls pFoo->Bar1(), which makes some calls that
|
|
// eventually call pFoo->Bar2();. On entrace to Bar2, the
|
|
// nest count of the object should be 2 (since the invocation
|
|
// of Bar1 is still on the stack above us).
|
|
//
|
|
// It is important to keep track of the nest count so we do
|
|
// not accidentally delete ourselves during a nested invocation.
|
|
// If we did, then when the stack unwinds to the original
|
|
// top level call, it could try to access a non-existent member
|
|
// variable and crash.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
ULONG CSafeRefCount::IncrementNestCount()
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
if( m_fInDelete )
|
|
{
|
|
TRACEWARNSZ("WARNING: CSafeRefCount, object "
|
|
"re-entered during delete!\n");
|
|
}
|
|
#endif
|
|
|
|
m_cNest++;
|
|
|
|
return m_cNest;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::DecrementNestCount
|
|
//
|
|
// Synopsis: decrements the nesting count and deletes the object
|
|
// (if necessary)
|
|
//
|
|
// Effects: may delete 'this' object!
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: ULONG, the nesting count after decrement
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm: decrements the nesting count. If the nesting count is zero
|
|
// AND the reference count is zero AND we are not currently
|
|
// trying to delete ourselves, then delete 'this' object
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
ULONG CSafeRefCount::DecrementNestCount()
|
|
{
|
|
ULONG cNest;
|
|
|
|
if( m_cNest > 0 )
|
|
{
|
|
cNest = --m_cNest;
|
|
|
|
if( m_cRefs == 0 && m_cNest == 0 && m_fInDelete == FALSE )
|
|
{
|
|
m_fInDelete = TRUE;
|
|
delete this;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// somebody forget to increment the nest count!!
|
|
AssertSz(0, "Unbalanced nest count!!");
|
|
|
|
cNest = 0;
|
|
}
|
|
|
|
return cNest;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::IsZombie
|
|
//
|
|
// Synopsis: determines whether or not the object is in a zombie state
|
|
// (i.e. all references gone, but we are still on the stack
|
|
// somewhere).
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: TRUE if in a zombie state
|
|
// FALSE otherwise
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm: If we are in the middle of a delete, or if the ref count
|
|
// is zero and the nest count is greater than zero, then we
|
|
// are a zombie
|
|
//
|
|
// History: dd-mmm-yy Author Comment
|
|
// 28-Jul-94 alexgo author
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
BOOL CSafeRefCount::IsZombie()
|
|
{
|
|
BOOL fIsZombie;
|
|
|
|
if( (m_cRefs == 0 && m_cNest > 0) || m_fInDelete == TRUE
|
|
|| m_fForceZombie == TRUE)
|
|
{
|
|
fIsZombie = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fIsZombie = FALSE;
|
|
}
|
|
|
|
return fIsZombie;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Member: CSafeRefCount::Zombie
|
|
//
|
|
// Synopsis: Forces the object into a zombie state. This is called
|
|
// when the object is still around but shouldn't be. It
|
|
// flags us so we behave safely while we are in this state.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: none
|
|
//
|
|
// Signals:
|
|
//
|
|
// Modifies:
|
|
//
|
|
// Derivation:
|
|
//
|
|
// Algorithm:
|
|
//
|
|
// History:
|
|
//
|
|
// Notes:
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
VOID CSafeRefCount::Zombie()
|
|
{
|
|
m_fForceZombie = TRUE;
|
|
}
|
|
|
|
/* OleStdSwitchDisplayAspect
|
|
**
|
|
** @mfunc
|
|
** Switch the currently cached display aspect between DVASPECT_ICON
|
|
** and DVASPECT_CONTENT.
|
|
**
|
|
** NOTE: when setting up icon aspect, any currently cached content
|
|
** cache is discarded and any advise connections for content aspect
|
|
** are broken.
|
|
**
|
|
** @rdesc
|
|
** S_OK -- new display aspect setup successfully
|
|
** E_INVALIDARG -- IOleCache interface is NOT supported (this is
|
|
** required).
|
|
** <other SCODE> -- any SCODE that can be returned by
|
|
** IOleCache::Cache method.
|
|
** NOTE: if an error occurs then the current display aspect and
|
|
** cache contents unchanged.
|
|
*/
|
|
HRESULT OleStdSwitchDisplayAspect(
|
|
LPOLEOBJECT lpOleObj,
|
|
LPDWORD lpdwCurAspect,
|
|
DWORD dwNewAspect,
|
|
HGLOBAL hMetaPict,
|
|
BOOL fDeleteOldAspect,
|
|
BOOL fSetupViewAdvise,
|
|
LPADVISESINK lpAdviseSink,
|
|
BOOL FAR* lpfMustUpdate)
|
|
{
|
|
LPOLECACHE lpOleCache = NULL;
|
|
LPVIEWOBJECT lpViewObj = NULL;
|
|
LPENUMSTATDATA lpEnumStatData = NULL;
|
|
STATDATA StatData;
|
|
FORMATETC FmtEtc;
|
|
STGMEDIUM Medium;
|
|
DWORD dwAdvf;
|
|
DWORD dwNewConnection;
|
|
DWORD dwOldAspect = *lpdwCurAspect;
|
|
HRESULT hrErr;
|
|
|
|
if (lpfMustUpdate)
|
|
*lpfMustUpdate = FALSE;
|
|
|
|
if (hrErr =
|
|
lpOleObj->QueryInterface(IID_IOleCache, (void**)&lpOleCache))
|
|
{
|
|
return hrErr;
|
|
}
|
|
|
|
// Setup new cache with the new aspect
|
|
FmtEtc.cfFormat = 0; // whatever is needed to draw
|
|
FmtEtc.ptd = NULL;
|
|
FmtEtc.dwAspect = dwNewAspect;
|
|
FmtEtc.lindex = -1;
|
|
FmtEtc.tymed = TYMED_NULL;
|
|
|
|
/* NOTE: if we are setting up Icon aspect with a custom icon
|
|
** then we do not want DataAdvise notifications to ever change
|
|
** the contents of the data cache. thus we set up a NODATA
|
|
** advise connection. otherwise we set up a standard DataAdvise
|
|
** connection.
|
|
*/
|
|
if (dwNewAspect == DVASPECT_ICON && hMetaPict)
|
|
dwAdvf = ADVF_NODATA;
|
|
else
|
|
dwAdvf = ADVF_PRIMEFIRST;
|
|
|
|
hrErr = lpOleCache->Cache(
|
|
(LPFORMATETC)&FmtEtc,
|
|
dwAdvf,
|
|
(LPDWORD)&dwNewConnection
|
|
);
|
|
|
|
if (! SUCCEEDED(hrErr)) {
|
|
lpOleCache->Release();
|
|
return hrErr;
|
|
}
|
|
|
|
*lpdwCurAspect = dwNewAspect;
|
|
|
|
/* NOTE: if we are setting up Icon aspect with a custom icon,
|
|
** then stuff the icon into the cache. otherwise the cache must
|
|
** be forced to be updated. set the *lpfMustUpdate flag to tell
|
|
** caller to force the object to Run so that the cache will be
|
|
** updated.
|
|
*/
|
|
if (dwNewAspect == DVASPECT_ICON && hMetaPict) {
|
|
|
|
FmtEtc.cfFormat = CF_METAFILEPICT;
|
|
FmtEtc.ptd = NULL;
|
|
FmtEtc.dwAspect = DVASPECT_ICON;
|
|
FmtEtc.lindex = -1;
|
|
FmtEtc.tymed = TYMED_MFPICT;
|
|
|
|
Medium.tymed = TYMED_MFPICT;
|
|
Medium.hGlobal = hMetaPict;
|
|
Medium.pUnkForRelease = NULL;
|
|
|
|
hrErr = lpOleCache->SetData(
|
|
(LPFORMATETC)&FmtEtc,
|
|
(LPSTGMEDIUM)&Medium,
|
|
FALSE /* fRelease */
|
|
);
|
|
} else {
|
|
if (lpfMustUpdate)
|
|
*lpfMustUpdate = TRUE;
|
|
}
|
|
|
|
if (fSetupViewAdvise && lpAdviseSink) {
|
|
/* NOTE: re-establish the ViewAdvise connection */
|
|
lpOleObj->QueryInterface(IID_IViewObject, (void**)&lpViewObj);
|
|
|
|
if (lpViewObj) {
|
|
|
|
lpViewObj->SetAdvise(
|
|
dwNewAspect,
|
|
0,
|
|
lpAdviseSink
|
|
);
|
|
|
|
lpViewObj->Release();
|
|
}
|
|
}
|
|
|
|
/* NOTE: remove any existing caches that are set up for the old
|
|
** display aspect. It WOULD be possible to retain the caches set
|
|
** up for the old aspect, but this would increase the storage
|
|
** space required for the object and possibly require additional
|
|
** overhead to maintain the unused cachaes. For these reasons the
|
|
** strategy to delete the previous caches is prefered. if it is a
|
|
** requirement to quickly switch between Icon and Content
|
|
** display, then it would be better to keep both aspect caches.
|
|
*/
|
|
|
|
if (fDeleteOldAspect) {
|
|
hrErr = lpOleCache->EnumCache(
|
|
(LPENUMSTATDATA FAR*)&lpEnumStatData
|
|
);
|
|
|
|
while(hrErr == NOERROR) {
|
|
hrErr = lpEnumStatData->Next(
|
|
1,
|
|
(LPSTATDATA)&StatData,
|
|
NULL
|
|
);
|
|
if (hrErr != NOERROR)
|
|
break; // DONE! no more caches.
|
|
|
|
if (StatData.formatetc.dwAspect == dwOldAspect) {
|
|
|
|
// Remove previous cache with old aspect
|
|
lpOleCache->Uncache(StatData.dwConnection);
|
|
}
|
|
}
|
|
|
|
if (lpEnumStatData)
|
|
lpEnumStatData->Release();
|
|
}
|
|
|
|
if (lpOleCache)
|
|
lpOleCache->Release();
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
/*
|
|
* ObjectReadSiteFlags
|
|
*
|
|
* @mfunc
|
|
* Read the dwFlags, dwUser, & dvaspect bytes from a container
|
|
* specific stream.
|
|
*
|
|
* Arguments:
|
|
* preobj The REOBJ in which to copy the flags.
|
|
*
|
|
* @rdesc
|
|
* HRESULT
|
|
*/
|
|
HRESULT ObjectReadSiteFlags(REOBJECT * preobj)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPSTREAM pstm = NULL;
|
|
OLECHAR StreamName[] = OLESTR("RichEditFlags");
|
|
|
|
|
|
// Make sure we have a storage to read from
|
|
if (!preobj->pstg)
|
|
return E_INVALIDARG;
|
|
|
|
// Open the stream
|
|
if (hr = preobj->pstg->OpenStream(StreamName, 0, STGM_READ |
|
|
STGM_SHARE_EXCLUSIVE, 0, &pstm))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((hr = pstm->Read(&preobj->dwFlags,
|
|
sizeof(preobj->dwFlags), NULL)) ||
|
|
(hr = pstm->Read(&preobj->dwUser,
|
|
sizeof(preobj->dwUser), NULL)) ||
|
|
(hr = pstm->Read(&preobj->dvaspect,
|
|
sizeof(preobj->dvaspect), NULL)))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
if (pstm)
|
|
pstm->Release();
|
|
return hr;
|
|
}
|
|
|
|
//Used for EnumMetafileCheckIcon & FIsIconMetafilePict
|
|
typedef struct _walkmetafile
|
|
{
|
|
BOOL fAND;
|
|
BOOL fPastIcon;
|
|
BOOL fHasIcon;
|
|
} WALKMETAFILE;
|
|
|
|
static CHAR szIconOnly[] = "IconOnly";
|
|
|
|
/*
|
|
* EnumMetafileCheckIcon
|
|
*
|
|
* @mfunc
|
|
* Stripped down version of EnumMetafileExtractIcon and
|
|
* EnumMetafileExtractIconSource from the OLE2UI library.
|
|
*
|
|
* EnumMetaFile callback function that walks a metafile looking for
|
|
* StretchBlt (3.1) and BitBlt (3.0) records. We expect to see two
|
|
* of them, the first being the AND mask and the second being the XOR
|
|
* data.
|
|
*
|
|
* Once we find the icon, we confirm this find by looking for the "IconOnly"
|
|
* comment block found in standard OLE iconic metafiles.
|
|
*
|
|
* Arguments:
|
|
* hDC HDC into which the metafile should be played.
|
|
* phTable HANDLETABLE FAR * providing handles selected into the DC.
|
|
* pMFR METARECORD FAR * giving the enumerated record.
|
|
* pIE LPICONEXTRACT providing the destination buffer and length.
|
|
*
|
|
* @rdesc
|
|
* int 0 to stop enumeration, 1 to continue.
|
|
*/
|
|
|
|
#ifndef NOMETAFILES
|
|
int CALLBACK EnumMetafileCheckIcon(HDC hdc, HANDLETABLE *phTable,
|
|
METARECORD *pMFR, int cObj,
|
|
LPARAM lparam)
|
|
{
|
|
WALKMETAFILE * pwmf = (WALKMETAFILE *) lparam;
|
|
|
|
switch (pMFR->rdFunction)
|
|
{
|
|
case META_DIBBITBLT: // Win30
|
|
case META_DIBSTRETCHBLT: // Win31
|
|
// If this is the first pass (pIE->fAND==TRUE) then save the memory
|
|
// of the AND bits for the next pass.
|
|
|
|
if (pwmf->fAND)
|
|
pwmf->fAND = FALSE;
|
|
else
|
|
pwmf->fPastIcon = TRUE;
|
|
break;
|
|
|
|
case META_ESCAPE:
|
|
if (pwmf->fPastIcon &&
|
|
pMFR->rdParm[0] == MFCOMMENT &&
|
|
!lstrcmpiA(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
|
|
{
|
|
pwmf->fHasIcon = TRUE;
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* FIsIconMetafilePict(hmfp)
|
|
*
|
|
* @mfunc
|
|
* Detect whether the metafile contains an iconic presentation. We do this
|
|
* by getting a screen DC and walking the metafile records until we find
|
|
* the landmarks denoting an icon.
|
|
*
|
|
* Arguments:
|
|
* hmfp The metafile to test
|
|
*
|
|
* @rdesc
|
|
* BOOL TRUE if the metafile contains an iconic view
|
|
*/
|
|
BOOL FIsIconMetafilePict(HGLOBAL hmfp)
|
|
{
|
|
#ifndef NOMETAFILES
|
|
LPMETAFILEPICT pmfp;
|
|
WALKMETAFILE wmf = { 0 };
|
|
HDC hdc;
|
|
|
|
wmf.fAND = TRUE;
|
|
if (!hmfp || !(pmfp = (LPMETAFILEPICT)GlobalLock(hmfp)))
|
|
goto CleanUp;
|
|
|
|
// We get information back in the ICONEXTRACT structure.
|
|
hdc = GetDC(NULL);
|
|
EnumMetaFile(hdc, pmfp->hMF, EnumMetafileCheckIcon, (LPARAM) &wmf);
|
|
ReleaseDC(NULL, hdc);
|
|
GlobalUnlock(hmfp);
|
|
|
|
CleanUp:
|
|
return wmf.fHasIcon;
|
|
#else
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* AllocObjectDescriptor (clsID, dwAspect, szl, ptl, dwMisc, pszName, pszSrc)
|
|
*
|
|
* @func
|
|
* Allocate and fill an OBJECTDESCRIPTOR structure
|
|
*
|
|
* @rdesc
|
|
* HBGLOBAL Handle to OBJECTDESCRIPTOR structure.
|
|
*/
|
|
static HGLOBAL AllocObjectDescriptor(
|
|
CLSID clsID, // CLSID to store
|
|
DWORD dwAspect, // Display aspect
|
|
SIZEL szl, // Optional extents container uses to display object
|
|
POINTL ptl, // Upper-left corner of obj where mouse went down for D&D
|
|
DWORD dwMisc, // MiscStatus flags
|
|
LPTSTR pszName, // Name of object to copy
|
|
LPTSTR pszSrc) // Source of object
|
|
{
|
|
HGLOBAL hMem=NULL;
|
|
LPOBJECTDESCRIPTOR pOD;
|
|
DWORD cb, cbStruct;
|
|
DWORD cchName, cchSrc;
|
|
|
|
cchName=wcslen(pszName)+1;
|
|
|
|
if (NULL!=pszSrc)
|
|
cchSrc=wcslen(pszSrc)+1;
|
|
else
|
|
{
|
|
cchSrc=cchName;
|
|
pszSrc=pszName;
|
|
}
|
|
|
|
/*
|
|
* Note: CFSTR_OBJECTDESCRIPTOR is an ANSI structure.
|
|
* That means strings in it must be ANSI. OLE will do
|
|
* internal conversions back to Unicode as necessary,
|
|
* but we have to put ANSI strings in it ourselves.
|
|
*/
|
|
cbStruct=sizeof(OBJECTDESCRIPTOR);
|
|
cb=cbStruct+(sizeof(WCHAR)*(cchName+cchSrc)); //HACK
|
|
|
|
hMem=GlobalAlloc(GHND, cb);
|
|
|
|
if (NULL==hMem)
|
|
return NULL;
|
|
|
|
pOD=(LPOBJECTDESCRIPTOR)GlobalLock(hMem);
|
|
|
|
pOD->cbSize=cb;
|
|
pOD->clsid=clsID;
|
|
pOD->dwDrawAspect=dwAspect;
|
|
pOD->sizel=szl;
|
|
pOD->pointl=ptl;
|
|
pOD->dwStatus=dwMisc;
|
|
|
|
if (pszName)
|
|
{
|
|
pOD->dwFullUserTypeName=cbStruct;
|
|
wcscpy((LPTSTR)((LPBYTE)pOD+pOD->dwFullUserTypeName)
|
|
, pszName);
|
|
}
|
|
else
|
|
pOD->dwFullUserTypeName=0; //No string
|
|
|
|
if (pszSrc)
|
|
{
|
|
pOD->dwSrcOfCopy=cbStruct+(cchName*sizeof(WCHAR));
|
|
|
|
wcscpy((LPTSTR)((LPBYTE)pOD+pOD->dwSrcOfCopy), pszSrc);
|
|
}
|
|
else
|
|
pOD->dwSrcOfCopy=0; //No string
|
|
|
|
GlobalUnlock(hMem);
|
|
return hMem;
|
|
}
|
|
|
|
HGLOBAL OleGetObjectDescriptorDataFromOleObject(
|
|
LPOLEOBJECT pObj,
|
|
DWORD dwAspect,
|
|
POINTL ptl,
|
|
LPSIZEL pszl
|
|
)
|
|
{
|
|
CLSID clsID;
|
|
LPTSTR pszName=NULL;
|
|
LPTSTR pszSrc=NULL;
|
|
BOOL fLink=FALSE;
|
|
IOleLink *pLink;
|
|
TCHAR szName[512];
|
|
DWORD dwMisc=0;
|
|
SIZEL szl = {0,0};
|
|
HGLOBAL hMem;
|
|
HRESULT hr;
|
|
|
|
if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
|
|
, (void **)&pLink)))
|
|
fLink=TRUE;
|
|
|
|
if (FAILED(pObj->GetUserClassID(&clsID)))
|
|
ZeroMemory(&clsID, sizeof(CLSID));
|
|
|
|
//Get user string, expand to "Linked %s" if this is link
|
|
pObj->GetUserType(USERCLASSTYPE_FULL, &pszName);
|
|
if (fLink && NULL!=pszName)
|
|
{
|
|
// NB!! we do these two lines of code below instead
|
|
// wcscat because we don't use wcscat anywhere else
|
|
// in the product at the moment. The string "Linked "
|
|
// should never change either.
|
|
wcscpy(szName, TEXT("Linked "));
|
|
wcscpy(&(szName[7]), pszName);
|
|
}
|
|
else if (pszName)
|
|
wcscpy(szName, pszName);
|
|
else
|
|
szName[0] = 0;
|
|
|
|
CoTaskMemFree(pszName);
|
|
|
|
/*
|
|
* Get the source name of this object using either the
|
|
* link display name (for link) or a moniker display
|
|
* name.
|
|
*/
|
|
|
|
if (fLink)
|
|
{
|
|
hr=pLink->GetSourceDisplayName(&pszSrc);
|
|
}
|
|
else
|
|
{
|
|
IMoniker *pmk;
|
|
|
|
hr=pObj->GetMoniker(OLEGETMONIKER_TEMPFORUSER
|
|
, OLEWHICHMK_OBJFULL, &pmk);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IBindCtx *pbc;
|
|
CreateBindCtx(0, &pbc);
|
|
|
|
pmk->GetDisplayName(pbc, NULL, &pszSrc);
|
|
pbc->Release();
|
|
pmk->Release();
|
|
}
|
|
}
|
|
|
|
if (fLink)
|
|
pLink->Release();
|
|
|
|
//Get MiscStatus bits
|
|
hr=pObj->GetMiscStatus(dwAspect, &dwMisc);
|
|
|
|
if (pszl)
|
|
{
|
|
szl.cx = pszl->cx;
|
|
szl.cy = pszl->cy;
|
|
}
|
|
//Get OBJECTDESCRIPTOR
|
|
hMem=AllocObjectDescriptor(clsID, dwAspect, szl, ptl, dwMisc, szName, pszSrc);
|
|
|
|
CoTaskMemFree(pszSrc);
|
|
|
|
return hMem;
|
|
}
|
|
|
|
/*
|
|
* OleStdGetMetafilePictFromOleObject()
|
|
*
|
|
* @mfunc:
|
|
* Generate a MetafilePict from the OLE object.
|
|
* Parameters:
|
|
* lpOleObj LPOLEOBJECT pointer to OLE Object
|
|
* dwDrawAspect DWORD Display Aspect of object
|
|
* lpSizelHim SIZEL (optional) If the object is being scaled in its
|
|
* container, then the container should pass the extents
|
|
* that it is using to display the object.
|
|
* May be NULL if the object is NOT being scaled. in this
|
|
* case, IViewObject2::GetExtent will be called to get the
|
|
* extents from the object.
|
|
* ptd TARGETDEVICE FAR* (optional) target device to render
|
|
* metafile for. May be NULL.
|
|
*
|
|
* @rdesc
|
|
* HANDLE -- handle of allocated METAFILEPICT
|
|
*/
|
|
HANDLE OleStdGetMetafilePictFromOleObject(
|
|
LPOLEOBJECT lpOleObj,
|
|
DWORD dwDrawAspect,
|
|
LPSIZEL lpSizelHim,
|
|
DVTARGETDEVICE FAR* ptd
|
|
)
|
|
{
|
|
#ifndef NOMETAFILES
|
|
LPVIEWOBJECT2 lpViewObj2 = NULL;
|
|
HDC hDC;
|
|
HMETAFILE hmf;
|
|
HANDLE hMetaPict;
|
|
LPMETAFILEPICT lpPict;
|
|
RECT rcHim;
|
|
RECTL rclHim;
|
|
SIZEL sizelHim;
|
|
HRESULT hrErr;
|
|
SIZE size;
|
|
POINT point;
|
|
LPOLECACHE polecache = NULL;
|
|
LPDATAOBJECT pdataobj = NULL;
|
|
FORMATETC fetc;
|
|
STGMEDIUM med;
|
|
|
|
// First try the easy way,
|
|
// pull out the cache's version of things.
|
|
ZeroMemory(&fetc, sizeof(FORMATETC));
|
|
fetc.dwAspect = dwDrawAspect;
|
|
fetc.cfFormat = CF_METAFILEPICT;
|
|
fetc.lindex = -1;
|
|
fetc.tymed = TYMED_MFPICT;
|
|
ZeroMemory(&med, sizeof(STGMEDIUM));
|
|
hMetaPict = NULL;
|
|
|
|
if (!lpOleObj->QueryInterface(IID_IOleCache, (void **)&polecache) &&
|
|
!polecache->QueryInterface(IID_IDataObject, (void **)&pdataobj) &&
|
|
!pdataobj->GetData(&fetc, &med))
|
|
{
|
|
hMetaPict = OleDuplicateData(med.hGlobal, CF_METAFILEPICT, 0);
|
|
ReleaseStgMedium(&med);
|
|
}
|
|
|
|
if (pdataobj)
|
|
{
|
|
pdataobj->Release();
|
|
}
|
|
|
|
if (polecache)
|
|
{
|
|
polecache->Release();
|
|
}
|
|
|
|
// If all this failed, fall back to the hard way and draw the object
|
|
// into a metafile.
|
|
if (hMetaPict)
|
|
return hMetaPict;
|
|
|
|
if (lpOleObj->QueryInterface(IID_IViewObject2, (void **)&lpViewObj2))
|
|
return NULL;
|
|
|
|
// Get SIZEL
|
|
if (lpSizelHim) {
|
|
// Use extents passed by the caller
|
|
sizelHim = *lpSizelHim;
|
|
} else {
|
|
// Get the current extents from the object
|
|
hrErr = lpViewObj2->GetExtent(
|
|
dwDrawAspect,
|
|
-1, /*lindex*/
|
|
ptd, /*ptd*/
|
|
(LPSIZEL)&sizelHim);
|
|
if (hrErr != NOERROR)
|
|
sizelHim.cx = sizelHim.cy = 0;
|
|
}
|
|
|
|
hDC = CreateMetaFileA(NULL);
|
|
|
|
rclHim.left = 0;
|
|
rclHim.top = 0;
|
|
rclHim.right = sizelHim.cx;
|
|
rclHim.bottom = sizelHim.cy;
|
|
|
|
rcHim.left = (int)rclHim.left;
|
|
rcHim.top = (int)rclHim.top;
|
|
rcHim.right = (int)rclHim.right;
|
|
rcHim.bottom = (int)rclHim.bottom;
|
|
|
|
SetWindowOrgEx(hDC, rcHim.left, rcHim.top, &point);
|
|
SetWindowExtEx(hDC, rcHim.right-rcHim.left, rcHim.bottom-rcHim.top,&size);
|
|
|
|
hrErr = lpViewObj2->Draw(
|
|
dwDrawAspect,
|
|
-1,
|
|
NULL,
|
|
ptd,
|
|
NULL,
|
|
hDC,
|
|
(LPRECTL)&rclHim,
|
|
(LPRECTL)&rclHim,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
lpViewObj2->Release();
|
|
|
|
hmf = CloseMetaFile(hDC);
|
|
|
|
if (hrErr != NOERROR) {
|
|
TRACEERRORHR(hrErr);
|
|
hMetaPict = NULL;
|
|
}
|
|
else
|
|
{
|
|
hMetaPict = GlobalAlloc(GHND|GMEM_SHARE, sizeof(METAFILEPICT));
|
|
|
|
if (hMetaPict && (lpPict = (LPMETAFILEPICT)GlobalLock(hMetaPict))){
|
|
lpPict->hMF = hmf;
|
|
lpPict->xExt = (int)sizelHim.cx ;
|
|
lpPict->yExt = (int)sizelHim.cy ;
|
|
lpPict->mm = MM_ANISOTROPIC;
|
|
GlobalUnlock(hMetaPict);
|
|
}
|
|
}
|
|
|
|
if (!hMetaPict)
|
|
DeleteMetaFile(hmf);
|
|
|
|
return hMetaPict;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* OleUIDrawShading
|
|
*
|
|
* Purpose:
|
|
* Shade the object when it is in in-place editing. Borders are drawn
|
|
* on the Object rectangle. The right and bottom edge of the rectangle
|
|
* are excluded in the drawing.
|
|
*
|
|
* Parameters:
|
|
* lpRect Dimensions of Container Object
|
|
* hdc HDC for drawing
|
|
*
|
|
* Return Value: null
|
|
*
|
|
*/
|
|
void OleUIDrawShading(LPRECT prc, HDC hdc)
|
|
{
|
|
HBITMAP hbmp;
|
|
WORD wHatchBmp[] = {0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88};
|
|
COLORREF crTextOld;
|
|
COLORREF crBkOld;
|
|
|
|
hbmp = CreateBitmap(8, 8, 1, 1, wHatchBmp);
|
|
if (hbmp)
|
|
{
|
|
HBRUSH hbr;
|
|
hbr = CreatePatternBrush(hbmp);
|
|
if (hbr)
|
|
{
|
|
HBRUSH hbrOld;
|
|
|
|
hbrOld = (HBRUSH)SelectObject(hdc, hbr);
|
|
|
|
crTextOld = SetTextColor(hdc, RGB(255, 255, 255));
|
|
crBkOld = SetBkColor(hdc, RGB(0, 0, 0));
|
|
|
|
PatBlt(hdc, prc->left, prc->top, prc->right - prc->left, prc->bottom - prc->top, 0x00A000C9L /* DPa */ );
|
|
|
|
SetTextColor(hdc, crTextOld);
|
|
SetBkColor(hdc, crBkOld);
|
|
|
|
SelectObject(hdc, hbrOld);
|
|
DeleteObject(hbr);
|
|
}
|
|
DeleteObject(hbmp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* OleSaveSiteFlags
|
|
*
|
|
* Purpose:
|
|
* Save the dwFlags and dwUser bytes into a container specific stream
|
|
*
|
|
* Arguments:
|
|
* pstg The storage to save to
|
|
* pobsite The site from where to copy the flags
|
|
*
|
|
* Returns:
|
|
* None.
|
|
*/
|
|
VOID OleSaveSiteFlags(LPSTORAGE pstg, DWORD dwFlags, DWORD dwUser, DWORD dvAspect)
|
|
{
|
|
#ifndef PEGASUS
|
|
HRESULT hr;
|
|
LPSTREAM pstm = NULL;
|
|
static const OLECHAR szSiteFlagsStm[] = OLESTR("RichEditFlags");
|
|
|
|
TRACEBEGIN(TRCSUBSYSDTE, TRCSCOPEINTERN, "OleSaveSiteFlags");
|
|
|
|
// Create/overwrite the stream
|
|
AssertSz(pstg, "Invalid storage");
|
|
if (hr = pstg->CreateStream(szSiteFlagsStm, STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
|
|
0, 0, &pstm))
|
|
{
|
|
TraceError("OleSaveSiteFlags", GetScode(hr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//$ FUTURE: Put a version stamp
|
|
|
|
// Write out the values
|
|
//$ BUG: byte order
|
|
if ((hr = pstm->Write(&dwFlags, sizeof(dwFlags), NULL)) ||
|
|
(hr = pstm->Write(&dwUser, sizeof(dwUser), NULL)) ||
|
|
(hr = pstm->Write(&dvAspect, sizeof(dvAspect), NULL)))
|
|
{
|
|
TraceError("OleSaveSiteFlags", GetScode(hr));
|
|
//$ FUTURE: Wipe the data to make this operation all or nothing
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
if (pstm)
|
|
pstm->Release();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* AppendString ( szInput, szAppendStr, dBuffSize, dByteUsed )
|
|
*
|
|
* Purpose:
|
|
* Append new string to original string. Check for size of buffer
|
|
* and re-allocate a large buffer is necessary
|
|
*
|
|
* Arguments:
|
|
* szInput Original String
|
|
* szAppendStr String to be appended to szInput
|
|
* dBuffSize Byte size of the buffer for szInput
|
|
* dByteUsed Byte used in the buffer for szInput
|
|
*
|
|
* Returns:
|
|
* INT The error code
|
|
*/
|
|
INT AppendString(
|
|
BYTE ** szInput,
|
|
BYTE * szAppendStr,
|
|
int * dBuffSize,
|
|
int * dByteUsed)
|
|
{
|
|
BYTE *pch;
|
|
int cchAppendStr;
|
|
|
|
pch = *szInput;
|
|
|
|
// check if we have enough space to append the new string
|
|
cchAppendStr = strlen( (char *)szAppendStr );
|
|
|
|
if ( cchAppendStr + *dByteUsed >= *dBuffSize )
|
|
{
|
|
// re-alloc a bigger buffer
|
|
int cchNewSize = *dBuffSize + cchAppendStr + 32;
|
|
|
|
pch = (BYTE *)PvReAlloc( *szInput, cchNewSize );
|
|
|
|
if ( !pch )
|
|
{
|
|
return ( ecNoMemory );
|
|
}
|
|
|
|
*dBuffSize = cchNewSize;
|
|
*szInput = pch;
|
|
}
|
|
|
|
pch += *dByteUsed;
|
|
*dByteUsed += cchAppendStr;
|
|
|
|
while (*pch++ = *szAppendStr++);
|
|
|
|
return ecNoError;
|
|
}
|
|
|
|
/*
|
|
* CTempBuf::Init
|
|
*
|
|
* @mfunc Set object to its initial state using the stack buffer
|
|
*
|
|
*/
|
|
void CTempBuf::Init()
|
|
{
|
|
_pv = (void *) &_chBuf[0];
|
|
_cb = MAX_STACK_BUF;
|
|
}
|
|
|
|
/*
|
|
* CTempBuf::FreeBuf
|
|
*
|
|
* @mfunc Free an allocated buffer if there is one
|
|
*
|
|
*/
|
|
void CTempBuf::FreeBuf()
|
|
{
|
|
if (_pv != &_chBuf[0])
|
|
{
|
|
delete _pv;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CTempBuf::GetBuf
|
|
*
|
|
* @mfunc Get a buffer for temporary use
|
|
*
|
|
* @rdesc Pointer to buffer if one could be allocated otherwise NULL.
|
|
*
|
|
*
|
|
*/
|
|
void *CTempBuf::GetBuf(
|
|
LONG cb) //@parm Size of buffer needed in bytes
|
|
{
|
|
if (_cb >= cb)
|
|
{
|
|
// Currently allocated buffer is big enough so use it
|
|
return _pv;
|
|
}
|
|
|
|
// Free our current buffer
|
|
FreeBuf();
|
|
|
|
// Allocate a new buffer if we can
|
|
_pv = new BYTE[cb];
|
|
|
|
if (NULL == _pv)
|
|
{
|
|
// Could not allocate a buffer so reset to our initial state and
|
|
// return NULL.
|
|
Init();
|
|
return NULL;
|
|
}
|
|
|
|
// Store the size of our new buffer.
|
|
_cb = cb;
|
|
|
|
// Returnt he pointer to the buffer.
|
|
return _pv;
|
|
}
|
|
|
|
|