654 lines
19 KiB
C++
654 lines
19 KiB
C++
//+-------------------------------------------------------------------
|
|
//
|
|
// File: idtable.cxx
|
|
//
|
|
// Contents: identity table
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 1-Dec-93 CraigWi Created
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
// CODEWORK: lookup from party apartment should not get individual apartments;
|
|
// but lookup from individual apartments should get party. Currently
|
|
// we only support individual apartments or a single party apartment, not both.
|
|
|
|
#include <ole2int.h>
|
|
|
|
#include <idtable.hxx>
|
|
|
|
CIDArray sg_idtable;
|
|
|
|
COleStaticMutexSem sg_mxsTable; // global mutext semaphore for id table
|
|
|
|
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: LookupIDFromUnk, private
|
|
//
|
|
// Synopsis: Looks up and may create the identity object for the given
|
|
// object. If the identity object is created, it is not
|
|
// aggregated to the given object.
|
|
//
|
|
// Identity lookup is based on pUnkControl.
|
|
//
|
|
// Effects: Due to multiple threads, it is possible for two identity
|
|
// objects to temporaily exist for the given object. Given that
|
|
// we don't aggregate the identity object in this routine, we are
|
|
// able to use any other identity object created in another
|
|
// thread. See CreateStdIdentity for more information.
|
|
//
|
|
// Arguments: [pUnk] -- the object; not necessarily the controlling unknown
|
|
// [fCreate] -- if the identity does not exist, create one.
|
|
// [ppStgID] -- when S_OK is returned, this is the identity
|
|
// object. This pointer doesn't necessarily hold
|
|
// the object alive. If the identity object was
|
|
// aggregated to the object, it will; if not, it will
|
|
// not. Only marshaling or lock connection will
|
|
// ensure the object stays alive.
|
|
//
|
|
// Returns: S_OK - identity now exists (whether created here or not)
|
|
//
|
|
// CO_E_OBJNOTREG - no identity and !fCreate
|
|
//
|
|
// BUGBUG - the identity GUID could not be created
|
|
//
|
|
// E_OUTOFMEMORY -
|
|
//
|
|
// E_UNEXPECTED - at least: no controlling unknown
|
|
//
|
|
// History: 11-Dec-93 CraigWi Created.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
INTERNAL LookupIDFromUnk(IUnknown *pUnk, BOOL fCreate, IStdIdentity **ppStdID)
|
|
{
|
|
// QI for IStdID; if ok, return that
|
|
if (pUnk->QueryInterface(IID_IStdIdentity, (void **)ppStdID) == NOERROR)
|
|
return NOERROR;
|
|
|
|
#if DBG == 1
|
|
|
|
if (*ppStdID != NULL)
|
|
{
|
|
CairoleDebugOut((DEB_ERROR,
|
|
"LookupIDFromUnk QI returned error but didn't null output\n"));
|
|
}
|
|
|
|
#endif // DBG == 1
|
|
|
|
// NOTE: other thread may create id on object before we check again
|
|
|
|
IUnknown *pUnkControl;
|
|
// QI for controlling unk; should succeed
|
|
if (pUnk->QueryInterface(IID_IUnknown, (void **)&pUnkControl) != NOERROR)
|
|
return E_UNEXPECTED;
|
|
|
|
IStdIdentity *pStdID = NULL;
|
|
sg_mxsTable.Request();
|
|
// scan for value in map; may find one attached to object created by now
|
|
int iID;
|
|
|
|
if (FreeThreading)
|
|
iID = sg_idtable.IndexOf((void *)&pUnkControl,
|
|
sizeof(pUnkControl), offsetof(IDENTRY, m_pUnkControl));
|
|
else
|
|
{
|
|
IDENTRY identry;
|
|
|
|
identry.m_tid = GetCurrentThreadId();
|
|
identry.m_pUnkControl = pUnkControl;
|
|
iID = sg_idtable.IndexOf((void *)&identry.m_tid,
|
|
sizeof(identry.m_tid) + sizeof(identry.m_pUnkControl),
|
|
offsetof(IDENTRY, m_tid));
|
|
}
|
|
|
|
// if found, addref pStdID which holds the identity alive
|
|
if (iID != -1)
|
|
{
|
|
pStdID = sg_idtable[iID].m_pStdID;
|
|
pStdID->AddRef();
|
|
|
|
#if DBG == 1
|
|
// verify correctness of entry
|
|
OID oidT;
|
|
|
|
Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
|
|
pStdID->GetObjectID(&oidT);
|
|
Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));
|
|
|
|
if (FreeThreading)
|
|
Assert(sg_idtable[iID].m_tid == NULL);
|
|
else
|
|
Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
|
|
#endif
|
|
}
|
|
sg_mxsTable.Release();
|
|
|
|
// We don't hold the mutex since CreateStdIdentity can call the server
|
|
// object, which we must assume can be expensive. CreateStdIdentity
|
|
// object calls SetObjectID() which gets the mutex again.
|
|
|
|
// CODEWORK: however, we must hold the mutex long enough so that two
|
|
// threads in the party apartment don't try to create the same id twice.
|
|
|
|
HRESULT hr;
|
|
|
|
// pStdID != NULL if found.
|
|
*ppStdID = pStdID;
|
|
if (pStdID != NULL)
|
|
hr = NOERROR;
|
|
|
|
// one didn't exist (when we checked a little bit ago) and we can't create;
|
|
// *ppStdID already == NULL.
|
|
else if (!fCreate)
|
|
hr = CO_E_OBJNOTREG;
|
|
|
|
// create identity; may return success with already created id; will
|
|
// never fail if id created by the time we register the newly created one
|
|
else
|
|
hr = CreateStdIdentity(NULL, pUnkControl, PSTDMARSHAL,
|
|
IID_IStdIdentity, (void **)ppStdID);
|
|
|
|
// we need to do this release via IWR if supported so it won't shutdown
|
|
// the server
|
|
if (hr != NOERROR)
|
|
{
|
|
// no identity to help in error case; for now, just let it shutdown
|
|
// this seems reasonsable since there may be no other time that it
|
|
// would shutdown.
|
|
pUnkControl->Release();
|
|
}
|
|
else
|
|
{
|
|
(*ppStdID)->ReleaseKeepAlive(pUnkControl, 0);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: LookupIDFromID, private
|
|
//
|
|
// Synopsis: Lookup an identity object based on an OID; does not create.
|
|
//
|
|
// Arguments: [oid] -- The identity
|
|
// [ppStdID] -- The cooresponding identity object if successful
|
|
//
|
|
// Returns: S_OK - have the identity object
|
|
//
|
|
// CO_E_OBJNOTREG - not present (when we looked)
|
|
//
|
|
// History: 11-Dec-93 CraigWi Created.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
INTERNAL LookupIDFromID(REFOID oid, BOOL fAddRef, IStdIdentity **ppStdID)
|
|
{
|
|
*ppStdID = NULL;
|
|
sg_mxsTable.Request();
|
|
// scan for value in map; may find one attached to object created by now
|
|
int iID;
|
|
if (FreeThreading)
|
|
iID = sg_idtable.IndexOf((void *)&oid, sizeof(oid), offsetof(IDENTRY, m_oid));
|
|
else
|
|
{
|
|
IDENTRY identry;
|
|
|
|
identry.m_oid = oid;
|
|
identry.m_tid = GetCurrentThreadId();
|
|
iID = sg_idtable.IndexOf((void *)&identry.m_oid,
|
|
sizeof(identry.m_oid) + sizeof(identry.m_tid),
|
|
offsetof(IDENTRY, m_oid));
|
|
}
|
|
|
|
|
|
// if found, addref pStdID which holds the identity alive
|
|
if (iID != -1)
|
|
{
|
|
*ppStdID = sg_idtable[iID].m_pStdID;
|
|
|
|
if (fAddRef)
|
|
// I sure hope the apps doesn't try anything fancy in AddRef
|
|
// that would cause a deadlock here! (That is, in the aggregated
|
|
// case we will run app code).
|
|
(*ppStdID)->AddRef();
|
|
|
|
#if DBG == 1
|
|
// verify correctness of entry
|
|
Assert(IsEqualGUID(oid, sg_idtable[iID].m_oid));
|
|
|
|
OID oidT;
|
|
(*ppStdID)->GetObjectID(&oidT);
|
|
Assert(IsEqualGUID(oid, oidT));
|
|
|
|
if (FreeThreading)
|
|
Assert(sg_idtable[iID].m_tid == NULL);
|
|
else
|
|
Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
|
|
|
|
#ifdef BUGBUG
|
|
can't do this yet since this occurs in dtors with unstable ref count;
|
|
e.g., CAdvBnd::~CAdvBnd does prot->Revoke which does CoUnmarshalInterface.
|
|
in 16bit OLE2 we required such dtors to artifically bump the ref count.
|
|
IUnknown *pUnkControl;
|
|
Verify(sg_idtable[iID].m_pUnkControl->QueryInterface(IID_IUnknown,
|
|
(void **)&pUnkControl) == NOERROR);
|
|
|
|
Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
|
|
pUnkControl->Release();
|
|
#endif // BUGBUG
|
|
#endif
|
|
}
|
|
sg_mxsTable.Release();
|
|
|
|
return *ppStdID == NULL ? CO_E_OBJNOTREG : NOERROR;
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: SetObjectID, private
|
|
//
|
|
// Synopsis: Called by the object id creation and unmarshal functions
|
|
// to establish the identity for an object (handler or server).
|
|
// Can fail if we discover an existing identity.
|
|
//
|
|
// Identity lookup is based on pUnkControl.
|
|
//
|
|
// Arguments: [oid] -- The id for the object
|
|
// [pUnkControl] -- The controlling uknown of the object being
|
|
// identitified.
|
|
// [pStdID] -- The identity object itself.
|
|
// [ppStdIDExisting] -- If another identity object go created
|
|
// since we (this thread) checked last, this is
|
|
// where that object is returned. May be NULL
|
|
// indicating that the caller doesn't care about
|
|
// an existing id.
|
|
//
|
|
// Returns: S_OK - identity was set successfully
|
|
//
|
|
// CO_E_OBJISREG - object was already registered (as determined
|
|
// by pUnkControl); *ppStdIDExisting set (if requested).
|
|
//
|
|
// E_OUTOFMEMORY -
|
|
//
|
|
// History: 11-Dec-93 CraigWi Created.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
INTERNAL SetObjectID(REFOID oid, IUnknown *pUnkControl, IStdIdentity *pStdID,
|
|
IStdIdentity **ppStdIDExisting)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
Assert(!IsEqualGUID(oid, OID_NULL));
|
|
|
|
sg_mxsTable.Request();
|
|
// scan for value in map; may find one attached to object created by now
|
|
int iID;
|
|
|
|
if (FreeThreading)
|
|
iID = sg_idtable.IndexOf((void *)&pUnkControl,
|
|
sizeof(pUnkControl), offsetof(IDENTRY, m_pUnkControl));
|
|
else
|
|
{
|
|
IDENTRY identry;
|
|
|
|
identry.m_tid = GetCurrentThreadId();
|
|
identry.m_pUnkControl = pUnkControl;
|
|
iID = sg_idtable.IndexOf((void *)&identry.m_tid,
|
|
sizeof(identry.m_tid) + sizeof(identry.m_pUnkControl),
|
|
offsetof(IDENTRY, m_tid));
|
|
}
|
|
|
|
// if found, another thread created identity for same object;
|
|
// addref pStdID and return it if requested
|
|
if (iID != -1)
|
|
{
|
|
if (ppStdIDExisting)
|
|
{
|
|
*ppStdIDExisting = sg_idtable[iID].m_pStdID;
|
|
(*ppStdIDExisting)->AddRef();
|
|
}
|
|
|
|
hr = CO_E_OBJISREG;
|
|
|
|
#if DBG == 1
|
|
// verify correctness of entry
|
|
OID oidT;
|
|
|
|
Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
|
|
sg_idtable[iID].m_pStdID->GetObjectID(&oidT);
|
|
Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));
|
|
|
|
if (FreeThreading)
|
|
Assert(sg_idtable[iID].m_tid == NULL);
|
|
else
|
|
Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (ppStdIDExisting)
|
|
*ppStdIDExisting = NULL;
|
|
|
|
// no id yet; add at end; no addrefs
|
|
IDENTRY identry;
|
|
identry.m_oid = oid;
|
|
identry.m_tid = FreeThreading ? NULL : GetCurrentThreadId();
|
|
identry.m_pUnkControl = pUnkControl;
|
|
identry.m_pStdID = pStdID;
|
|
|
|
if (sg_idtable.Add(identry) == -1)
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
sg_mxsTable.Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: ClearObjectUnk, private
|
|
//
|
|
// Synopsis: Called during the disconnect of the id only. Clears
|
|
// the pUnkControl entry for the identity.
|
|
//
|
|
// Identity lookup is based on oid.
|
|
//
|
|
// Arguments: [oid] -- The identity; used for asserts only.
|
|
// [pUnkControl] -- The object for which the identity is being
|
|
// revoked; used for asserts only.
|
|
// [pStdID] -- The identity object; used for asserts only.
|
|
//
|
|
// Returns: S_OK - removed successfully
|
|
//
|
|
// CO_E_OBJNOTREG - not present (often ignored).
|
|
//
|
|
// History: 02-May-94 CraigWi Created.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
INTERNAL ClearObjectUnk(REFOID oid, IUnknown *pUnkControl, IStdIdentity *pStdID)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
sg_mxsTable.Request();
|
|
// scan for value in map; may find one attached to object created by now
|
|
int iID;
|
|
|
|
if (FreeThreading)
|
|
iID = sg_idtable.IndexOf((void *)&oid, sizeof(oid), offsetof(IDENTRY, m_oid));
|
|
else
|
|
{
|
|
IDENTRY identry;
|
|
|
|
identry.m_oid = oid;
|
|
identry.m_tid = GetCurrentThreadId();
|
|
iID = sg_idtable.IndexOf((void *)&identry.m_oid,
|
|
sizeof(identry.m_oid) + sizeof(identry.m_tid),
|
|
offsetof(IDENTRY, m_oid));
|
|
}
|
|
|
|
// if found, clear the pUnkControl field
|
|
if (iID != -1)
|
|
{
|
|
#if DBG == 1
|
|
// verify correctness of entry
|
|
OID oidT;
|
|
|
|
Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
|
|
pStdID->GetObjectID(&oidT);
|
|
Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));
|
|
Assert(pStdID == sg_idtable[iID].m_pStdID);
|
|
if (FreeThreading)
|
|
Assert(sg_idtable[iID].m_tid == NULL);
|
|
else
|
|
Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
|
|
#endif
|
|
|
|
sg_idtable[iID].m_pUnkControl = NULL;
|
|
}
|
|
else
|
|
hr = CO_E_OBJNOTREG;
|
|
sg_mxsTable.Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: ClearObjectID, private
|
|
//
|
|
// Synopsis: Called during the revokation of the id only. Clears
|
|
// the identity entry in the table.
|
|
//
|
|
// Identity lookup is based on oid.
|
|
//
|
|
// Arguments: [oid] -- The identity; used for asserts only.
|
|
// [pUnkControl] -- The object for which the identity is being
|
|
// revoked; used for asserts only.
|
|
// [pStdID] -- The identity object; used for asserts only.
|
|
//
|
|
// Returns: S_OK - removed successfully
|
|
//
|
|
// CO_E_OBJNOTREG - not present (often ignored).
|
|
//
|
|
// History: 11-Dec-93 CraigWi Created.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
INTERNAL ClearObjectID(REFOID oid, IUnknown *pUnkControl, IStdIdentity *pStdID)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
sg_mxsTable.Request();
|
|
// scan for value in map; may find one attached to object created by now
|
|
int iID;
|
|
|
|
if (FreeThreading)
|
|
iID = sg_idtable.IndexOf((void *)&oid, sizeof(oid), offsetof(IDENTRY, m_oid));
|
|
else
|
|
{
|
|
IDENTRY identry;
|
|
|
|
identry.m_oid = oid;
|
|
identry.m_tid = GetCurrentThreadId();
|
|
iID = sg_idtable.IndexOf((void *)&identry.m_oid,
|
|
sizeof(identry.m_oid) + sizeof(identry.m_tid),
|
|
offsetof(IDENTRY, m_oid));
|
|
}
|
|
|
|
// if found, remove it.
|
|
if (iID != -1)
|
|
{
|
|
#if DBG == 1
|
|
// verify correctness of entry
|
|
OID oidT;
|
|
|
|
# ifndef _CAIRO_
|
|
// BUGBUG [mikese] There is a race during shutdown of a
|
|
// multithreaded server which renders this assertion invalid.
|
|
// CraigWi is fixing the race (see CODEWORK in stdid.cxx l 1570)
|
|
// but in the mean time we disable the assert for Cairo builds.
|
|
Assert(pUnkControl == sg_idtable[iID].m_pUnkControl);
|
|
# endif
|
|
pStdID->GetObjectID(&oidT);
|
|
Assert(IsEqualGUID(oidT, sg_idtable[iID].m_oid));
|
|
Assert(pStdID == sg_idtable[iID].m_pStdID);
|
|
if (FreeThreading)
|
|
Assert(sg_idtable[iID].m_tid == NULL);
|
|
else
|
|
Assert(sg_idtable[iID].m_tid == GetCurrentThreadId());
|
|
#endif
|
|
|
|
Assert(sg_idtable.GetSize() != 0);
|
|
int iLast = sg_idtable.GetSize() - 1;
|
|
if (iID != iLast)
|
|
{
|
|
// element removed is not last; copy last element to current
|
|
sg_idtable[iID] = sg_idtable[iLast];
|
|
}
|
|
|
|
// now setsize to one less to remove the now unused last element
|
|
|
|
sg_idtable.SetSize(iLast);
|
|
}
|
|
else
|
|
hr = CO_E_OBJNOTREG;
|
|
sg_mxsTable.Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: IDTableUninitialize, private
|
|
//
|
|
// Synopsis: Clears the id table memory for the current thread (or all
|
|
// if party model). This involves scanning the table and for
|
|
// entries on the current thread, calling
|
|
// IMarshal::DisconnectObject.
|
|
//
|
|
// The purpose of this routine is to simulate inter-thread rundown
|
|
// as well as clean up memory.
|
|
//
|
|
// History: 23-Dec-93 CraigWi Created.
|
|
// 26-Apr-94 CraigWi Now called per-thread and disconnects
|
|
//
|
|
// Note: This function should only be called when the IDTable
|
|
// really needs to be uninitialized. For the party model, this
|
|
// means that it should only be called when the last thread
|
|
// is exiting.
|
|
//
|
|
// This function must NOT assume that it is being called within
|
|
// a critical section.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
INTERNAL_(void) IDTableThreadUninitialize(void)
|
|
{
|
|
// We should never get here if we are free threading. Any and all
|
|
// clean up must happen when the last CoUninitialize happens for the
|
|
// process in free threading.
|
|
Win4Assert(!FreeThreading
|
|
&& "IDTableThreadUninitialize called and Free Threading");
|
|
|
|
DWORD tid = GetCurrentThreadId(); // CODEWORK get apartment for thread
|
|
|
|
// CODEWORK: we will hold this for each entry; this needs to be changed
|
|
// later since we could skip an entry if another thread
|
|
// deleted an entry before our point in the list.
|
|
sg_mxsTable.Request();
|
|
|
|
for (int i = 0; i < sg_idtable.GetSize(); i++)
|
|
{
|
|
if (FreeThreading || sg_idtable[i].m_tid == tid)
|
|
{
|
|
CairoleDebugOut((DEB_ERROR,
|
|
"Object at %lx still has connections\n",
|
|
sg_idtable[i].m_pUnkControl));
|
|
|
|
|
|
// NOTE: for party apartment model, we don't have to check
|
|
// for valid entry since we only scan it once, at process exit.
|
|
|
|
Assert(IsValidInterface(sg_idtable[i].m_pStdID));
|
|
IStdIdentity *pStdID = sg_idtable[i].m_pStdID;
|
|
pStdID->AddRef();
|
|
|
|
// release semaphore for this period since the disconnect
|
|
// could take a long time and the revoke id needs access to
|
|
// the table (even though it won't find anything).
|
|
sg_mxsTable.Release();
|
|
|
|
IMarshal *pMarshal;
|
|
if (pStdID->QueryInterface(IID_IMarshal, (void **)&pMarshal) == NOERROR)
|
|
{
|
|
pMarshal->DisconnectObject(0);
|
|
pMarshal->Release();
|
|
}
|
|
|
|
pStdID->RevokeObjectID();
|
|
pStdID->Release();
|
|
|
|
// re-request the table since we need to guard the GetSize above
|
|
sg_mxsTable.Request();
|
|
}
|
|
}
|
|
|
|
sg_mxsTable.Release();
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: IDTableProcessUninitialize
|
|
//
|
|
// Synopsis: Process specific IDTable uninitialization
|
|
//
|
|
// Effects: Frees up table memory
|
|
//
|
|
// Requires: All thread specific uninitialization already complete. This
|
|
// function assumes that the caller is holding the
|
|
// g_mxsSingleThreadOle mutex (so that no other thread is trying
|
|
// to use the table while we clean it up).
|
|
//
|
|
// Modifies: sg_idtable
|
|
//
|
|
// History: 29-Jun-94 AlexT Created
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
INTERNAL_(void) IDTableProcessUninitialize(void)
|
|
{
|
|
sg_idtable.RemoveAll();
|
|
}
|
|
|
|
#if DBG == 1
|
|
|
|
//+-------------------------------------------------------------------
|
|
//
|
|
// Function: Dbg_FindRemoteHdlr
|
|
//
|
|
// Synopsis: finds a remote object handler for the specified object,
|
|
// and returns an instance of IMarshal on it. This is debug
|
|
// code for assert that reference counts are as expected.
|
|
//
|
|
// Exceptions: None
|
|
//
|
|
// History: 23-Nov-93 Rickhi Created
|
|
// 23-Dec-93 CraigWi Changed to identity object
|
|
//
|
|
//--------------------------------------------------------------------
|
|
|
|
extern "C"
|
|
IMarshal * _stdcall Dbg_FindRemoteHdlr(IUnknown *punkObj)
|
|
{
|
|
// validate input parms
|
|
Win4Assert(punkObj);
|
|
|
|
IMarshal *pIM = NULL;
|
|
IStdIdentity *pStdID;
|
|
if (LookupIDFromUnk(punkObj, FALSE, &pStdID) == NOERROR)
|
|
{
|
|
(void)pStdID->QueryInterface(IID_IMarshal, (void **)&pIM);
|
|
pStdID->Release();
|
|
}
|
|
return pIM;
|
|
}
|
|
|
|
#endif // DBG == 1
|