How to deal with COM objects?

Windows specific questions.
Post Reply
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

How to deal with COM objects?

Post by Tourist Trap »

Hello,

the topic here: viewtopic.php?f=6&t=18952, is really going very complicated, and it was almost useless for me for now. Even the first example there, supposedly the simplest, lacks the DECLARE_INTERFACE_ definition so...

I managed to grab another tutorial here then: https://faculty.cascadia.edu/mpanitz/CO ... nProc.html.
It's in C but it's really well written. The only problem is that some of the functions or stuff that are supposed to be interfaces and classfactories, and many details, are difficult for me to translate in FB without any help.

So far I've been following this part: https://faculty.cascadia.edu/mpanitz/CO ... ntInC.html. Not much difficult, and managed to get this below almost working:

Code: Select all

#include "windows.bi"
#Include once "win/ocidl.bi"
'#include once "server.bas" ??

dim as GUID         outClsid
dim as string       inString

inString = "{D20EA4E1-3957-11d2-A40B-0C5020524153}" 
'https://www.tenforums.com/tutorials/3123-clsid-key-guid-shortcuts-list-windows-10-a.html

? CLSIDFromString(wStr(inString), @outClsid)                                'must return 0


' ?
dim as any ptr ptr outInstance
dim as any ptr IID_pointerTowhat   'fake
? CoCreateInstance(@outClsid, NULL, CLSCTX_INPROC_SERVER, @IID_pointerTowhat, outInstance)   'must return 0
? outInstance                                                               'must NOT return 0


CoUninitialize()
That's supposed to be the client.
And the server part should be I think in a DLL made by the user that we should define the necessary stuff that leads to @IID_pointerTowhat in the CoCreateInstance function. That's where I'm stuck...

Has anyone any idea on how to write a minimalistic example of how we should write the "server" part?

My final goal would be to find how to use WASAPI for sound control on Windows.

Thanks.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: How to deal with COM objects?

Post by Josep Roca »

Template for a COM server:

Code: Select all

' // 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"

' 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 ***)
   DECLARE VIRTUAL FUNCTION SetString (BYVAL pwsz AS WSTRING PTR) AS HRESULT
   DECLARE VIRTUAL FUNCTION GetString (BYVAL pbuffer AS WSTRING PTR, BYVAL cch AS DWORD) AS HRESULT
   ' Constructor/destructor
   DECLARE CONSTRUCTOR
   DECLARE DESTRUCTOR
   ' Reference count
   cRef AS DWORD ' (*** keep it ***)
   ' Data (*** change it ***)
   buffer AS WSTRING * 80
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
' ========================================================================================

' ========================================================================================
' IExample's SetString
' This copies the passed string to IExample's buffer
' ========================================================================================
FUNCTION IExample.SetString (BYVAL pwsz AS WSTRING PTR) AS HRESULT
   ' // Make sure that caller passed a buffer
   IF pwsz = NULL THEN RETURN E_POINTER
   ' // Copy the passed str to IExample's buffer
   this.buffer = *pwsz
   RETURN NOERROR
END FUNCTION
' ========================================================================================

' ========================================================================================
' IExample's GetString
' This retrieves IExample's buffer and stores its contents in a buffer passed by the caller.
' ========================================================================================
FUNCTION IExample.GetString (BYVAL pbuffer AS WSTRING PTR, BYVAL cch AS DWORD) AS HRESULT
   ' // Make sure that caller passed a buffer
   IF pbuffer = NULL THEN RETURN E_POINTER
   IF cch THEN
      ' // Let's copy IExample's buffer to the passed buffer
      IF cch > 79 THEN cch = 79
      memcpy(pbuffer, @this.buffer, cch)
   END IF
   RETURN NOERROR
END FUNCTION
' ========================================================================================

' ========================================================================================
' // 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
' ========================================================================================
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: How to deal with COM objects?

Post by Josep Roca »

Test for the above example:

Code: Select all

'#CONSOLE ON
#define UNICODE
#INCLUDE ONCE "windows.bi"
#include once "win/ocidl.bi"
'#include once "Afx/AfxCOM.inc"
DECLARE FUNCTION AfxSafeRelease (BYREF pv AS ANY PTR) AS ULONG
DECLARE FUNCTION AfxNewCom OVERLOAD (BYREF wszLibName AS CONST WSTRING, BYREF rclsid AS CONST CLSID, BYREF riid AS CONST IID, BYREF wszLicKey AS WSTRING = "") AS ANY PTR

' // Our IExample object's GUID
' // {6899A2A3-405B-44d4-A415-E0 8C EE 2A 97 CB}
DIM CLSID_IExample AS GUID = TYPE(&h6899A2A3, &h405B, &h44D4, {&hA4, &h15, &hE0, &h8C, &hEE, &h2A, &h97, &hCB})
' // Our IExample VTable's GUID
' // {74666CAC-C2B1-4FA8-A049-97F3214802F0}
DIM IID_IExample AS GUID = TYPE(&h74666CAC, &hC2B1, &h4FA8, {&hA0, &h49, &h97, &hF3, &h21, &h48, &h02, &hF0})

TYPE IExample EXTENDS OBJECT
   ' Methods of the IUnknown Interface
   DECLARE ABSTRACT FUNCTION QueryInterface (BYVAL vTableGuid AS CONST IID CONST PTR, BYVAL ppv AS ANY PTR PTR) AS HRESULT
   DECLARE ABSTRACT FUNCTION AddRef () AS ULONG
   DECLARE ABSTRACT FUNCTION Release () AS ULONG
   ' Our methods
   DECLARE ABSTRACT FUNCTION SetString (BYVAL pwsz AS WSTRING PTR) AS HRESULT
   DECLARE ABSTRACT FUNCTION GetString (BYVAL pbuffer AS WSTRING PTR, BYVAL cch AS DWORD) AS HRESULT
END TYPE

' // Initialize the COM library
CoInitialize NULL

DIM wszLibName AS WSTRING * MAX_PATH = ExePath & "\IExample_template_01.dll"
DIM pExample AS IExample PTR = AfxNewCom(wszLibName, CLSID_IExample, IID_IExample)
IF pExample THEN
   DIM hr AS HRESULT
   hr = pExample->SetString("José Roca")
   DIM wsz AS WSTRING * 80
   hr = pExample->GetString(@wsz, SIZEOF(wsz))
   PRINT wsz
END IF
AfxSafeRelease(pExample)

' // Uninitialize the COM library
CoUninitialize

' // Functions extracted from my WinFBX framework //

' ========================================================================================
' Decrements the reference count for an interface on an object.
' The function returns the new reference count. This value is intended to be used only
' for test purposes.
' When the reference count on an object reaches zero, Release must cause the interface
' pointer to free itself. When the released pointer is the only existing reference to an
' object (whether the object supports single or multiple interfaces), the implementation
' must free the object.
' ========================================================================================
FUNCTION AfxSafeRelease (BYREF pv AS ANY PTR) AS ULONG
   IF pv = NULL THEN RETURN 0
   FUNCTION = cast(IUnknown PTR, pv)->lpvtbl->Release(pv)
   pv = NULL
END FUNCTION
' ========================================================================================

' ========================================================================================
' Loads the specified library from file and creates an instance of an object.
' Parameters:
' - wszLibName = Full path where the library is located.
' - rclsid = The CLSID (class identifier) associated with the data and code that will be
'   used to create the object.
' - riid = A reference to the identifier of the interface to be used to communicate with the object.
' - wszLicKey = The license key.
' If it succeeds, returns a reference to the requested interface; otherwise, it returns null.
' Not every component is a suitable candidate for use under this overloaded AfxNewCom function.
'  - Only in-process servers (DLLs) are supported.
'  - Components that are system components or part of the operating system, such as XML,
'    Data Access, Internet Explorer, or DirectX, aren't supported
'  - Components that are part of an application, such Microsoft Office, aren't supported.
'  - Components intended for use as an add-in or a snap-in, such as an Office add-in or
'    a control in a Web browser, aren't supported.
'  - Components that manage a shared physical or virtual system resource aren't supported.
'  - Visual ActiveX controls aren't supported because they need to be initilized and
'    activated by the OLE container.
' Note: Do not use DyLibFree to unload the library once you have got a valid reference
' to an interface or your application will GPF. Before calling DyLibFree, all the
' interface references must be released. If you don't need to unload the library until
' the application ends, then you don't need to call DyLibFree because CoUninitialize
' closes the COM library on the current thread, unloads all DLLs loaded by the thread,
' frees any other resources that the thread maintains, and forces all RPC connections on
' the thread to close.
' ========================================================================================
FUNCTION AfxNewCom OVERLOAD (BYREF wszLibName AS CONST WSTRING, BYREF rclsid AS CONST CLSID, BYREF riid AS CONST IID, BYREF wszLicKey AS WSTRING = "") AS ANY PTR

   DIM hr AS LONG, hLib AS HMODULE, pDisp AS ANY PTR
   DIM pIClassFactory AS IClassFactory PTR, pIClassFactory2 AS IClassFactory2 PTR

   ' // See if the library is already loaded in the address space
   hLib = GetModuleHandleW(wszLibName)
   ' // If it is not loaded, load it
   IF hLib = NULL THEN hLib = LoadLibraryW(wszLibName)
   ' // If it fails, abort
   IF hLib = NULL THEN EXIT FUNCTION

   ' // Retrieve the address of the exported function DllGetClassObject
   DIM pfnDllGetClassObject AS FUNCTION (BYVAL rclsid AS CONST IID CONST PTR, BYVAL riid AS CONST IID CONST PTR, BYVAL ppv AS LPVOID PTR) AS HRESULT
   pfnDllGetClassObject = CAST(ANY PTR, GetProcAddress(hLib, "DllGetClassObject"))
   IF pfnDllGetClassObject = NULL THEN EXIT FUNCTION

   IF LEN(wszLicKey) = 0 THEN
      ' // Request a reference to the IClassFactory interface
      hr = pfnDllGetClassObject(@rclsid, @IID_IClassFactory, @pIClassFactory)
      IF hr <> S_OK THEN EXIT FUNCTION
      ' // Create an instance of the server or control
      hr = pIClassFactory->lpVtbl->CreateInstance(pIClassFactory, NULL, @riid, @pDisp)
      IF hr <> S_OK THEN
         pIClassFactory->lpVtbl->Release(pIClassFactory)
         EXIT FUNCTION
      END IF
   ELSE
      ' // Request a reference to the IClassFactory2 interface
      hr = pfnDllGetClassObject(@rclsid, @IID_IClassFactory, @pIClassFactory2)
      IF hr <> S_OK THEN EXIT FUNCTION
      ' // Create a licensed instance of the server or control
      hr = pIClassFactory2->lpVtbl->CreateInstanceLic(pIClassFactory2, NULL, NULL, @riid, @wszLicKey, @pDisp)
      IF hr <> S_OK THEN
         pIClassFactory2->lpVtbl->Release(pIClassFactory2)
         EXIT FUNCTION
      END IF
   END IF

   IF pIClassFactory THEN pIClassFactory->lpVtbl->Release(pIClassFactory)
   IF pIClassFactory2 THEN pIClassFactory2->lpVtbl->Release(pIClassFactory2)
   RETURN pDisp

END FUNCTION
' ========================================================================================


PRINT
PRINT "Press any key..."
SLEEP
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: How to deal with COM objects?

Post by Josep Roca »

Please note that I'm using a low-level interface and a free registration technique. If you want to mess with registration, dual interfaces, type libraries, etc., it is up to you.
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: How to deal with COM objects?

Post by Tourist Trap »

Josep Roca wrote:Test for the above example
Thanks infinitely Josep Roca, this works fine! And obviously I couldn't have figured out such an amount of technical code by my own. WinFBX is really an underestimated ressource.

Then now I changed the CLSID in both the client executable and server dll, I put mine (of the 1st post) in replacement of yours just for trying, and it works. It shows José Roca as expected.

My next step will be to find the CLSID of WASAPI and try to figure out how to run its functions, now that the COM link is done! I'll try and post feedback then. Thanks again :)
Josep Roca wrote:Please note that I'm using a low-level interface and a free registration technique. If you want to mess with registration, dual interfaces, type libraries, etc., it is up to you.
No it's fine ! I thought that what you have done was only possible in ATL from what I grabbed from Stackoverflow, with SafeRelease and so on. That's why I'm surprised that it could be done this way. But it's really great.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: How to deal with COM objects?

Post by Josep Roca »

My next step will be to find the CLSID of WASAPI and try to figure out how to run its functions, now that the COM link is done! I'll try and post feedback then. Thanks again :)
WASAPI has not a CLSID.
WASAPI consists of several interfaces. The first of these is the IAudioClient interface. To access the WASAPI interfaces, a client first obtains a reference to the IAudioClient interface of an audio endpoint device by calling the IMMDevice::Activate method with parameter iid set to REFIID IID_IAudioClient. The client calls the IAudioClient::Initialize method to initialize a stream on an endpoint device. After initializing a stream, the client can obtain references to the other WASAPI interfaces by calling the IAudioClient::GetService method.
https://docs.microsoft.com/en-us/window ... dio/wasapi
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: How to deal with COM objects?

Post by Tourist Trap »

Josep Roca wrote: WASAPI has not a CLSID.
WASAPI consists of several interfaces. The first of these is the IAudioClient interface. To access the WASAPI interfaces, a client first obtains a reference to the IAudioClient interface of an audio endpoint device by calling the IMMDevice::Activate method with parameter iid set to REFIID IID_IAudioClient. The client calls the IAudioClient::Initialize method to initialize a stream on an endpoint device. After initializing a stream, the client can obtain references to the other WASAPI interfaces by calling the IAudioClient::GetService method.
https://docs.microsoft.com/en-us/window ... dio/wasapi
Damn, yes I realize it now. I found some example confirming it here (portaudio stuff) : https://app.assembla.com/spaces/portaud ... eviceapi.h.

They use this as the IMMDevice interface:

Code: Select all

    typedef struct IMMDeviceVtbl

    {

        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE *QueryInterface )(

            IMMDevice * This,

            /* [in] */ REFIID riid,

            /* [iid_is][out] */

            __RPC__deref_out  void **ppvObject);

        ULONG ( STDMETHODCALLTYPE *AddRef )(

            IMMDevice * This);

        ULONG ( STDMETHODCALLTYPE *Release )(

            IMMDevice * This);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Activate )(

            IMMDevice * This,

            /* [in] */

            __in  REFIID iid,

            /* [in] */

            __in  DWORD dwClsCtx,

            /* [unique][in] */

            __in_opt  PROPVARIANT *pActivationParams,

            /* [iid_is][out] */

            __out  void **ppInterface);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OpenPropertyStore )(

            IMMDevice * This,

            /* [in] */

            __in  DWORD stgmAccess,

            /* [out] */

            __out  IPropertyStore **ppProperties);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetId )(

            IMMDevice * This,

            /* [out] */

            __deref_out  LPWSTR *ppstrId);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetState )(

            IMMDevice * This,

            /* [out] */

            __out  DWORD *pdwState);

        END_INTERFACE

    } IMMDeviceVtbl;

    interface IMMDevice

    {

        CONST_VTBL struct IMMDeviceVtbl *lpVtbl;

    };
I don't know what to think now. Maybe I should try then to see if it wouldn't be faster to use a tiny subset PORTAUDIO rather...
Post Reply