This is a revised version of the example. Demonstrates that we can also use our own data types such CBSTR, CWSTR and CVAR. Of course, if we intend to use the DLL with other languages, then we can't do that beause the other language does not know how to deal with them and we will have to return standard data types such BSTR, WSTRING PTR and VARIANT.
My idea of converting the factory class into a virtual class doesn't seem possible. First, we only need a class factory, so a static one is enough; second, the CreateInstance method of the factory class needs to know the name of the class to create; third, Free Basic does not support to have more than one interface in a class.
DLL code:
' // Free Basic source code to a simple COM object, compiled into an ordinary
' // dynamic link library (DLL).
#include once "windows.bi"
#include once "win/ocidl.bi"
#include once "Afx/AfxCOM.inc"
#include once "Afx/CWSTR.inc"
#include once "Afx/CVar.inc"
using Afx
' Things to change:
' - The name of the interface
' - CLSID and IID of the inteface
' - Replace CLSID_IExample and IID_IExample with the names of your election
' - Replace CLSID_IExample in the DllGetClassObject function
' - Replace IID_IExample in the QueryInteface method of the class
' - Our virtual functions
' - The variables to store data
' Things to keep:
' - The virtual methods QueryInterface, AddRef and Release
' - The static variables OutstandingObjects and LockCount
' // Our IExample CLSID (class identifier)
' // {6899A2A3-405B-44d4-A415-E08CEE2A97CB}
' // (*** change it***)
DIM SHARED CLSID_IExample AS GUID = TYPE(&h6899A2A3, &h405B, &h44D4, {&hA4, &h15, &hE0, &h8C, &hEE, &h2A, &h97, &hCB})
' // Our IExample IID (interface identifier)
' // {74666CAC-C2B1-4FA8-A049-97F3214802F0}
' // (*** change it***)
DIM SHARED IID_IExample AS GUID = TYPE(&h74666CAC, &hC2B1, &h4FA8, {&hA0, &h49, &h97, &hF3, &h21, &h48, &h02, &hF0})
' // A count of how many objects our DLL has created (by some
' // app calling our IClassFactory object's CreateInstance())
' // which have not yet been Release()'d by the app
' // (***keep it***)
STATIC SHARED OutstandingObjects AS DWORD
' // A count of how many apps have locked our DLL via calling our
' // IClassFactory object's LockServer()
' // (***keep it***)
STATIC SHARED LockCount AS DWORD
' ========================================================================================
' IExample object
' ========================================================================================
TYPE IExample EXTENDS OBJECT
' Functions for the IUnknown Interface (*** keep them ***)
DECLARE VIRTUAL FUNCTION QueryInterface (BYVAL riid AS CONST IID CONST PTR, BYVAL ppvObj AS ANY PTR PTR) AS HRESULT
DECLARE VIRTUAL FUNCTION AddRef () AS ULONG
DECLARE VIRTUAL FUNCTION Release () AS ULONG
' Our functions (*** change them ***)
' / ------------------------------------------------
' // Adequate for use with other languages that won't understand our CBSTR class
' // The caller will be responsible of freeing the returned BSTR with SysFreeString
' // PowerBASIC will attach the handle and free it when the WSTRING will go out of scope
' // e.g. LOCAL ws AS WSTRING = pExample.GetString
' // If called with Free Basic, we must assign it to a CBSTR to avoid memory leaks, e.g.
' // DIM cbs AS CBSTR = pExample->GetString
DECLARE VIRTUAL SUB SetString (BYVAL bs AS AFX_BSTR)
DECLARE VIRTUAL FUNCTION GetString () AS AFX_BSTR
' / ------------------------------------------------
' // If we are going to use the server only with Free Basic, we can use CBSTR
DECLARE VIRTUAL PROPERTY MyCBStr (BYREF cbs AS CBSTR)
DECLARE VIRTUAL PROPERTY MyCBStr () AS CBSTR
' / ------------------------------------------------
DECLARE VIRTUAL PROPERTY MyCWStr (BYREF cws AS CWSTR)
DECLARE VIRTUAL PROPERTY MyCWStr () AS CWSTR
DECLARE VIRTUAL PROPERTY MyCVar (BYREF cv AS CVAR)
DECLARE VIRTUAL PROPERTY MyCVar () AS CVAR
DECLARE VIRTUAL PROPERTY MyNumber (BYVAL num AS DOUBLE)
DECLARE VIRTUAL PROPERTY MyNumber () AS DOUBLE
' Constructor/destructor
DECLARE CONSTRUCTOR
DECLARE DESTRUCTOR
' Reference count
cRef AS DWORD ' (*** keep it ***)
' Data (*** change it ***)
m_MyCBStr AS CBSTR
m_MyCWStr AS CWSTR
m_MyCVar AS CVAR
m_MyNumber AS DOUBLE
END TYPE
' ========================================================================================
' ========================================================================================
' IExample constructor
' ========================================================================================
CONSTRUCTOR IExample
END CONSTRUCTOR
' ========================================================================================
' ========================================================================================
' IExample destructor
' ========================================================================================
DESTRUCTOR IExample
END DESTRUCTOR
' ========================================================================================
' ========================================================================================
' IExample's QueryInterface
' (***change IID_IExample***)
' ========================================================================================
FUNCTION IExample.QueryInterface (BYVAL riid AS CONST IID CONST PTR, BYVAL ppvObj AS ANY PTR PTR) AS HRESULT
' // Check if the GUID matches the IID of our interface or the IUnknown interface.
IF IsEqualIID(riid, @IID_IUnknown) = FALSE AND IsEqualIID(riid, @IID_IExample) = FALSE THEN
' // We don't recognize the GUID passed to us. Let the caller know this,
' // by clearing his handle, and returning E_NOINTERFACE.
*ppvObj = 0
RETURN E_NOINTERFACE
END IF
' // Fill in the caller's handle
*ppvObj = @this
' // Increment the count of callers who have an outstanding pointer to this object
this.AddRef
RETURN NOERROR
END FUNCTION
' ========================================================================================
' ========================================================================================
' IExample's AddRef
' ========================================================================================
FUNCTION IExample.AddRef() AS ULONG
' // Increment IExample's reference count, and return the updated value.
this.cRef += 1
RETURN this.cRef
END FUNCTION
' ========================================================================================
' ========================================================================================
' IExample's Release
' ========================================================================================
FUNCTION IExample.Release () AS ULONG
' // Decrement IExample's reference count
this.cRef -= 1
' // If 0, then we can safely free this IExample now
IF this.cRef = 0 THEN
Delete @this
InterlockedDecrement(@OutstandingObjects)
RETURN 0
END IF
RETURN this.cRef
END FUNCTION
' ========================================================================================
' ========================================================================================
' Sets/gets a string
' ========================================================================================
SUB IExample.SetString (BYVAL bs AS AFX_BSTR)
this.m_MyCBStr = bs
END SUB
' ========================================================================================
' ========================================================================================
FUNCTION IExample.GetString () AS AFX_BSTR
RETURN this.m_MyCBStr
END FUNCTION
' ========================================================================================
' ========================================================================================
' Sets/gets a CBSTR
' ========================================================================================
PROPERTY IExample.MyCBStr (BYREF cbs AS CBSTR)
this.m_MyCBStr = cbs
END PROPERTY
' ========================================================================================
' ========================================================================================
PROPERTY IExample.MyCBStr () AS CBSTR
RETURN this.m_MyCBStr
END PROPERTY
' ========================================================================================
' ========================================================================================
' Sets/gets a CWSTR
' ========================================================================================
PROPERTY IExample.MyCWStr (BYREF cws AS CWSTR)
this.m_MyCWStr = cws
END PROPERTY
' ========================================================================================
' ========================================================================================
PROPERTY IExample.MyCWStr () AS CWSTR
RETURN this.m_MyCWStr
END PROPERTY
' ========================================================================================
' ========================================================================================
' Sets/gets a CVAR
' ========================================================================================
PROPERTY IExample.MyCVar (BYREF cv AS CVAR)
this.m_MyCVar = cv
END PROPERTY
' ========================================================================================
' ========================================================================================
PROPERTY IExample.MyCVar () AS CVAR
RETURN this.m_MyCVar
END PROPERTY
' ========================================================================================
' ========================================================================================
' Sets/gets a number
' ========================================================================================
PROPERTY IExample.MyNumber (BYVAL num AS DOUBLE)
this.m_MyNumber = num
END PROPERTY
' ========================================================================================
' ========================================================================================
PROPERTY IExample.MyNumber () AS DOUBLE
PROPERTY = this.m_MyNumber
END PROPERTY
' ========================================================================================
' ========================================================================================
' // The IClassFactory object ////////////////////////////////////////////////////////////
' ========================================================================================
' // Since we only ever need one IClassFactory object, we declare
' // it static. The only requirement is that we ensure any
' // access to its members is thread-safe
STATIC SHARED MyIClassFactoryObj As IClassFactory
' // IClassFactory's AddRef()
FUNCTION classAddRef (BYVAL pthis AS IClassFactory PTR) AS ULONG
' // Someone is obtaining my IClassFactory, so inc the count of
' // pointers that I've returned which some app needs to Release()
InterlockedIncrement(@OutstandingObjects)
' // Since we never actually allocate/free an IClassFactory (ie, we
' // use just 1 static one), we don't need to maintain a separate
' // reference count for our IClassFactory. We'll just tell the caller
' // that there's at least one of our IClassFactory objects in existance
RETURN 1
END FUNCTION
' // IClassFactory's QueryInterface()
FUNCTION classQueryInterface (BYVAL pthis AS IClassFactory PTR, BYVAL factoryGuid AS CONST IID CONST PTR, BYVAL ppv AS ANY PTR PTR) AS HRESULT
' // Make sure the caller wants either an IUnknown or an IClassFactory.
' // In either case, we return the same IClassFactory pointer passed to
' // us since it can also masquerade as an IUnknown
IF IsEqualIID(factoryGuid, @IID_IUnknown) OR IsEqualIID(factoryGuid, @IID_IClassFactory) THEN
' // Call my IClassFactory's AddRef
pthis->lpVtbl->AddRef(pthis)
' // Return (to the caller) a ptr to my IClassFactory
*ppv = pthis
RETURN NOERROR
END IF
' // We don't know about any other GUIDs
*ppv = 0
RETURN E_NOINTERFACE
END FUNCTION
' // IClassFactory's Release()
FUNCTION classRelease(BYVAL pthis AS IClassFactory PTR) AS ULONG
' // One less object that an app has not yet Release()'ed
RETURN InterlockedDecrement(@OutstandingObjects)
END FUNCTION
' // IClassFactory's CreateInstance() function. It is called by
' // someone who has a pointer to our IClassFactory object and now
' // wants to create and retrieve a pointer to our IExample
FUNCTION classCreateInstance(BYVAL pthis AS IClassFactory PTR, BYVAL punkOuter AS IUnknown PTR, _
BYVAL riid AS CONST IID CONST PTR, BYVAL objHandle AS ANY PTR PTR) AS HRESULT
DIM hr AS HRESULT
' // Assume an error by clearing caller's handle
*objHandle = 0
' // We don't support aggregation in this example
IF punkOuter THEN RETURN CLASS_E_NOAGGREGATION
' // Allocate our object (***change the name of the class***)
DIM thisObj AS IExample PTR = NEW IExample
' // Increment the reference count so we can call Release() below and
' // it will deallocate only if there is an error with QueryInterface()
thisobj->cRef = 1
' // Fill in the caller's handle with a pointer to the object we just allocated
' // above. We'll let the QueryInterface method of the object do that, because
' // it also checks the GUID the caller passed, and also increments the
' // reference count (to 2) if all goes well
hr = thisObj->QueryInterface(riid, objHandle)
' // Decrement reference count. NOTE: If there was an error in QueryInterface()
' // then Release() will be decrementing the count back to 0 and will free the
' // IExample for us. One error that may occur is that the caller is asking for
' // some sort of object that we don't support (ie, it's a GUID we don't recognize)
thisObj->Release
' // If success, inc static object count to keep this DLL loaded
IF hr = S_OK THEN InterlockedIncrement(@OutstandingObjects)
RETURN hr
END FUNCTION
' // IClassFactory's LockServer(). It is called by someone
' // who wants to lock this DLL in memory
FUNCTION classLockServer (BYVAL pthis AS IClassFactory PTR, BYVAL flock AS WINBOOL) AS HRESULT
IF flock THEN InterlockedIncrement(@LockCount) ELSE InterlockedDecrement(@LockCount)
RETURN NOERROR
END FUNCTION
STATIC SHARED MyClassFactoryVTbl AS IClassFactoryVTbl = TYPE(@classQueryInterface, _
@classAddRef, @classRelease, @classCreateInstance, @classLockServer)
' ========================================================================================
' Implementation of the DllGetClassObject and DllCanUnloadNow functions.
' ========================================================================================
EXTERN "windows-ms"
#UNDEF DllGetClassObject
FUNCTION DllGetClassObject ALIAS "DllGetClassObject" (BYVAL objGuid AS CLSID PTR, _
BYVAL factoryGuid AS IID PTR, BYVAL factoryHandle As VOID PTR PTR) AS HRESULT EXPORT
DIM hr AS HRESULT
' // Check that the caller is passing our interface CLSID.
' // That's the only object our DLL implements
' // (***change CLSID_IExample***)
IF IsEqualCLSID(objGuid, @CLSID_IExample) THEN
' // Fill in the caller's handle with a pointer to our IClassFactory object.
' // We'll let our IClassFactory's QueryInterface do that, because it also
' // checks the IClassFactory GUID and does other book-keeping
hr = classQueryInterface(@MyIClassFactoryObj, factoryGuid, factoryHandle)
ELSE
' // We don't understand this GUID. It's obviously not for our DLL.
' // Let the caller know this by clearing his handle and returning
' // CLASS_E_CLASSNOTAVAILABLE
*factoryHandle = 0
hr = CLASS_E_CLASSNOTAVAILABLE
END IF
RETURN hr
END FUNCTION
' * This is called by some OLE function in order to determine
' * whether it is safe to unload our DLL from memory.
' *
' * RETURNS: S_OK if safe to unload, or S_FALSE if not.
' // If someone has retrieved pointers to any of our objects, and
' // not yet Release()'ed them, then we return S_FALSE to indicate
' // not to unload this DLL. Also, if someone has us locked, return
' // S_FALSE
#UNDEF DllCanUnloadNow
FUNCTION DllCanUnloadNow ALIAS "DllCanUnloadNow" () AS HRESULT EXPORT
RETURN IIF(OutstandingObjects OR LockCount, S_FALSE, S_OK)
END FUNCTION
' ========================================================================================
END EXTERN
' ========================================================================================
' Constructor of the module
' ========================================================================================
SUB ctor () CONSTRUCTOR
' OutputDebugStringW "DLL loaded"
' // Clear static counts
OutstandingObjects = 0
LockCount = 0
' // Initialize my IClassFactory with the pointer to its VTable
MyIClassFactoryObj.lpVtbl = @MyClassFactoryVTbl
END SUB
' ========================================================================================
' ========================================================================================
' Destructor of the module
' ========================================================================================
SUB dtor () DESTRUCTOR
' OutputDebugStringW "DLL unloaded"
END SUB
' ========================================================================================