538 lines
13 KiB
C++
538 lines
13 KiB
C++
|
/*
|
||
|
* QEMU Guest Agent win32 VSS Provider implementations
|
||
|
*
|
||
|
* Copyright Hitachi Data Systems Corp. 2013
|
||
|
*
|
||
|
* Authors:
|
||
|
* Tomoki Sekiyama <tomoki.sekiyama@hds.com>
|
||
|
*
|
||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||
|
* See the COPYING file in the top-level directory.
|
||
|
*/
|
||
|
|
||
|
#include "qemu/osdep.h"
|
||
|
#include "vss-common.h"
|
||
|
#ifdef HAVE_VSS_SDK
|
||
|
#include <vscoordint.h>
|
||
|
#else
|
||
|
#include <vsadmin.h>
|
||
|
#endif
|
||
|
#include <vsprov.h>
|
||
|
|
||
|
#define VSS_TIMEOUT_MSEC (60*1000)
|
||
|
|
||
|
static long g_nComObjsInUse;
|
||
|
HINSTANCE g_hinstDll;
|
||
|
|
||
|
/* VSS common GUID's */
|
||
|
|
||
|
const CLSID CLSID_VSSCoordinator = { 0xE579AB5F, 0x1CC4, 0x44b4,
|
||
|
{0xBE, 0xD9, 0xDE, 0x09, 0x91, 0xFF, 0x06, 0x23} };
|
||
|
const IID IID_IVssAdmin = { 0x77ED5996, 0x2F63, 0x11d3,
|
||
|
{0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };
|
||
|
|
||
|
const IID IID_IVssHardwareSnapshotProvider = { 0x9593A157, 0x44E9, 0x4344,
|
||
|
{0xBB, 0xEB, 0x44, 0xFB, 0xF9, 0xB0, 0x6B, 0x10} };
|
||
|
const IID IID_IVssSoftwareSnapshotProvider = { 0x609e123e, 0x2c5a, 0x44d3,
|
||
|
{0x8f, 0x01, 0x0b, 0x1d, 0x9a, 0x47, 0xd1, 0xff} };
|
||
|
const IID IID_IVssProviderCreateSnapshotSet = { 0x5F894E5B, 0x1E39, 0x4778,
|
||
|
{0x8E, 0x23, 0x9A, 0xBA, 0xD9, 0xF0, 0xE0, 0x8C} };
|
||
|
const IID IID_IVssProviderNotifications = { 0xE561901F, 0x03A5, 0x4afe,
|
||
|
{0x86, 0xD0, 0x72, 0xBA, 0xEE, 0xCE, 0x70, 0x04} };
|
||
|
|
||
|
const IID IID_IVssEnumObject = { 0xAE1C7110, 0x2F60, 0x11d3,
|
||
|
{0x8A, 0x39, 0x00, 0xC0, 0x4F, 0x72, 0xD8, 0xE3} };
|
||
|
|
||
|
|
||
|
void LockModule(BOOL lock)
|
||
|
{
|
||
|
if (lock) {
|
||
|
InterlockedIncrement(&g_nComObjsInUse);
|
||
|
} else {
|
||
|
InterlockedDecrement(&g_nComObjsInUse);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Empty enumerator for VssObject */
|
||
|
|
||
|
class CQGAVSSEnumObject : public IVssEnumObject
|
||
|
{
|
||
|
public:
|
||
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
|
||
|
STDMETHODIMP_(ULONG) AddRef();
|
||
|
STDMETHODIMP_(ULONG) Release();
|
||
|
|
||
|
/* IVssEnumObject Methods */
|
||
|
STDMETHODIMP Next(
|
||
|
ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched);
|
||
|
STDMETHODIMP Skip(ULONG celt);
|
||
|
STDMETHODIMP Reset(void);
|
||
|
STDMETHODIMP Clone(IVssEnumObject **ppenum);
|
||
|
|
||
|
/* CQGAVSSEnumObject Methods */
|
||
|
CQGAVSSEnumObject();
|
||
|
~CQGAVSSEnumObject();
|
||
|
|
||
|
private:
|
||
|
long m_nRefCount;
|
||
|
};
|
||
|
|
||
|
CQGAVSSEnumObject::CQGAVSSEnumObject()
|
||
|
{
|
||
|
m_nRefCount = 0;
|
||
|
LockModule(TRUE);
|
||
|
}
|
||
|
|
||
|
CQGAVSSEnumObject::~CQGAVSSEnumObject()
|
||
|
{
|
||
|
LockModule(FALSE);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVSSEnumObject::QueryInterface(REFIID riid, void **ppObj)
|
||
|
{
|
||
|
if (riid == IID_IUnknown || riid == IID_IVssEnumObject) {
|
||
|
*ppObj = static_cast<void*>(static_cast<IVssEnumObject*>(this));
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppObj = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CQGAVSSEnumObject::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_nRefCount);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CQGAVSSEnumObject::Release()
|
||
|
{
|
||
|
long nRefCount = InterlockedDecrement(&m_nRefCount);
|
||
|
if (m_nRefCount == 0) {
|
||
|
delete this;
|
||
|
}
|
||
|
return nRefCount;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVSSEnumObject::Next(
|
||
|
ULONG celt, VSS_OBJECT_PROP *rgelt, ULONG *pceltFetched)
|
||
|
{
|
||
|
*pceltFetched = 0;
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVSSEnumObject::Skip(ULONG celt)
|
||
|
{
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVSSEnumObject::Reset(void)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVSSEnumObject::Clone(IVssEnumObject **ppenum)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* QGAVssProvider */
|
||
|
|
||
|
class CQGAVssProvider :
|
||
|
public IVssSoftwareSnapshotProvider,
|
||
|
public IVssProviderCreateSnapshotSet,
|
||
|
public IVssProviderNotifications
|
||
|
{
|
||
|
public:
|
||
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppObj);
|
||
|
STDMETHODIMP_(ULONG) AddRef();
|
||
|
STDMETHODIMP_(ULONG) Release();
|
||
|
|
||
|
/* IVssSoftwareSnapshotProvider Methods */
|
||
|
STDMETHODIMP SetContext(LONG lContext);
|
||
|
STDMETHODIMP GetSnapshotProperties(
|
||
|
VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp);
|
||
|
STDMETHODIMP Query(
|
||
|
VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
|
||
|
VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum);
|
||
|
STDMETHODIMP DeleteSnapshots(
|
||
|
VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
|
||
|
BOOL bForceDelete, LONG *plDeletedSnapshots,
|
||
|
VSS_ID *pNondeletedSnapshotID);
|
||
|
STDMETHODIMP BeginPrepareSnapshot(
|
||
|
VSS_ID SnapshotSetId, VSS_ID SnapshotId,
|
||
|
VSS_PWSZ pwszVolumeName, LONG lNewContext);
|
||
|
STDMETHODIMP IsVolumeSupported(
|
||
|
VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider);
|
||
|
STDMETHODIMP IsVolumeSnapshotted(
|
||
|
VSS_PWSZ pwszVolumeName, BOOL *pbSnapshotsPresent,
|
||
|
LONG *plSnapshotCompatibility);
|
||
|
STDMETHODIMP SetSnapshotProperty(
|
||
|
VSS_ID SnapshotId, VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId,
|
||
|
VARIANT vProperty);
|
||
|
STDMETHODIMP RevertToSnapshot(VSS_ID SnapshotId);
|
||
|
STDMETHODIMP QueryRevertStatus(VSS_PWSZ pwszVolume, IVssAsync **ppAsync);
|
||
|
|
||
|
/* IVssProviderCreateSnapshotSet Methods */
|
||
|
STDMETHODIMP EndPrepareSnapshots(VSS_ID SnapshotSetId);
|
||
|
STDMETHODIMP PreCommitSnapshots(VSS_ID SnapshotSetId);
|
||
|
STDMETHODIMP CommitSnapshots(VSS_ID SnapshotSetId);
|
||
|
STDMETHODIMP PostCommitSnapshots(
|
||
|
VSS_ID SnapshotSetId, LONG lSnapshotsCount);
|
||
|
STDMETHODIMP PreFinalCommitSnapshots(VSS_ID SnapshotSetId);
|
||
|
STDMETHODIMP PostFinalCommitSnapshots(VSS_ID SnapshotSetId);
|
||
|
STDMETHODIMP AbortSnapshots(VSS_ID SnapshotSetId);
|
||
|
|
||
|
/* IVssProviderNotifications Methods */
|
||
|
STDMETHODIMP OnLoad(IUnknown *pCallback);
|
||
|
STDMETHODIMP OnUnload(BOOL bForceUnload);
|
||
|
|
||
|
/* CQGAVssProvider Methods */
|
||
|
CQGAVssProvider();
|
||
|
~CQGAVssProvider();
|
||
|
|
||
|
private:
|
||
|
long m_nRefCount;
|
||
|
};
|
||
|
|
||
|
CQGAVssProvider::CQGAVssProvider()
|
||
|
{
|
||
|
m_nRefCount = 0;
|
||
|
LockModule(TRUE);
|
||
|
}
|
||
|
|
||
|
CQGAVssProvider::~CQGAVssProvider()
|
||
|
{
|
||
|
LockModule(FALSE);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::QueryInterface(REFIID riid, void **ppObj)
|
||
|
{
|
||
|
if (riid == IID_IUnknown) {
|
||
|
*ppObj = static_cast<void*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
if (riid == IID_IVssSoftwareSnapshotProvider) {
|
||
|
*ppObj = static_cast<void*>(
|
||
|
static_cast<IVssSoftwareSnapshotProvider*>(this));
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
if (riid == IID_IVssProviderCreateSnapshotSet) {
|
||
|
*ppObj = static_cast<void*>(
|
||
|
static_cast<IVssProviderCreateSnapshotSet*>(this));
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
if (riid == IID_IVssProviderNotifications) {
|
||
|
*ppObj = static_cast<void*>(
|
||
|
static_cast<IVssProviderNotifications*>(this));
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppObj = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CQGAVssProvider::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_nRefCount);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CQGAVssProvider::Release()
|
||
|
{
|
||
|
long nRefCount = InterlockedDecrement(&m_nRefCount);
|
||
|
if (m_nRefCount == 0) {
|
||
|
delete this;
|
||
|
}
|
||
|
return nRefCount;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* IVssSoftwareSnapshotProvider methods
|
||
|
*/
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::SetContext(LONG lContext)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::GetSnapshotProperties(
|
||
|
VSS_ID SnapshotId, VSS_SNAPSHOT_PROP *pProp)
|
||
|
{
|
||
|
return VSS_E_OBJECT_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::Query(
|
||
|
VSS_ID QueriedObjectId, VSS_OBJECT_TYPE eQueriedObjectType,
|
||
|
VSS_OBJECT_TYPE eReturnedObjectsType, IVssEnumObject **ppEnum)
|
||
|
{
|
||
|
try {
|
||
|
*ppEnum = new CQGAVSSEnumObject;
|
||
|
} catch (...) {
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
(*ppEnum)->AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::DeleteSnapshots(
|
||
|
VSS_ID SourceObjectId, VSS_OBJECT_TYPE eSourceObjectType,
|
||
|
BOOL bForceDelete, LONG *plDeletedSnapshots, VSS_ID *pNondeletedSnapshotID)
|
||
|
{
|
||
|
*plDeletedSnapshots = 0;
|
||
|
*pNondeletedSnapshotID = SourceObjectId;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::BeginPrepareSnapshot(
|
||
|
VSS_ID SnapshotSetId, VSS_ID SnapshotId,
|
||
|
VSS_PWSZ pwszVolumeName, LONG lNewContext)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::IsVolumeSupported(
|
||
|
VSS_PWSZ pwszVolumeName, BOOL *pbSupportedByThisProvider)
|
||
|
{
|
||
|
HANDLE hEventFrozen;
|
||
|
|
||
|
/* Check if a requester is qemu-ga by whether an event is created */
|
||
|
hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
|
||
|
if (!hEventFrozen) {
|
||
|
*pbSupportedByThisProvider = FALSE;
|
||
|
return S_OK;
|
||
|
}
|
||
|
CloseHandle(hEventFrozen);
|
||
|
|
||
|
*pbSupportedByThisProvider = TRUE;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::IsVolumeSnapshotted(VSS_PWSZ pwszVolumeName,
|
||
|
BOOL *pbSnapshotsPresent, LONG *plSnapshotCompatibility)
|
||
|
{
|
||
|
*pbSnapshotsPresent = FALSE;
|
||
|
*plSnapshotCompatibility = 0;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::SetSnapshotProperty(VSS_ID SnapshotId,
|
||
|
VSS_SNAPSHOT_PROPERTY_ID eSnapshotPropertyId, VARIANT vProperty)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::RevertToSnapshot(VSS_ID SnapshotId)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::QueryRevertStatus(
|
||
|
VSS_PWSZ pwszVolume, IVssAsync **ppAsync)
|
||
|
{
|
||
|
return E_NOTIMPL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* IVssProviderCreateSnapshotSet methods
|
||
|
*/
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::EndPrepareSnapshots(VSS_ID SnapshotSetId)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::PreCommitSnapshots(VSS_ID SnapshotSetId)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::CommitSnapshots(VSS_ID SnapshotSetId)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
HANDLE hEventFrozen, hEventThaw, hEventTimeout;
|
||
|
|
||
|
hEventFrozen = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_FROZEN);
|
||
|
if (!hEventFrozen) {
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
hEventThaw = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_THAW);
|
||
|
if (!hEventThaw) {
|
||
|
CloseHandle(hEventFrozen);
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
hEventTimeout = OpenEvent(EVENT_ALL_ACCESS, FALSE, EVENT_NAME_TIMEOUT);
|
||
|
if (!hEventTimeout) {
|
||
|
CloseHandle(hEventFrozen);
|
||
|
CloseHandle(hEventThaw);
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
/* Send event to qemu-ga to notify filesystem is frozen */
|
||
|
SetEvent(hEventFrozen);
|
||
|
|
||
|
/* Wait until the snapshot is taken by the host. */
|
||
|
if (WaitForSingleObject(hEventThaw, VSS_TIMEOUT_MSEC) != WAIT_OBJECT_0) {
|
||
|
/* Send event to qemu-ga to notify the provider is timed out */
|
||
|
SetEvent(hEventTimeout);
|
||
|
}
|
||
|
|
||
|
CloseHandle(hEventThaw);
|
||
|
CloseHandle(hEventFrozen);
|
||
|
CloseHandle(hEventTimeout);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::PostCommitSnapshots(
|
||
|
VSS_ID SnapshotSetId, LONG lSnapshotsCount)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::PreFinalCommitSnapshots(VSS_ID SnapshotSetId)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::PostFinalCommitSnapshots(VSS_ID SnapshotSetId)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::AbortSnapshots(VSS_ID SnapshotSetId)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* IVssProviderNotifications methods
|
||
|
*/
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::OnLoad(IUnknown *pCallback)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProvider::OnUnload(BOOL bForceUnload)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* CQGAVssProviderFactory class
|
||
|
*/
|
||
|
|
||
|
class CQGAVssProviderFactory : public IClassFactory
|
||
|
{
|
||
|
public:
|
||
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
|
||
|
STDMETHODIMP_(ULONG) AddRef();
|
||
|
STDMETHODIMP_(ULONG) Release();
|
||
|
STDMETHODIMP CreateInstance(
|
||
|
IUnknown *pUnknownOuter, REFIID iid, void **ppv);
|
||
|
STDMETHODIMP LockServer(BOOL lock) { return E_NOTIMPL; }
|
||
|
|
||
|
CQGAVssProviderFactory();
|
||
|
~CQGAVssProviderFactory();
|
||
|
|
||
|
private:
|
||
|
long m_nRefCount;
|
||
|
};
|
||
|
|
||
|
CQGAVssProviderFactory::CQGAVssProviderFactory()
|
||
|
{
|
||
|
m_nRefCount = 0;
|
||
|
LockModule(TRUE);
|
||
|
}
|
||
|
|
||
|
CQGAVssProviderFactory::~CQGAVssProviderFactory()
|
||
|
{
|
||
|
LockModule(FALSE);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProviderFactory::QueryInterface(REFIID riid, void **ppv)
|
||
|
{
|
||
|
if (riid == IID_IUnknown || riid == IID_IClassFactory) {
|
||
|
*ppv = static_cast<void*>(this);
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
*ppv = NULL;
|
||
|
return E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CQGAVssProviderFactory::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&m_nRefCount);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CQGAVssProviderFactory::Release()
|
||
|
{
|
||
|
long nRefCount = InterlockedDecrement(&m_nRefCount);
|
||
|
if (m_nRefCount == 0) {
|
||
|
delete this;
|
||
|
}
|
||
|
return nRefCount;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CQGAVssProviderFactory::CreateInstance(
|
||
|
IUnknown *pUnknownOuter, REFIID iid, void **ppv)
|
||
|
{
|
||
|
CQGAVssProvider *pObj;
|
||
|
|
||
|
if (pUnknownOuter) {
|
||
|
return CLASS_E_NOAGGREGATION;
|
||
|
}
|
||
|
try {
|
||
|
pObj = new CQGAVssProvider;
|
||
|
} catch (...) {
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
HRESULT hr = pObj->QueryInterface(iid, ppv);
|
||
|
if (FAILED(hr)) {
|
||
|
delete pObj;
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* DLL functions
|
||
|
*/
|
||
|
|
||
|
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
|
||
|
{
|
||
|
CQGAVssProviderFactory *factory;
|
||
|
try {
|
||
|
factory = new CQGAVssProviderFactory;
|
||
|
} catch (...) {
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
factory->AddRef();
|
||
|
HRESULT hr = factory->QueryInterface(riid, ppv);
|
||
|
factory->Release();
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
STDAPI DllCanUnloadNow()
|
||
|
{
|
||
|
return g_nComObjsInUse == 0 ? S_OK : S_FALSE;
|
||
|
}
|
||
|
|
||
|
EXTERN_C
|
||
|
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpReserved)
|
||
|
{
|
||
|
if (dwReason == DLL_PROCESS_ATTACH) {
|
||
|
g_hinstDll = hinstDll;
|
||
|
DisableThreadLibraryCalls(hinstDll);
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|