648 lines
15 KiB
C++
648 lines
15 KiB
C++
|
/*++
|
|||
|
|
|||
|
Copyright (c) 2000 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
mdwriter.cxx
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This file contains implementation for snapshot writer class
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Ming Lu (MingLu) 30-Apr-2000
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include <dbgutil.h>
|
|||
|
#include "mdwriter.hxx"
|
|||
|
|
|||
|
#define TIMEOUT_INTERVAL ( 2 * 60 )
|
|||
|
#define DEFAULT_SAVE_TIMEOUT 30000
|
|||
|
#define MDWRITER_EVENT_TIMEOUT 30000
|
|||
|
#define IISCOMPONENT L"IISMETABASE"
|
|||
|
#define METABASEPATH L"%windir%\\system32\\inetsrv"
|
|||
|
#define METABASENAME1 L"metabase.bin"
|
|||
|
#define METABASENAME2 L"MetaBase.XML"
|
|||
|
#define METABASENAME3 L"MBSchema.XML"
|
|||
|
|
|||
|
#define NT_SETUP_KEY "SYSTEM\\Setup"
|
|||
|
|
|||
|
static VSS_ID s_WRITERID =
|
|||
|
{
|
|||
|
0x59b1f0cf, 0x90ef, 0x465f,
|
|||
|
0x96, 0x09, 0x6c, 0xa8, 0xb2, 0x93, 0x83, 0x66
|
|||
|
};
|
|||
|
|
|||
|
static LPCWSTR s_WRITERNAME = L"IIS Metabase Writer";
|
|||
|
|
|||
|
BOOL g_fWriterSubscribed = FALSE;
|
|||
|
CIISVssWriter * g_pIISVssWriter = NULL;
|
|||
|
|
|||
|
VOID CALLBACK UnlockMBProc(
|
|||
|
LPVOID pIISVssWriter,
|
|||
|
DWORD dwTimerLowValue,
|
|||
|
DWORD dwTimerHighValue
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
CIISVssWriter::Initialize(
|
|||
|
VOID
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
hr = CVssWriter::Initialize( s_WRITERID,
|
|||
|
s_WRITERNAME,
|
|||
|
VSS_UT_SYSTEMSERVICE,
|
|||
|
VSS_ST_OTHER
|
|||
|
);
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error in base object Initialize(). hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
m_hTimer = CreateWaitableTimer( NULL, FALSE, NULL );
|
|||
|
if( !m_hTimer )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error creating timer object. hr = %x\n",
|
|||
|
HRESULT_FROM_WIN32( GetLastError() ) ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
hr = CoCreateInstance( CLSID_MDCOM, NULL, CLSCTX_SERVER, IID_IMDCOM, (void**) &m_pMdObject);
|
|||
|
if( SUCCEEDED( hr ) )
|
|||
|
{
|
|||
|
hr = m_pMdObject->ComMDInitialize();
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error initialize MDCOM object. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
m_pMdObject->Release();
|
|||
|
m_pMdObject = NULL;
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error creating MDCOM object. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnIdentify(
|
|||
|
IN IVssCreateWriterMetadata *pMetadata
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
hr = pMetadata->AddComponent( VSS_CT_FILEGROUP,
|
|||
|
NULL,
|
|||
|
IISCOMPONENT,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
0,
|
|||
|
FALSE,
|
|||
|
FALSE,
|
|||
|
FALSE );
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error AddComponent(). hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
hr = pMetadata->AddFilesToFileGroup( NULL,
|
|||
|
IISCOMPONENT,
|
|||
|
METABASEPATH,
|
|||
|
METABASENAME1,
|
|||
|
FALSE,
|
|||
|
NULL );
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error AddFilesToFileGroup(). hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
hr = pMetadata->AddFilesToFileGroup( NULL,
|
|||
|
IISCOMPONENT,
|
|||
|
METABASEPATH,
|
|||
|
METABASENAME2,
|
|||
|
FALSE,
|
|||
|
NULL );
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error AddFilesToFileGroup(). hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
hr = pMetadata->AddFilesToFileGroup( NULL,
|
|||
|
IISCOMPONENT,
|
|||
|
METABASEPATH,
|
|||
|
METABASENAME3,
|
|||
|
FALSE,
|
|||
|
NULL );
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error AddFilesToFileGroup(). hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
hr = pMetadata->SetRestoreMethod(
|
|||
|
VSS_RME_RESTORE_AT_REBOOT, // restore method
|
|||
|
NULL, // service name
|
|||
|
NULL, // user procedure
|
|||
|
VSS_WRE_NEVER, // when to call writer restore method
|
|||
|
TRUE // reboot is required
|
|||
|
);
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error setting restore method. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnPrepareBackup(
|
|||
|
IN IVssWriterComponents *pWriterComponents
|
|||
|
)
|
|||
|
{
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnPrepareSnapshot(
|
|||
|
VOID
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
if( IsPathAffected( METABASEPATH ) )
|
|||
|
{
|
|||
|
//
|
|||
|
// First try to lock the tree
|
|||
|
//
|
|||
|
|
|||
|
hr = m_pMdObject->ComMDOpenMetaObjectW( METADATA_MASTER_ROOT_HANDLE,
|
|||
|
NULL,
|
|||
|
METADATA_PERMISSION_READ,
|
|||
|
DEFAULT_SAVE_TIMEOUT,
|
|||
|
&m_mdhRoot);
|
|||
|
|
|||
|
if ( SUCCEEDED( hr ) ) {
|
|||
|
//
|
|||
|
// call metadata com api
|
|||
|
//
|
|||
|
|
|||
|
hr = m_pMdObject->ComMDSaveData( m_mdhRoot );
|
|||
|
|
|||
|
if( SUCCEEDED( hr ) )
|
|||
|
{
|
|||
|
hr = m_pMdObject->ComMDCloseMetaObject( m_mdhRoot );
|
|||
|
|
|||
|
if( SUCCEEDED ( hr ) )
|
|||
|
{
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on unlocking the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on saving the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
m_pMdObject->ComMDCloseMetaObject( m_mdhRoot );
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on locking down the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnFreeze(
|
|||
|
VOID
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
if( IsPathAffected( METABASEPATH ) )
|
|||
|
{
|
|||
|
//
|
|||
|
// Lock down the metabase
|
|||
|
//
|
|||
|
|
|||
|
hr = m_pMdObject->ComMDOpenMetaObjectW(METADATA_MASTER_ROOT_HANDLE,
|
|||
|
NULL,
|
|||
|
METADATA_PERMISSION_READ,
|
|||
|
DEFAULT_SAVE_TIMEOUT,
|
|||
|
&m_mdhRoot);
|
|||
|
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on locking down the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if( !ResetTimer( m_hTimer, TIMEOUT_INTERVAL ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Could not reset the internal timer. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
EnterCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
m_fMBLocked = TRUE;
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnThaw(
|
|||
|
VOID
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
if( IsPathAffected( METABASEPATH ) )
|
|||
|
{
|
|||
|
|
|||
|
EnterCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
if( m_fMBLocked )
|
|||
|
{
|
|||
|
hr = m_pMdObject->ComMDCloseMetaObject( m_mdhRoot );
|
|||
|
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on unlocking the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
m_fMBLocked = FALSE;
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
CancelWaitableTimer( m_hTimer );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnBackupComplete(
|
|||
|
IN IVssWriterComponents *pWriterComponents
|
|||
|
)
|
|||
|
{
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
bool STDMETHODCALLTYPE
|
|||
|
CIISVssWriter::OnAbort(
|
|||
|
VOID
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
if( IsPathAffected( METABASEPATH ) )
|
|||
|
{
|
|||
|
|
|||
|
EnterCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
if( m_fMBLocked )
|
|||
|
{
|
|||
|
hr = m_pMdObject->ComMDCloseMetaObject( m_mdhRoot );
|
|||
|
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on unlocking the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
m_fMBLocked = FALSE;
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
CancelWaitableTimer( m_hTimer );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
CIISVssWriter::UnlockMetaBase(
|
|||
|
VOID
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr;
|
|||
|
|
|||
|
EnterCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
if( m_fMBLocked )
|
|||
|
{
|
|||
|
hr = m_pMdObject->ComMDCloseMetaObject( m_mdhRoot );
|
|||
|
|
|||
|
if( FAILED( hr ) )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on unlocking the metabase. hr = %x\n",
|
|||
|
hr ));
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
m_fMBLocked = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
LeaveCriticalSection( &m_csMBLock );
|
|||
|
}
|
|||
|
|
|||
|
BOOL
|
|||
|
CIISVssWriter::ResetTimer(
|
|||
|
HANDLE hTimer,
|
|||
|
DWORD dwDuration
|
|||
|
)
|
|||
|
{
|
|||
|
LARGE_INTEGER li;
|
|||
|
const int nNanosecondsPersecond = 10000000;
|
|||
|
__int64 qwTimeFromNowInNanoseconds =
|
|||
|
(__int64)dwDuration * nNanosecondsPersecond;
|
|||
|
|
|||
|
qwTimeFromNowInNanoseconds = -qwTimeFromNowInNanoseconds;
|
|||
|
|
|||
|
li.LowPart = (DWORD) (qwTimeFromNowInNanoseconds & 0xFFFFFFFF);
|
|||
|
li.HighPart = (LONG) (qwTimeFromNowInNanoseconds >> 32);
|
|||
|
|
|||
|
if( !SetWaitableTimer( hTimer, &li, 0, UnlockMBProc, this, FALSE ) )
|
|||
|
{
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
VOID CALLBACK
|
|||
|
UnlockMBProc(
|
|||
|
LPVOID pIISVssWriter,
|
|||
|
DWORD dwTimerLowValue,
|
|||
|
DWORD dwTimerHighValue
|
|||
|
)
|
|||
|
{
|
|||
|
( ( CIISVssWriter * )pIISVssWriter )->UnlockMetaBase();
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
InitMDWriterThread(
|
|||
|
HANDLE hMDWriterEvent
|
|||
|
)
|
|||
|
{
|
|||
|
HKEY hKey;
|
|||
|
DWORD dwType;
|
|||
|
DWORD cbData;
|
|||
|
DWORD dwSetupInProgress = 0;
|
|||
|
DWORD dwUpgradeInProcess = 0;
|
|||
|
|
|||
|
DBG_ASSERT( hMDWriterEvent != NULL );
|
|||
|
|
|||
|
//
|
|||
|
// Read the setup registry key to see if we are in
|
|||
|
// setup mode. If we are, don't init IIS writer.
|
|||
|
//
|
|||
|
if ( !RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|||
|
NT_SETUP_KEY,
|
|||
|
0,
|
|||
|
KEY_QUERY_VALUE,
|
|||
|
&hKey ) )
|
|||
|
{
|
|||
|
cbData = sizeof( DWORD );
|
|||
|
|
|||
|
if( !RegQueryValueEx( hKey,
|
|||
|
"SystemSetupInProgress",
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
( LPBYTE )&dwSetupInProgress,
|
|||
|
&cbData ) )
|
|||
|
{
|
|||
|
if( dwType == REG_DWORD && dwSetupInProgress != 0 )
|
|||
|
{
|
|||
|
//
|
|||
|
// We are in setup mode
|
|||
|
//
|
|||
|
RegCloseKey( hKey );
|
|||
|
goto exit;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if( !RegQueryValueEx( hKey,
|
|||
|
"UpgradeInProgress",
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
( LPBYTE )&dwUpgradeInProcess,
|
|||
|
&cbData ) )
|
|||
|
{
|
|||
|
if( dwType == REG_DWORD && dwUpgradeInProcess != 0 )
|
|||
|
{
|
|||
|
//
|
|||
|
// We are in upgrade mode
|
|||
|
//
|
|||
|
RegCloseKey( hKey );
|
|||
|
goto exit;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
RegCloseKey( hKey );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// OK, we are not in setup mode, initialize our IIS writer
|
|||
|
//
|
|||
|
|
|||
|
g_pIISVssWriter = new CIISVssWriter;
|
|||
|
if ( g_pIISVssWriter == NULL )
|
|||
|
{
|
|||
|
//
|
|||
|
// oh well. guess we won<6F>t support snapshots
|
|||
|
//
|
|||
|
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on creating the writer object, out of memory\n"
|
|||
|
));
|
|||
|
|
|||
|
goto exit;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//
|
|||
|
// cool, we<77>ve got the object now.
|
|||
|
//
|
|||
|
|
|||
|
if( !g_pIISVssWriter->Initialize() )
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on initializing the writer object\n"
|
|||
|
));
|
|||
|
|
|||
|
delete g_pIISVssWriter;
|
|||
|
g_pIISVssWriter = NULL;
|
|||
|
|
|||
|
goto exit;
|
|||
|
}
|
|||
|
|
|||
|
if( SUCCEEDED( g_pIISVssWriter->Subscribe() ) )
|
|||
|
{
|
|||
|
g_fWriterSubscribed = TRUE;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
DBGPRINTF(( DBG_CONTEXT,
|
|||
|
"Error on subscribing the writer object\n"
|
|||
|
));
|
|||
|
|
|||
|
delete g_pIISVssWriter;
|
|||
|
g_pIISVssWriter = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
exit:
|
|||
|
//
|
|||
|
// Let the TerminateMDWriter know we are ready to terminate during
|
|||
|
// IISADMIN service shutdown
|
|||
|
//
|
|||
|
SetEvent( hMDWriterEvent );
|
|||
|
}
|
|||
|
|
|||
|
HRESULT
|
|||
|
InitializeMDWriter(
|
|||
|
HANDLE hMDWriterEvent
|
|||
|
)
|
|||
|
{
|
|||
|
HRESULT hr = S_OK;
|
|||
|
HANDLE hThread = NULL;
|
|||
|
DWORD dwThreadID;
|
|||
|
|
|||
|
DBG_ASSERT( hMDWriterEvent != NULL );
|
|||
|
|
|||
|
hThread = CreateThread( NULL,
|
|||
|
0,
|
|||
|
( LPTHREAD_START_ROUTINE )InitMDWriterThread,
|
|||
|
( PVOID )hMDWriterEvent,
|
|||
|
0,
|
|||
|
&dwThreadID);
|
|||
|
if( hThread == NULL )
|
|||
|
{
|
|||
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|||
|
}
|
|||
|
|
|||
|
return hr;
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
TerminateMDWriter(
|
|||
|
HANDLE hMDWriterEvent
|
|||
|
)
|
|||
|
{
|
|||
|
DBG_ASSERT( hMDWriterEvent != NULL );
|
|||
|
|
|||
|
//
|
|||
|
// Only do cleanup if the hMDWriterEvent is signaled
|
|||
|
//
|
|||
|
if( WAIT_OBJECT_0 == WaitForSingleObject( hMDWriterEvent,
|
|||
|
MDWRITER_EVENT_TIMEOUT ) )
|
|||
|
{
|
|||
|
if( g_fWriterSubscribed )
|
|||
|
{
|
|||
|
DBG_ASSERT( g_pIISVssWriter );
|
|||
|
|
|||
|
g_pIISVssWriter->Unsubscribe();
|
|||
|
}
|
|||
|
|
|||
|
if( g_pIISVssWriter )
|
|||
|
{
|
|||
|
delete g_pIISVssWriter;
|
|||
|
g_pIISVssWriter = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|