mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
567 lines
17 KiB
567 lines
17 KiB
//+------------------------------------------------------------------------
|
|
//
|
|
// File: riftbl.cxx
|
|
//
|
|
// Contents: RIF (Registered Interfaces) Table.
|
|
//
|
|
// Classes: CRIFTable
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
#include <ole2int.h>
|
|
#include <riftbl.hxx> // class definition
|
|
#include <locks.hxx> // LOCK/UNLOCK
|
|
#include <channelb.hxx> // ThreadInvoke
|
|
|
|
|
|
// number of Registered Interface Entries per allocator page
|
|
#define RIFS_PER_PAGE 32
|
|
|
|
// global RIF table
|
|
CRIFTable gRIFTbl;
|
|
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Vector Table: All calls on registered interfaces are dispatched through
|
|
// this table to ThreadInvoke, which subsequently dispatches to the
|
|
// appropriate interface stub. All calls on COM interfaces are dispatched
|
|
// on method #0 so the table only needs to be 1 entry long.
|
|
//
|
|
//+------------------------------------------------------------------------
|
|
|
|
const RPC_DISPATCH_FUNCTION vector[] =
|
|
{
|
|
(void (_stdcall *) (struct ::_RPC_MESSAGE *)) ThreadInvoke,
|
|
};
|
|
|
|
const RPC_DISPATCH_TABLE gDispatchTable =
|
|
{
|
|
sizeof(vector)/sizeof(RPC_DISPATCH_FUNCTION),
|
|
(RPC_DISPATCH_FUNCTION *)&vector, 0
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Interface Templates. When we register an interface with the RPC runtime,
|
|
// we allocate an structure, copy one of these templates in (depending on
|
|
// whether we want client side or server side) and then set the interface
|
|
// IID to the interface being registered.
|
|
//
|
|
// We hand-register the RemUnknown interface because we normally marshal its
|
|
// derived verion (IRundown), yet expect calls on IRemUnknown.
|
|
//
|
|
//+------------------------------------------------------------------------
|
|
|
|
const RPC_SERVER_INTERFACE gServerIf =
|
|
{
|
|
sizeof(RPC_SERVER_INTERFACE),
|
|
{0x69C09EA0, 0x4A09, 0x101B, 0xAE, 0x4B, 0x08, 0x00, 0x2B, 0x34, 0x9A, 0x02,
|
|
{0, 0}},
|
|
{0x8A885D04, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60,
|
|
{2, 0}},
|
|
(RPC_DISPATCH_TABLE *)&gDispatchTable, 0, 0, 0
|
|
};
|
|
|
|
const RPC_CLIENT_INTERFACE gClientIf =
|
|
{
|
|
sizeof(RPC_CLIENT_INTERFACE),
|
|
{0x69C09EA0, 0x4A09, 0x101B, 0xAE, 0x4B, 0x08, 0x00, 0x2B, 0x34, 0x9A, 0x02,
|
|
{0, 0}},
|
|
{0x8A885D04, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60,
|
|
{2, 0}},
|
|
0, 0, 0, 0
|
|
};
|
|
|
|
const RPC_SERVER_INTERFACE gRemUnknownIf =
|
|
{
|
|
sizeof(RPC_SERVER_INTERFACE),
|
|
{0x00000131, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
|
|
{0, 0}},
|
|
{0x8A885D04, 0x1CEB, 0x11C9, 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60,
|
|
{2, 0}},
|
|
(RPC_DISPATCH_TABLE *)&gDispatchTable, 0, 0, 0
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Registered Interface hash table buckets. This is defined as a global
|
|
// so that we dont have to run any code to initialize the hash table.
|
|
//
|
|
//+------------------------------------------------------------------------
|
|
SHashChain RIFBuckets[23] =
|
|
{
|
|
{&RIFBuckets[0], &RIFBuckets[0]},
|
|
{&RIFBuckets[1], &RIFBuckets[1]},
|
|
{&RIFBuckets[2], &RIFBuckets[2]},
|
|
{&RIFBuckets[3], &RIFBuckets[3]},
|
|
{&RIFBuckets[4], &RIFBuckets[4]},
|
|
{&RIFBuckets[5], &RIFBuckets[5]},
|
|
{&RIFBuckets[6], &RIFBuckets[6]},
|
|
{&RIFBuckets[7], &RIFBuckets[7]},
|
|
{&RIFBuckets[8], &RIFBuckets[8]},
|
|
{&RIFBuckets[9], &RIFBuckets[9]},
|
|
{&RIFBuckets[10], &RIFBuckets[10]},
|
|
{&RIFBuckets[11], &RIFBuckets[11]},
|
|
{&RIFBuckets[12], &RIFBuckets[12]},
|
|
{&RIFBuckets[13], &RIFBuckets[13]},
|
|
{&RIFBuckets[14], &RIFBuckets[14]},
|
|
{&RIFBuckets[15], &RIFBuckets[15]},
|
|
{&RIFBuckets[16], &RIFBuckets[16]},
|
|
{&RIFBuckets[17], &RIFBuckets[17]},
|
|
{&RIFBuckets[18], &RIFBuckets[18]},
|
|
{&RIFBuckets[19], &RIFBuckets[19]},
|
|
{&RIFBuckets[20], &RIFBuckets[20]},
|
|
{&RIFBuckets[21], &RIFBuckets[21]},
|
|
{&RIFBuckets[22], &RIFBuckets[22]}
|
|
};
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: CleanupRIFEntry
|
|
//
|
|
// Synopsis: Call the RIFTable to cleanup an entry. This is called
|
|
// by the hash table cleanup code.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
void CleanupRIFEntry(SHashChain *pNode)
|
|
{
|
|
gRIFTbl.UnRegisterInterface((RIFEntry *)pNode);
|
|
}
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::Initialize, public
|
|
//
|
|
// Synopsis: Initialize the Registered Interface Table
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
void CRIFTable::Initialize()
|
|
{
|
|
ComDebOut((DEB_CHANNEL, "CRIFTable::Initialize\n"));
|
|
ASSERT_LOCK_HELD
|
|
_HashTbl.Initialize(RIFBuckets);
|
|
_palloc.Initialize(sizeof(RIFEntry), RIFS_PER_PAGE);
|
|
}
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::Cleanup, public
|
|
//
|
|
// Synopsis: Cleanup the Registered Interface Table.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
void CRIFTable::Cleanup()
|
|
{
|
|
ComDebOut((DEB_CHANNEL, "CRIFTable::Cleanup\n"));
|
|
ASSERT_LOCK_HELD
|
|
_HashTbl.Cleanup(CleanupRIFEntry);
|
|
_palloc.Cleanup();
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::GetClientInterfaceInfo, public
|
|
//
|
|
// Synopsis: returns the interface info for a given interface
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
RPC_CLIENT_INTERFACE *CRIFTable::GetClientInterfaceInfo(REFIID riid)
|
|
{
|
|
DWORD iHash = _HashTbl.Hash(riid);
|
|
RIFEntry *pRIFEntry = (RIFEntry *) _HashTbl.Lookup(iHash, riid);
|
|
Win4Assert(pRIFEntry); // must already be registered
|
|
Win4Assert(pRIFEntry->pCliInterface);
|
|
return pRIFEntry->pCliInterface;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::RegisterInterface, public
|
|
//
|
|
// Synopsis: returns the proxy stub clsid of the specified interface,
|
|
// and adds an entry to the registered interface hash table
|
|
// if needed.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
HRESULT CRIFTable::RegisterInterface(REFIID riid, BOOL fServer, CLSID *pClsid)
|
|
{
|
|
ComDebOut((DEB_CHANNEL, "CRIFTable::RegisterInterface riid:%I\n", &riid));
|
|
ASSERT_LOCK_RELEASED
|
|
LOCK
|
|
|
|
// look for the interface in the table.
|
|
RIFEntry *pRIFEntry;
|
|
HRESULT hr = GetPSClsid(riid, pClsid, &pRIFEntry);
|
|
|
|
if (pRIFEntry)
|
|
{
|
|
if (fServer)
|
|
{
|
|
if (pRIFEntry->pSrvInterface == NULL)
|
|
{
|
|
hr = RegisterServerInterface(pRIFEntry, riid);
|
|
}
|
|
}
|
|
else if (pRIFEntry->pCliInterface == NULL)
|
|
{
|
|
hr = RegisterClientInterface(pRIFEntry, riid);
|
|
}
|
|
}
|
|
|
|
UNLOCK
|
|
ASSERT_LOCK_RELEASED
|
|
ComDebOut((DEB_CHANNEL,
|
|
"CRIFTable::RegisterInterface hr:%x clsid:%I\n", hr, pClsid));
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::RegisterClientInterface, private
|
|
//
|
|
// Synopsis: Register with the RPC runtime a client RPC interface
|
|
// structure for the given IID. The IID must not already
|
|
// be registered.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
HRESULT CRIFTable::RegisterClientInterface(RIFEntry *pRIFEntry, REFIID riid)
|
|
{
|
|
ComDebOut((DEB_CHANNEL,
|
|
"CRIFTable::RegisterClientInterface pRIFEntry:%x\n", pRIFEntry));
|
|
Win4Assert(pRIFEntry->pCliInterface == NULL);
|
|
ASSERT_LOCK_HELD
|
|
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
pRIFEntry->pCliInterface = (RPC_CLIENT_INTERFACE *)
|
|
PrivMemAlloc(sizeof(RPC_CLIENT_INTERFACE));
|
|
|
|
if (pRIFEntry->pCliInterface != NULL)
|
|
{
|
|
memcpy(pRIFEntry->pCliInterface, &gClientIf, sizeof(gClientIf));
|
|
pRIFEntry->pCliInterface->InterfaceId.SyntaxGUID = riid;
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::RegisterServerInterface, private
|
|
//
|
|
// Synopsis: Register with the RPC runtime a server RPC interface
|
|
// structure for the given IID. The IID must not already
|
|
// be registered
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
HRESULT CRIFTable::RegisterServerInterface(RIFEntry *pRIFEntry, REFIID riid)
|
|
{
|
|
ComDebOut((DEB_CHANNEL,
|
|
"CRIFTable::RegisterServerInterface pRIFEntry:%x\n", pRIFEntry));
|
|
Win4Assert(pRIFEntry->pSrvInterface == NULL);
|
|
ASSERT_LOCK_HELD
|
|
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
pRIFEntry->pSrvInterface = (RPC_SERVER_INTERFACE *)
|
|
PrivMemAlloc(sizeof(RPC_SERVER_INTERFACE));
|
|
|
|
if (pRIFEntry->pSrvInterface != NULL)
|
|
{
|
|
hr = S_OK;
|
|
memcpy(pRIFEntry->pSrvInterface, &gServerIf, sizeof(gServerIf));
|
|
pRIFEntry->pSrvInterface->InterfaceId.SyntaxGUID = riid;
|
|
|
|
RPC_STATUS sc = RpcServerRegisterIfEx(pRIFEntry->pSrvInterface, NULL,
|
|
NULL,
|
|
RPC_IF_AUTOLISTEN | RPC_IF_OLE,
|
|
0xffff, GetAclFn());
|
|
if (sc != RPC_S_OK)
|
|
{
|
|
ComDebOut((DEB_ERROR,
|
|
"RegisterServerInterface %I failed:0x%x.\n", &riid, sc));
|
|
|
|
PrivMemFree(pRIFEntry->pSrvInterface);
|
|
pRIFEntry->pSrvInterface = NULL;
|
|
hr = HRESULT_FROM_WIN32(sc);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::UnRegisterInterface
|
|
//
|
|
// Synopsis: UnRegister with the RPC runtime a server RPC interface
|
|
// structure for the given IID. This is called by
|
|
// CUUIDHashTable::Cleanup during CoUninitialize. Also
|
|
// delete the interface structures.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
void CRIFTable::UnRegisterInterface(RIFEntry *pRIFEntry)
|
|
{
|
|
if (pRIFEntry->pSrvInterface)
|
|
{
|
|
// server side entry exists, unregister the interface with RPC.
|
|
// Note that this can result in calls being dispatched so we
|
|
// have to release the lock around the call.
|
|
|
|
UNLOCK
|
|
ASSERT_LOCK_RELEASED
|
|
|
|
RpcServerUnregisterIf(pRIFEntry->pSrvInterface, 0, 1);
|
|
PrivMemFree(pRIFEntry->pSrvInterface);
|
|
|
|
ASSERT_LOCK_RELEASED
|
|
LOCK
|
|
|
|
pRIFEntry->pSrvInterface = NULL;
|
|
}
|
|
|
|
PrivMemFree(pRIFEntry->pCliInterface);
|
|
|
|
_palloc.ReleaseEntry((PageEntry *)pRIFEntry);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::GetPSClsid, public
|
|
//
|
|
// Synopsis: Finds the RIFEntry in the table for the given riid, and
|
|
// adds an entry if one is not found. Called by CoGetPSClsid
|
|
// and by CRIFTable::RegisterInterface.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
HRESULT CRIFTable::GetPSClsid(REFIID riid, CLSID *pclsid, RIFEntry **ppEntry)
|
|
{
|
|
ComDebOut((DEB_CHANNEL,
|
|
"CRIFTable::GetPSClsid riid:%I pclsid:%x\n", &riid, pclsid));
|
|
ASSERT_LOCK_HELD
|
|
HRESULT hr = S_OK;
|
|
|
|
// look for the interface in the table.
|
|
DWORD iHash = _HashTbl.Hash(riid);
|
|
RIFEntry *pRIFEntry = (RIFEntry *) _HashTbl.Lookup(iHash, riid);
|
|
|
|
if (pRIFEntry == NULL)
|
|
{
|
|
// no entry exists for this interface, add one. Dont hold
|
|
// the lock over a call to the SCM.
|
|
|
|
UNLOCK
|
|
ASSERT_LOCK_RELEASED
|
|
hr = wCoGetPSClsid(riid, pclsid);
|
|
ASSERT_LOCK_RELEASED
|
|
LOCK
|
|
|
|
// now that we are holding the lock again, do another lookup incase
|
|
// some other thread came it while the lock was released.
|
|
|
|
pRIFEntry = (RIFEntry *) _HashTbl.Lookup(iHash, riid);
|
|
|
|
if (pRIFEntry == NULL && SUCCEEDED(hr))
|
|
{
|
|
hr = AddEntry(*pclsid, riid, iHash, &pRIFEntry);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// found an entry, return the clsid
|
|
*pclsid = pRIFEntry->psclsid;
|
|
}
|
|
|
|
*ppEntry = pRIFEntry;
|
|
|
|
ASSERT_LOCK_HELD
|
|
ComDebOut((DEB_CHANNEL, "CRIFTable::RegisterPSClsid pRIFEntry:%x\n", pRIFEntry));
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::RegisterPSClsid, public
|
|
//
|
|
// Synopsis: Adds an entry to the table. Used by CoRegisterPSClsid
|
|
// so that applications can add a temporary entry that only
|
|
// affects the local process without having to muck with
|
|
// the system registry.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
HRESULT CRIFTable::RegisterPSClsid(REFIID riid, REFCLSID rclsid)
|
|
{
|
|
ComDebOut((DEB_CHANNEL,
|
|
"CRIFTable::RegisterPSClsid rclsid:%I riid:%I\n", &rclsid, &riid));
|
|
|
|
HRESULT hr = S_OK;
|
|
ASSERT_LOCK_RELEASED
|
|
LOCK
|
|
|
|
// look for the interface in the table.
|
|
DWORD iHash = _HashTbl.Hash(riid);
|
|
RIFEntry *pRIFEntry = (RIFEntry *) _HashTbl.Lookup(iHash, riid);
|
|
|
|
if (pRIFEntry == NULL)
|
|
{
|
|
// no entry exists for this interface, add one.
|
|
hr = AddEntry(rclsid, riid, iHash, &pRIFEntry);
|
|
}
|
|
else
|
|
{
|
|
// found an entry, update the clsid
|
|
pRIFEntry->psclsid = rclsid;
|
|
}
|
|
|
|
UNLOCK
|
|
ASSERT_LOCK_RELEASED
|
|
ComDebOut((DEB_CHANNEL, "CRIFTable::RegisterPSClsid hr:%x\n", hr));
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Member: CRIFTable::AddEntry, private
|
|
//
|
|
// Synopsis: allocates and entry, fills in the values, and adds it
|
|
// to the hash table.
|
|
//
|
|
// History: 12-Feb-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
HRESULT CRIFTable::AddEntry(REFCLSID rclsid, REFIID riid,
|
|
DWORD iHash, RIFEntry **ppRIFEntry)
|
|
{
|
|
ASSERT_LOCK_HELD
|
|
RIFEntry *pRIFEntry = (RIFEntry *) _palloc.AllocEntry();
|
|
|
|
if (pRIFEntry)
|
|
{
|
|
pRIFEntry->psclsid = rclsid;
|
|
pRIFEntry->pSrvInterface = NULL;
|
|
pRIFEntry->pCliInterface = NULL;
|
|
*ppRIFEntry = pRIFEntry;
|
|
|
|
// add to the hash table
|
|
_HashTbl.Add(iHash, riid, &pRIFEntry->HashNode);
|
|
|
|
ComDebOut((DEB_CHANNEL,
|
|
"Added RIFEntry riid:%I pRIFEntry\n", &riid, pRIFEntry));
|
|
return S_OK;
|
|
}
|
|
|
|
ASSERT_LOCK_HELD
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: CoRegisterPSClsid, public
|
|
//
|
|
// Synopsis: registers a IID->PSCLSID mapping that applies only within
|
|
// the current process. Can be used by code downloaded over
|
|
// a network to do custom interface marshaling without having
|
|
// to muck with the system registry.
|
|
//
|
|
// Algorithm: validate the parameters then add an entry to the RIFTable.
|
|
//
|
|
// History: 15-Apr-96 Rickhi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
STDAPI CoRegisterPSClsid(REFIID riid, REFCLSID rclsid)
|
|
{
|
|
ComDebOut((DEB_MARSHAL,
|
|
"CoRegisterPSClsid riid:%I rclsid:%I\n", &riid, &rclsid));
|
|
|
|
HRESULT hr = InitChannelIfNecessary();
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = E_INVALIDARG;
|
|
|
|
if ((&riid != NULL) && (&rclsid != NULL) &&
|
|
IsValidPtrIn(&riid, sizeof(riid)) &&
|
|
IsValidPtrIn(&rclsid, sizeof(rclsid)))
|
|
{
|
|
ASSERT_LOCK_RELEASED
|
|
|
|
hr = gRIFTbl.RegisterPSClsid(riid, rclsid);
|
|
|
|
ASSERT_LOCK_RELEASED
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: CoGetPSClsid, public
|
|
//
|
|
// Synopsis: returns the proxystub clsid associated with the specified
|
|
// interface IID.
|
|
//
|
|
// Arguments: [riid] - the interface iid to lookup
|
|
// [lpclsid] - where to return the clsid
|
|
//
|
|
// Returns: S_OK if successfull
|
|
// REGDB_E_IIDNOTREG if interface is not registered.
|
|
// REGDB_E_READREGDB if any other error
|
|
//
|
|
// Algorithm: First it looks in the local RIFTable for a matching IID. If
|
|
// no entry is found, the RIFTable looks in the shared memory
|
|
// table (NT only), and if not found and the table is FULL, it
|
|
// will look in the registry itself.
|
|
//
|
|
// History: 07-Apr-94 Rickhi rewrite
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
STDAPI CoGetPSClsid(REFIID riid, CLSID *pclsid)
|
|
{
|
|
ComDebOut((DEB_MARSHAL, "CoGetPSClsid riid:%I pclsid:%x\n", &riid, pclsid));
|
|
|
|
HRESULT hr = InitChannelIfNecessary();
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = E_INVALIDARG;
|
|
|
|
if ((&riid != NULL) &&
|
|
IsValidPtrIn(&riid, sizeof(riid)) &&
|
|
IsValidPtrOut(pclsid, sizeof(*pclsid)))
|
|
{
|
|
ASSERT_LOCK_RELEASED
|
|
LOCK
|
|
|
|
RIFEntry *pRIFEntry;
|
|
hr = gRIFTbl.GetPSClsid(riid, pclsid, &pRIFEntry);
|
|
|
|
UNLOCK
|
|
ASSERT_LOCK_RELEASED
|
|
}
|
|
|
|
return hr;
|
|
}
|