COM IID in C, unresolved external symbol IID_IAudioClient

I'm trying to compile a function that uses COM (for WASAPI) as a .c file and got some problems (it works as a .cpp file). After looking into the system headers (audioclient.h, mmdeviceapi.h) I found that by defining COBJMACROS before including the files I can get access to a C api. Changing the function calls made the codes compiles, but several IIDs are defined as extern and I don't know what I need to link to or include to get those symbols. Does anyone knows where to find them or if didn't understood how it's supposed to work ?
1
2
3
For example in audioclient.h
EXTERN_C const IID IID_IAudioClient;
// main.obj : error LNK2001: unresolved external symbol IID_IAudioClient

The cpp version of the function uses __uuidof to get those ids, but MSVC says that this keyword is only supported in C++.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// To get the c interface even in .cpp
// #define CINTERFACE
#define COBJMACROS
#include <audioclient.h>
#include <Mmdeviceapi.h>

b32 sound_create( PlatformSpecific* platform, sound_Format* format, u32* bufferSizeInSamples ) {
    
    b32 result = false;
    IMMDeviceEnumerator* enumerator;
    
    /* TODO simon: make sure that Ole32.dll is present by default on windows. */
    HMODULE oleDLL = LoadLibrary( "Ole32.dll" );
    typedef HRESULT ( *CoCreateInstance_type )( REFCLSID  rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv );
    CoCreateInstance_type CoCreateInstance = ( CoCreateInstance_type ) GetProcAddress( oleDLL, "CoCreateInstance" );
    
#define C_INTERFACE
    
#ifndef C_INTERFACE
    HRESULT error = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &enumerator );
#else
    HRESULT error = CoCreateInstance( &CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, ( void** ) &enumerator );
#endif
    
    FreeLibrary( oleDLL );
    
    if ( error == S_OK ) {
        
        IMMDevice* device;
#ifndef C_INTERFACE
        error = enumerator->GetDefaultAudioEndpoint( eRender, eConsole, &device );
#else
        error = IMMDeviceEnumerator_GetDefaultAudioEndpoint( enumerator, eRender, eConsole, &device );
#endif
        
        if ( error == S_OK ) {
            
#ifndef C_INTERFACE
            error = device->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &platform->audioClient );
#else
            error = IMMDevice_Activate( device, &IID_IAudioClient, CLSCTX_ALL, NULL, ( void** ) &platform->audioClient );
#endif
            
            if ( error == S_OK ) {
                
                WAVEFORMATEXTENSIBLE bufferFormat;
                
                bufferFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
                bufferFormat.Format.nChannels = format->channelCount;
                bufferFormat.Format.nSamplesPerSec = format->samplesPerSecond;
                bufferFormat.Format.wBitsPerSample = format->containerSize * 8;
                bufferFormat.Format.nBlockAlign = bufferFormat.Format.nChannels * format->containerSize;
                bufferFormat.Format.nAvgBytesPerSec = bufferFormat.Format.nSamplesPerSec * bufferFormat.Format.nBlockAlign;
                bufferFormat.Format.cbSize = sizeof( bufferFormat );
                bufferFormat.Samples.wValidBitsPerSample = format->bitsPerSample;
                bufferFormat.dwChannelMask = format->channelMask;
                WAVE_EXTENSIBLE_GUID( WAVE_FORMAT_PCM, ( u8* ) &bufferFormat.SubFormat );
                
                /* NOTE simon: 1 reference_time = 100 nanoseconds = 1 sec / 10 000 000. */
                REFERENCE_TIME bufferDuration = 10000000;
                
#ifndef C_INTERFACE
                error = platform->audioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, 0, bufferDuration, 0, &bufferFormat.Format, 0 );
#else
                error = IAudioClient_Initialize( platform->audioClient, AUDCLNT_SHAREMODE_SHARED, 0, bufferDuration, 0, &bufferFormat.Format, 0 );
#endif
                
                if ( error == S_OK ) {
                    
#ifndef C_INTERFACE
                    error = platform->audioClient->GetBufferSize( bufferSizeInSamples );
#else
                    error = IAudioClient_GetBufferSize( platform->audioClient, bufferSizeInSamples );
#endif
                    _assert( *bufferSizeInSamples == format->samplesPerSecond );
                    
#ifndef C_INTERFACE
                    error = platform->audioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &platform->audioRenderClient );
#else
                    error = IAudioClient_GetService( platform->audioClient, &IID_IAudioRenderClient, ( void** ) &platform->audioRenderClient );
#endif
                    
                    if ( error == S_OK ) {
                        
                        result = true;
                        
                    } else {
#ifndef C_INTERFACE
                        platform->audioClient->Release( );
#else
                        IAudioClient_Release( platform->audioClient );
#endif
                        platform->audioClient = 0;
                    }
                } else {
                    
#ifndef C_INTERFACE
                    platform->audioClient->Release( );
#else
                    IAudioClient_Release( platform->audioClient );
#endif
                    platform->audioClient = 0;
                }   
            }
            
#ifndef C_INTERFACE
            device->Release( );
#else
            IMMDevice_Release( device );
#endif  
        }
        
#ifndef C_INTERFACE
        enumerator->Release( );
#else
        IMMDeviceEnumerator_Release( enumerator );
#endif  
    }
    
    return result;
}

Edited by Simon Anciaux on Reason: Initial post
Yea, you need to define them yourself. Sometimes Microsoft gives static library with IID/CLSID values (like dxguid.lib), but sometimes not.

1
2
3
#include <initguid.h> // include this for DEFINE_GUID to create definition, not declaration

DEFINE_GUID(my_IID_IAudioClient, 0x1CB9AD4C, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2);

To get IID value, look up IAudioClient class declaration in header file. it will have it's IID value one line above it.

Edited by Mārtiņš Možeiko on
Thanks.
I'm having another issue. This code is in a header that can be included in a .c or .cpp file and to have only one version of the code I defined CINTERFACE so that mmdeviceapi.h always generates the C functions even in CPP. The problem is that in CPP, REFIID is defined as a reference instead of a pointer and so the interface is different depending on the language.
1
2
3
4
5
6
7
8
9
// From guiddef.h
#ifndef _REFIID_DEFINED
#define _REFIID_DEFINED
#ifdef __cplusplus
#define REFIID const IID &
#else
#define REFIID const IID * __MIDL_CONST
#endif
#endif

I have two workarounds but I'm not sure if they are good ideas:
1. Define REFIID to be an IID pointer myself before including the files that use it. I'm not sure what consequences this have with other things that use COM.
1
2
#define _REFIID_DEFINED
#define REFIID const IID * __MIDL_CONST

2. Use a macro to have different values if the language is CPP. This seems a better approach, but I'm not sure how it's working because a function prototype that uses a pointer in C is now a function using a reference in CPP. The generated assembly is the same in C and CPP, which makes sense since a reference is a pointer. I would have expected an error because of the different types. Is it working because the 2 function signatures generate the same code and that this code corresponds to the function signature of the dll loaded by COM ?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#ifdef __cplusplus
#define IID_IAudioClient_ref IID_IAudioClient
#define IID_IAudioRenderClient_ref IID_IAudioRenderClient
#else
#define IID_IAudioClient_ref &IID_IAudioClient
#define IID_IAudioRenderClient_ref &IID_IAudioRenderClient
#endif

/* In c the prototype is:
HRESULT ( STDMETHODCALLTYPE *Activate )( IMMDevice * This, IID* iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface );

In cpp the prototype is:
HRESULT ( STDMETHODCALLTYPE *Activate )( IMMDevice * This, IID& iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface );*/

Is there a better way to have REFIID defined as a pointer even in CPP ?

Edited by Simon Anciaux on Reason: typos
If this is intended to be put in header file (single-file-lib) which user includes in their code, then (1) approach seems bad, because it will override some defines which now may not work with rest of users code.

(2) approach should work fine. Yeah, they put in COM APIs only types that can be represented in C. So you won't see std::string or big structure passed by value.

Another approach is to define your own IAudioClient as C structure. Then use C style code that will work with both - C and C++ code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct mrmixer_IAudioClient_vtable
{
    HRESULT (*QueryInferface)(..);
    ULONG (*AddRef)(...);
    ....
};

struct mrmixer_IAudioClient
{
    struct mrmixer_IAudioClient_vtable* vtbl;
};
Thanks again. I ended up copying everything I needed from audioclient.h and mmdeviceapi.h so I don't need those headers anymore. Since it's only for my personal use it should be OK.