1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <osl/diagnose.h>
21 #include <o3tl/char16_t2wchar_t.hxx>
22 #include <o3tl/safeint.hxx>
24 #include "XTDataObject.hxx"
25 #include <com/sun/star/datatransfer/DataFlavor.hpp>
26 #include "ImplHelper.hxx"
27 #include "DTransHelper.hxx"
28 #include "TxtCnvtHlp.hxx"
29 #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
30 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
31 #include <com/sun/star/awt/AsyncCallback.hpp>
32 #include <com/sun/star/awt/XCallback.hpp>
33 #include "FmtFilter.hxx"
34 #include <cppuhelper/implbase.hxx>
36 #if !defined WIN32_LEAN_AND_MEAN
37 # define WIN32_LEAN_AND_MEAN
42 using namespace com::sun::star::datatransfer
;
43 using namespace com::sun::star::datatransfer::clipboard
;
44 using namespace com::sun::star::uno
;
45 using namespace com::sun::star::lang
;
49 void setupStgMedium( const FORMATETC
& fetc
,
50 CStgTransferHelper
& stgTransHlp
,
51 STGMEDIUM
& stgmedium
)
53 stgmedium
.pUnkForRelease
= nullptr;
55 if ( fetc
.cfFormat
== CF_METAFILEPICT
)
57 stgmedium
.tymed
= TYMED_MFPICT
;
58 stgmedium
.hMetaFilePict
= static_cast< HMETAFILEPICT
>( stgTransHlp
.getHGlobal( ) );
60 else if ( fetc
.cfFormat
== CF_ENHMETAFILE
)
62 stgmedium
.tymed
= TYMED_ENHMF
;
63 stgmedium
.hEnhMetaFile
= static_cast< HENHMETAFILE
>( stgTransHlp
.getHGlobal( ) );
65 else if ( fetc
.tymed
& TYMED_HGLOBAL
)
67 stgmedium
.tymed
= TYMED_HGLOBAL
;
68 stgmedium
.hGlobal
= stgTransHlp
.getHGlobal( );
70 else if ( fetc
.tymed
& TYMED_ISTREAM
)
72 stgmedium
.tymed
= TYMED_ISTREAM
;
73 stgTransHlp
.getIStream( &stgmedium
.pstm
);
80 We need to destroy XTransferable in the main thread to avoid dead lock
81 when locking in the clipboard thread. So we transfer the ownership of the
82 XTransferable reference to this object and release it when the callback
83 is executed in main thread.
85 class AsyncDereference
: public cppu::WeakImplHelper
<css::awt::XCallback
>
87 Reference
<XTransferable
> maTransferable
;
90 AsyncDereference(css::uno::Reference
<css::datatransfer::XTransferable
> const & rTransferable
)
91 : maTransferable(rTransferable
)
94 virtual void SAL_CALL
notify(css::uno::Any
const &) override
96 maTransferable
.set(nullptr);
100 // a helper class that will be thrown by the function validateFormatEtc
102 class CInvalidFormatEtcException
106 explicit CInvalidFormatEtcException( HRESULT hr
) : m_hr( hr
) {};
109 void validateFormatEtc( LPFORMATETC lpFormatEtc
)
111 OSL_ASSERT( lpFormatEtc
);
113 if ( lpFormatEtc
->lindex
!= -1 )
114 throw CInvalidFormatEtcException( DV_E_LINDEX
);
116 if ( !(lpFormatEtc
->dwAspect
& DVASPECT_CONTENT
) &&
117 !(lpFormatEtc
->dwAspect
& DVASPECT_SHORTNAME
) )
118 throw CInvalidFormatEtcException( DV_E_DVASPECT
);
120 if ( !(lpFormatEtc
->tymed
& TYMED_HGLOBAL
) &&
121 !(lpFormatEtc
->tymed
& TYMED_ISTREAM
) &&
122 !(lpFormatEtc
->tymed
& TYMED_MFPICT
) &&
123 !(lpFormatEtc
->tymed
& TYMED_ENHMF
) )
124 throw CInvalidFormatEtcException( DV_E_TYMED
);
126 if ( lpFormatEtc
->cfFormat
== CF_METAFILEPICT
&&
127 !(lpFormatEtc
->tymed
& TYMED_MFPICT
) )
128 throw CInvalidFormatEtcException( DV_E_TYMED
);
130 if ( lpFormatEtc
->cfFormat
== CF_ENHMETAFILE
&&
131 !(lpFormatEtc
->tymed
& TYMED_ENHMF
) )
132 throw CInvalidFormatEtcException( DV_E_TYMED
);
135 void invalidateStgMedium( STGMEDIUM
& stgmedium
)
137 stgmedium
.tymed
= TYMED_NULL
;
140 HRESULT
translateStgExceptionCode( HRESULT hr
)
146 case STG_E_MEDIUMFULL
:
151 hrTransl
= E_UNEXPECTED
;
159 void renderDataAndSetupStgMedium(
160 const sal_Int8
* lpStorage
, const FORMATETC
& fetc
, sal_uInt32 nInitStgSize
,
161 sal_uInt32 nBytesToTransfer
, STGMEDIUM
& stgmedium
)
163 OSL_PRECOND( !nInitStgSize
|| (nInitStgSize
>= nBytesToTransfer
),
164 "Memory size less than number of bytes to transfer" );
166 CStgTransferHelper
stgTransfHelper( AUTO_INIT
);
168 // setup storage size
169 if ( nInitStgSize
> 0 )
170 stgTransfHelper
.init( nInitStgSize
);
172 #if OSL_DEBUG_LEVEL > 0
173 sal_uInt32 nBytesWritten
= 0;
174 stgTransfHelper
.write( lpStorage
, nBytesToTransfer
, &nBytesWritten
);
175 OSL_ASSERT( nBytesWritten
== nBytesToTransfer
);
177 stgTransfHelper
.write( lpStorage
, nBytesToTransfer
);
180 setupStgMedium( fetc
, stgTransfHelper
, stgmedium
);
185 CXTDataObject::CXTDataObject( const Reference
< XComponentContext
>& rxContext
,
186 const Reference
< XTransferable
>& aXTransferable
)
188 , m_XTransferable( aXTransferable
)
189 , m_XComponentContext( rxContext
)
190 , m_bFormatEtcContainerInitialized( false )
191 , m_DataFormatTranslator( rxContext
)
192 , m_FormatRegistrar( rxContext
, m_DataFormatTranslator
)
196 CXTDataObject::~CXTDataObject()
198 css::awt::AsyncCallback::create(m_XComponentContext
)->addCallback(
199 new AsyncDereference(m_XTransferable
),
203 // IUnknown->QueryInterface
205 STDMETHODIMP
CXTDataObject::QueryInterface( REFIID iid
, void** ppvObject
)
207 if ( nullptr == ppvObject
)
210 HRESULT hr
= E_NOINTERFACE
;
212 *ppvObject
= nullptr;
213 if ( ( __uuidof( IUnknown
) == iid
) ||
214 ( __uuidof( IDataObject
) == iid
) )
216 *ppvObject
= static_cast< IUnknown
* >( this );
217 static_cast<LPUNKNOWN
>(*ppvObject
)->AddRef( );
226 STDMETHODIMP_(ULONG
) CXTDataObject::AddRef( )
228 return static_cast< ULONG
>( InterlockedIncrement( &m_nRefCnt
) );
233 STDMETHODIMP_(ULONG
) CXTDataObject::Release( )
236 static_cast< ULONG
>( InterlockedDecrement( &m_nRefCnt
) );
244 STDMETHODIMP
CXTDataObject::GetData( FORMATETC
* pFormatetc
, STGMEDIUM
* pmedium
)
246 if ( !(pFormatetc
&& pmedium
) )
251 // prepare data transfer
252 invalidateStgMedium( *pmedium
);
253 validateFormatEtc( pFormatetc
);
255 // handle locale request, because locale is an artificial format for us
256 if ( CF_LOCALE
== pFormatetc
->cfFormat
)
257 renderLocaleAndSetupStgMedium( *pFormatetc
, *pmedium
);
258 else if ( CF_UNICODETEXT
== pFormatetc
->cfFormat
)
259 renderUnicodeAndSetupStgMedium( *pFormatetc
, *pmedium
);
261 renderAnyDataAndSetupStgMedium( *pFormatetc
, *pmedium
);
263 catch(UnsupportedFlavorException
&)
265 HRESULT hr
= DV_E_FORMATETC
;
267 CFormatEtc
aFormatetc(*pFormatetc
);
268 if (CFormatRegistrar::isSynthesizeableFormat(aFormatetc
))
269 hr
= renderSynthesizedFormatAndSetupStgMedium( *pFormatetc
, *pmedium
);
273 catch( CInvalidFormatEtcException
& ex
)
277 catch( CStgTransferHelper::CStgTransferException
& ex
)
279 return translateStgExceptionCode( ex
.m_hr
);
290 void CXTDataObject::renderLocaleAndSetupStgMedium(
291 FORMATETC
const & fetc
, STGMEDIUM
& stgmedium
)
293 if ( !m_FormatRegistrar
.hasSynthesizedLocale( ) )
294 throw CInvalidFormatEtcException( DV_E_FORMATETC
);
295 LCID lcid
= CFormatRegistrar::getSynthesizedLocale( );
296 renderDataAndSetupStgMedium(
297 reinterpret_cast< sal_Int8
* >( &lcid
),
304 void CXTDataObject::renderUnicodeAndSetupStgMedium(
305 FORMATETC
const & fetc
, STGMEDIUM
& stgmedium
)
307 DataFlavor aFlavor
= formatEtcToDataFlavor( fetc
);
309 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
311 // unfortunately not all transferables fulfill the
312 // spec. and do throw an UnsupportedFlavorException
313 // so we must check the any
314 if ( !aAny
.hasValue( ) )
316 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
317 throw UnsupportedFlavorException( );
323 sal_uInt32 nBytesToTransfer
= aText
.getLength( ) * sizeof( sal_Unicode
);
325 // to be sure there is an ending 0
326 sal_uInt32 nRequiredMemSize
= nBytesToTransfer
+ sizeof( sal_Unicode
);
328 renderDataAndSetupStgMedium(
329 reinterpret_cast< const sal_Int8
* >( aText
.getStr( ) ),
336 void CXTDataObject::renderAnyDataAndSetupStgMedium(
337 FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
339 DataFlavor aFlavor
= formatEtcToDataFlavor( fetc
);
341 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
343 // unfortunately not all transferables fulfill the
344 // spec. and do throw an UnsupportedFlavorException
345 // so we must check the any
346 if ( !aAny
.hasValue( ) )
348 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
349 throw UnsupportedFlavorException( );
352 // unfortunately not all transferables fulfill the
353 // spec. and do throw an UnsupportedFlavorException
354 // so we must check the any
355 if ( !aAny
.hasValue( ) )
356 throw UnsupportedFlavorException( );
358 Sequence
< sal_Int8
> clipDataStream
;
359 aAny
>>= clipDataStream
;
361 sal_uInt32 nRequiredMemSize
= 0;
362 if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc
.cfFormat
) )
363 nRequiredMemSize
= sizeof( sal_Int8
) * clipDataStream
.getLength( ) + 1;
365 // prepare data for transmission
366 // #i124085# DIBV5 should not happen for now, but keep as hint here
367 if ( CF_DIBV5
== fetc
.cfFormat
|| CF_DIB
== fetc
.cfFormat
)
370 if(CF_DIBV5
== fetc
.cfFormat
)
372 OSL_ENSURE(o3tl::make_unsigned(clipDataStream
.getLength()) > (sizeof(BITMAPFILEHEADER
) + sizeof(BITMAPV5HEADER
)), "Wrong size on CF_DIBV5 data (!)");
374 else // CF_DIB == fetc.cfFormat
376 OSL_ENSURE(o3tl::make_unsigned(clipDataStream
.getLength()) > (sizeof(BITMAPFILEHEADER
) + sizeof(BITMAPINFOHEADER
)), "Wrong size on CF_DIB data (!)");
380 // remove BITMAPFILEHEADER
381 clipDataStream
= OOBmpToWinDIB( clipDataStream
);
384 if ( CF_METAFILEPICT
== fetc
.cfFormat
)
386 stgmedium
.tymed
= TYMED_MFPICT
;
387 stgmedium
.hMetaFilePict
= OOMFPictToWinMFPict( clipDataStream
);
388 stgmedium
.pUnkForRelease
= nullptr;
390 else if( CF_ENHMETAFILE
== fetc
.cfFormat
)
392 stgmedium
.tymed
= TYMED_ENHMF
;
393 stgmedium
.hMetaFilePict
= OOMFPictToWinENHMFPict( clipDataStream
);
394 stgmedium
.pUnkForRelease
= nullptr;
397 renderDataAndSetupStgMedium(
398 clipDataStream
.getArray( ),
401 clipDataStream
.getLength( ),
405 HRESULT
CXTDataObject::renderSynthesizedFormatAndSetupStgMedium( FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
411 if ( CF_UNICODETEXT
== fetc
.cfFormat
)
412 // the transferable seems to have only text
413 renderSynthesizedUnicodeAndSetupStgMedium( fetc
, stgmedium
);
414 else if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc
.cfFormat
) )
415 // the transferable seems to have only unicode text
416 renderSynthesizedTextAndSetupStgMedium( fetc
, stgmedium
);
418 // the transferable seems to have only text/html
419 renderSynthesizedHtmlAndSetupStgMedium( fetc
, stgmedium
);
421 catch(UnsupportedFlavorException
&)
425 catch( CInvalidFormatEtcException
& )
427 OSL_FAIL( "Unexpected exception" );
429 catch( CStgTransferHelper::CStgTransferException
& ex
)
431 return translateStgExceptionCode( ex
.m_hr
);
441 // the transferable must have only text, so we will synthesize unicode text
443 void CXTDataObject::renderSynthesizedUnicodeAndSetupStgMedium( FORMATETC
const & fetc
, STGMEDIUM
& stgmedium
)
445 OSL_ASSERT( CF_UNICODETEXT
== fetc
.cfFormat
);
447 Any aAny
= m_XTransferable
->getTransferData( m_FormatRegistrar
.getRegisteredTextFlavor( ) );
449 // unfortunately not all transferables fulfill the
450 // spec. and do throw an UnsupportedFlavorException
451 // so we must check the any
452 if ( !aAny
.hasValue( ) )
454 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
455 throw UnsupportedFlavorException( );
458 Sequence
< sal_Int8
> aText
;
461 CStgTransferHelper stgTransfHelper
;
463 MultiByteToWideCharEx(
464 CFormatRegistrar::getRegisteredTextCodePage( ),
465 reinterpret_cast< char* >( aText
.getArray( ) ),
469 setupStgMedium( fetc
, stgTransfHelper
, stgmedium
);
472 // the transferable must have only unicode text so we will synthesize text
474 void CXTDataObject::renderSynthesizedTextAndSetupStgMedium( FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
476 OSL_ASSERT( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc
.cfFormat
) );
478 DataFlavor aFlavor
= formatEtcToDataFlavor(
479 CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT
) );
481 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
483 // unfortunately not all transferables fulfill the
484 // spec. and do throw an UnsupportedFlavorException
485 // so we must check the any
486 if ( !aAny
.hasValue( ) )
488 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
489 throw UnsupportedFlavorException( );
492 OUString aUnicodeText
;
493 aAny
>>= aUnicodeText
;
495 CStgTransferHelper stgTransfHelper
;
497 WideCharToMultiByteEx(
499 o3tl::toW( aUnicodeText
.getStr( ) ),
500 aUnicodeText
.getLength( ),
503 setupStgMedium( fetc
, stgTransfHelper
, stgmedium
);
506 void CXTDataObject::renderSynthesizedHtmlAndSetupStgMedium( FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
508 OSL_ASSERT( CDataFormatTranslator::isHTMLFormat( fetc
.cfFormat
) );
512 // creating a DataFlavor on the fly
513 aFlavor
.MimeType
= "text/html";
514 aFlavor
.DataType
= cppu::UnoType
<Sequence
< sal_Int8
>>::get();
516 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
518 // unfortunately not all transferables fulfill the
519 // spec. and do throw an UnsupportedFlavorException
520 // so we must check the any
521 if ( !aAny
.hasValue( ) )
523 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
524 throw UnsupportedFlavorException( );
527 Sequence
< sal_Int8
> aTextHtmlSequence
;
528 aAny
>>= aTextHtmlSequence
;
530 Sequence
< sal_Int8
> aHTMLFormatSequence
= TextHtmlToHTMLFormat( aTextHtmlSequence
);
532 sal_uInt32 nBytesToTransfer
= aHTMLFormatSequence
.getLength( );
534 renderDataAndSetupStgMedium(
535 reinterpret_cast< const sal_Int8
* >( aHTMLFormatSequence
.getArray( ) ),
542 // IDataObject->EnumFormatEtc
544 STDMETHODIMP
CXTDataObject::EnumFormatEtc(
545 DWORD dwDirection
, IEnumFORMATETC
** ppenumFormatetc
)
547 if ( nullptr == ppenumFormatetc
)
550 if ( DATADIR_SET
== dwDirection
)
553 *ppenumFormatetc
= nullptr;
555 InitializeFormatEtcContainer( );
558 if ( DATADIR_GET
== dwDirection
)
560 *ppenumFormatetc
= new CEnumFormatEtc( this, m_FormatEtcContainer
);
561 static_cast< LPUNKNOWN
>( *ppenumFormatetc
)->AddRef( );
571 // IDataObject->QueryGetData
573 STDMETHODIMP
CXTDataObject::QueryGetData( FORMATETC
* pFormatetc
)
575 if ( (nullptr == pFormatetc
) || IsBadReadPtr( pFormatetc
, sizeof( FORMATETC
) ) )
578 InitializeFormatEtcContainer( );
580 CFormatEtc
aFormatetc(*pFormatetc
);
581 return m_FormatEtcContainer
.hasFormatEtc(aFormatetc
) ? S_OK
: S_FALSE
;
584 // IDataObject->GetDataHere
586 STDMETHODIMP
CXTDataObject::GetDataHere( FORMATETC
*, STGMEDIUM
* )
591 // IDataObject->GetCanonicalFormatEtc
593 STDMETHODIMP
CXTDataObject::GetCanonicalFormatEtc( FORMATETC
*, FORMATETC
* )
598 // IDataObject->SetData
600 STDMETHODIMP
CXTDataObject::SetData( FORMATETC
*, STGMEDIUM
*, BOOL
)
605 // IDataObject->DAdvise
607 STDMETHODIMP
CXTDataObject::DAdvise( FORMATETC
*, DWORD
, IAdviseSink
*, DWORD
* )
612 // IDataObject->DUnadvise
614 STDMETHODIMP
CXTDataObject::DUnadvise( DWORD
)
619 // IDataObject->EnumDAdvise
621 STDMETHODIMP
CXTDataObject::EnumDAdvise( IEnumSTATDATA
** )
626 // for our convenience
628 CXTDataObject::operator IDataObject
*( )
630 return static_cast< IDataObject
* >( this );
634 DataFlavor
CXTDataObject::formatEtcToDataFlavor( const FORMATETC
& aFormatEtc
) const
638 if ( m_FormatRegistrar
.hasSynthesizedLocale( ) )
639 aFlavor
= m_DataFormatTranslator
.getDataFlavorFromFormatEtc(
640 aFormatEtc
.cfFormat
, CFormatRegistrar::getSynthesizedLocale());
642 aFlavor
= m_DataFormatTranslator
.getDataFlavorFromFormatEtc(aFormatEtc
.cfFormat
);
644 if ( !aFlavor
.MimeType
.getLength( ) )
645 throw UnsupportedFlavorException( );
650 inline void CXTDataObject::InitializeFormatEtcContainer( )
652 if ( !m_bFormatEtcContainerInitialized
)
654 m_FormatRegistrar
.RegisterFormats( m_XTransferable
, m_FormatEtcContainer
);
655 m_bFormatEtcContainerInitialized
= true;
659 CEnumFormatEtc::CEnumFormatEtc( LPUNKNOWN lpUnkOuter
, const CFormatEtcContainer
& aFormatEtcContainer
) :
661 m_lpUnkOuter( lpUnkOuter
),
662 m_FormatEtcContainer( aFormatEtcContainer
)
667 // IUnknown->QueryInterface
669 STDMETHODIMP
CEnumFormatEtc::QueryInterface( REFIID iid
, void** ppvObject
)
671 if ( nullptr == ppvObject
)
674 HRESULT hr
= E_NOINTERFACE
;
676 *ppvObject
= nullptr;
678 if ( ( __uuidof( IUnknown
) == iid
) ||
679 ( __uuidof( IEnumFORMATETC
) == iid
) )
681 *ppvObject
= static_cast< IUnknown
* >( this );
682 static_cast< LPUNKNOWN
>( *ppvObject
)->AddRef( );
691 STDMETHODIMP_(ULONG
) CEnumFormatEtc::AddRef( )
693 // keep the dataobject alive
694 m_lpUnkOuter
->AddRef( );
695 return InterlockedIncrement( &m_nRefCnt
);
700 STDMETHODIMP_(ULONG
) CEnumFormatEtc::Release( )
702 // release the outer dataobject
703 m_lpUnkOuter
->Release( );
705 ULONG nRefCnt
= InterlockedDecrement( &m_nRefCnt
);
712 // IEnumFORMATETC->Next
714 STDMETHODIMP
CEnumFormatEtc::Next( ULONG nRequested
, FORMATETC
* lpDest
, ULONG
* lpFetched
)
716 if ( ( nRequested
< 1 ) ||
717 (( nRequested
> 1 ) && ( nullptr == lpFetched
)) ||
718 IsBadWritePtr( lpDest
, sizeof( FORMATETC
) * nRequested
) )
721 sal_uInt32 nFetched
= m_FormatEtcContainer
.nextFormatEtc( lpDest
, nRequested
);
723 if ( nullptr != lpFetched
)
724 *lpFetched
= nFetched
;
726 return (nFetched
== nRequested
) ? S_OK
: S_FALSE
;
729 // IEnumFORMATETC->Skip
731 STDMETHODIMP
CEnumFormatEtc::Skip( ULONG celt
)
733 return m_FormatEtcContainer
.skipFormatEtc( celt
) ? S_OK
: S_FALSE
;
736 // IEnumFORMATETC->Reset
738 STDMETHODIMP
CEnumFormatEtc::Reset( )
740 m_FormatEtcContainer
.beginEnumFormatEtc( );
744 // IEnumFORMATETC->Clone
746 STDMETHODIMP
CEnumFormatEtc::Clone( IEnumFORMATETC
** ppenum
)
748 if ( nullptr == ppenum
)
751 *ppenum
= new CEnumFormatEtc( m_lpUnkOuter
, m_FormatEtcContainer
);
752 static_cast< LPUNKNOWN
>( *ppenum
)->AddRef( );
757 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */