PDA

Click to See Complete Forum and Search --> : Implementing IUnkown and accessing Interfaces


Magiaus
Aug 31st, 2002, 04:41 PM
ok, i ave 3 c++ books open and a shell programming in vb book and i can't find a good explination of how to impament an interface. can some one please give me a run down on it or a crash course. i am using vc++ 7 but am willing to open 6 if it will get me going easier

thanks in advance

CornedBee
Sep 2nd, 2002, 10:44 AM
You have two options, one easier but less flexible than the other. I will explain only the easier one now. See this URL (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_MFCNOTES_TN038.asp) for an explanation of the other method.


// A implementation is usually a C++ class that provides
// implementation for all methods of all base interfaces.
// In this case we want to implement the IStream interface, which
// provides methods for file-like reading and writing.

// This is our class
class CByteArrayStream : public IStream, IClassFactory
{
// This is the reference count: it counts how many pointers to
// this object exist.
UINT m_uiRefC;
public:
// IUnknown methods:
STDMETHOD_(UINT, AddRef)();
STDMETHOD_(UINT, Release)();
STDMETHOD(QueryInterface)(REFIID iid, void** ppv);

// IClassFactory methods:
STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID iid, void **ppvObject);
STDMETHOD(LockServer)(BOOL fLock);

// IStream methods:
STDMETHOD(Read)(void *pv, ULONG cb, ULONG *pcbRead);
STDMETHOD(Write)(void const* pv, ULONG cb, ULONG *pcbWritten);
STDMETHOD(Seek)(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *pLibNewPosition);
STDMETHOD(SetSize)(ULARGE_INTEGER libNewSize);
// and and and...
};

// Implement the functions. This is a simple object which doesn't
// support aggregation, so we can keep the IUnknown functions
// very simple.
STDMETHODIMP_(UINT) CByteArrayStream::AddRef()
{
// The purpose of AddRef is to increment the reference count
// so that the object only destroys itself when it is no longer needed.
return ++m_uiRefC;
}

STDMETHODIMP_(UINT) CByteArrayStream::Release()
{
// Release inverts the behavior of AddRef and destroys the object
// if it is no longer needed.
if(--m_uiRefC == 0)
{
UINT ret = m_uiRefC;
delete this;
return ret;
}
else
return m_uiRefC;
}

STDMETHODIMP CByteArrayStream::QueryInterface(REFIID iid, void **ppv)
{
// See if interface supported and return pointer.
if(iid == IID_IUnknown)
{
// A new copy of the pointer
AddRef();
// The casting is necessary!!!
*ppv = (void*)((IUnknown*)this);
return S_OK;
}
else if(iid == IID_IClassFactory)
{
AddRef();
*ppv = (void*)((IClassFactory*)this);
return S_OK;
}
else if(iid == IID_IStream)
{
AddRef();
*ppv = (void*)((IStream*)this);
return S_OK;
}
else
{
// interface not supported
*ppv = NULL;
return E_NOINTERFACE;
}
}

// The IClassFactory is necessary because CoCreateInstance first calls CoGetClassObject which obtains a IClassFactory pointer on
// this object and then uses the CreateInstance method to get
// more objects

// I must admit that I'm very unsure whether this
// implementation makes sense.
STDMETHODIMP CByteArrayStream::CreateInstance(IUnknown *pUnkOuter, REFIID iid, void **ppv)
{
// Don't support aggregation
if(pUnkOuter != NULL)
{
*ppv = NULL;
return CLASS_E_NOAGGREGATION;
}
IUnknown *pNew = new CByteArrayStream;
hr=pNew->QueryInterface(iid, ppv);
pNew->Release(); // We don't need this pointer anymore
return hr;
}

// I have no idea what IClassFactory::LockServer is for
STDMETHODIMP CByteArrayStream::LockServer(BOOL bLock)
{
return E_NOTIMPLEMENTED;
}

// Implement the functionality of IStream - this is the
// actual thing your object should do!

Magiaus
Sep 2nd, 2002, 11:08 AM
ok. I understand that fairly well. i think... going to play with it and see what i can get working.

CornedBee
Sep 3rd, 2002, 03:42 AM
If I had my reference with me I could help you more, but I don't have it, sorry. (I'd need the MFC source)

Magiaus
Sep 3rd, 2002, 03:45 AM
yeah i have everything but a good mfc and atl book. i thought i would never need mfc shows what i know.

CornedBee
Sep 9th, 2002, 04:31 AM
Big mistake on my part!!!
Your COM object doesn't need to implement IClassFactory.
Instead, there is a "class object" associated with your COM object which only implements IClassFactory, whose CreateInstance methode returns a pointer to your COM object and which is somehow obtained by calling CoGetClassObject with your class id. But I don't know how, must research into it (stupid MSDN's explanations are so bad...).

CornedBee
Sep 9th, 2002, 04:50 AM
I think I've found it.

Any DLL that contains COM objects must implement and export the function DllGetClassObject, which allocates a class object (an object that implements IClassFactory) and returns a pointer to it.

Any EXE that contains COM objects must call CoRegisterClassObject on startup, passing the CLSID of the COM object, an IUnknown pointer to the class object, context, flags and a pointer to the return value which can be used to unregister the class object.

CoGetClassObject will determine which context the COM object uses and act appropriatly: a DLL context triggers these actions:
a) Load the DLL (if not loaded)
b) Blend into app mem space
c) Call DllGetClassObject and return

An EXE context triggers these actions:
a) Start the exe
b) Wait until it has registered it's class object
c) Obtain the class object and return

CornedBee
Sep 9th, 2002, 05:15 AM
So here comes a full COM object DLL source.

class CByteArrayStream : public IStream
{
UINT m_uiRefC;
public:
// IUnknown methods:
STDMETHOD_(UINT, AddRef)();
STDMETHOD_(UINT, Release)();
STDMETHOD(QueryInterface)(REFIID iid, void** ppv);

// IStream methods:
STDMETHOD(Read)(void *pv, ULONG cb, ULONG *pcbRead);
STDMETHOD(Write)(void const* pv, ULONG cb, ULONG *pcbWritten);
STDMETHOD(Seek)(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *pLibNewPosition);
STDMETHOD(SetSize)(ULARGE_INTEGER libNewSize);
// and and and...
};

STDMETHODIMP_(UINT) CByteArrayStream::AddRef()
{
return ++m_uiRefC;
}

STDMETHODIMP_(UINT) CByteArrayStream::Release()
{
if(--m_uiRefC == 0)
{
delete this;
return 0;
}
else
return m_uiRefC;
}

STDMETHODIMP CByteArrayStream::QueryInterface(REFIID iid, void **ppv)
{
if(ppv == NULL)
return E_INVALIDARG;
if(iid == IID_IUnknown)
{
AddRef();
*ppv = (void*)((IUnknown*)this);
return S_OK;
}
else if(iid == IID_IStream)
{
AddRef();
*ppv = (void*)((IStream*)this);
return S_OK;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
}

// Implement the functionality of IStream - this is the
// actual thing your object should do

// Here is the class object
class CMyClassObject : public IClassFactory
{
UINT m_uiRefC;
UINT m_bLockCount;

public:
CMyClassObject() : m_bLockCount(0), m_uiRefC(0) {};
~CMyClassObject() { pCO = NULL; };

// IUnknown methods:
STDMETHOD_(UINT, AddRef)();
STDMETHOD_(UINT, Release)();
STDMETHOD(QueryInterface)(REFIID iid, void** ppv);

// IClassObject methods
STDMETHOD(CreateInstance)(IUnknown *pOut, REFIID iid, void **ppvObject);
STDMETHOD(LockServer)(BOOL bLock);

// This is the only object of this type
static CMyClassObject *pCO;
};

CMyClassObject * CMyClassObject::pCO = NULL;

STDMETHODIMP_(UINT) CMyClassObject::AddRef()
{
return ++m_uiRefC;
}

STDMETHODIMP_(UINT) CMyClassObject::Release()
{
if(--m_uiRefC == 0)
{
delete this;
return 0;
}
else
return m_uiRefC;
}

STDMETHODIMP CMyClassObject::QueryInterface(REFIID iid, void **ppv)
{
if(ppv == NULL)
return E_INVALIDARG;
if(iid == IID_IUnknown)
{
AddRef();
*ppv = (void*)((IUnknown*)this);
return S_OK;
}
else if(iid == IID_IClassFactory)
{
AddRef();
*ppv = (void*)((IClassFactory*)this);
return S_OK;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
}

STDMETHODIMP CMyClassObject::CreateInstance(IUnknown *pOut, REFIID iid, void **ppvObject)
{
if(ppvObject == NULL)
return E_INVALIDARG;
if(pOut != NULL)
return CLASS_E_NOAGGREGATION;
CByteArrayStream *p = new CByteArrayStream;
if(p == NULL)
return E_OUTOFMEMORY;
HRESULT hr = p->QueryInterface(iid, ppvObject);
p->Release();
return hr;
}

STDMETHODIMP CMyClassObject::LockServer(BOOL bLock)
{
if(bLock)
{
AddRef();
m_bLockCount++;
}
else if(!bLock && m_bLockCount > 0)
{
Release();
m_bLockCount--;
}
else
return E_UNEXPECTED; // too many calls
return S_OK;
}

STDAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **ppv)
{
if(ppv == NULL)
return E_INVALIDARG;
*ppv = NULL;
if(clsid != Id_Of_CByteArrayStream)
return CLASS_E_CLASSNOTAVAILABLE;
pCO = new CMyClassObject();
if(!pCO)
return E_OUTOFMEMORY;
return pCO->QueryInterface(iid, ppv);
}

STDAPI DllCanUnloadNow()
{
return CMyClassObject::pCO != NULL;
}

Magiaus
Sep 9th, 2002, 06:44 AM
I think I get it. Windows uses the class the inherits IClassFactory and gives a pointer to my object to manage the instacing of my object and all that jazz. vb6 made it so easy. i get the idea but i am going to have to do some heavy study before i feel confident about building a big object.

thanks CornedBee

CornedBee
Sep 9th, 2002, 06:56 AM
Yep.

I'm now going to build a COM object for me. Exactly what I posted here: it implements IStream.
It's purpose is to offer an IStream interface for a CByteArray - I need this to load an image from a DAO DB binary blob using CImage...

Currently I load the data, copy to a HGLOBAL block and call CreateIStreamOnHGlobal, but that's a waste.