Search code examples
cwinapidirectshow

Unresolved external symbol IID and CLSID errors in Visual Studio


I keep getting a group of errors when I run my code as follows:

LNK 2001 unresolved external symbol IID_IMediaEvent
LNK 2001 unresolved external symbol IID_IMediaControl
LNK 2001 unresolved external symbol IID_IGraphBuilder
LNK 2001 unresolved external symbol CLSID_FilterGraph

My code is here:

#include <dshow.h>
#include <stdio.h>
#include <Windows.h>

void main(void) 
{
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        printf("FAIL");
    }
    else
    {
        printf("PASS");
    }
    IGraphBuilder *pGraph=NULL;
    hr=CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,&IID_IGraphBuilder, (void **)&pGraph);
    if (FAILED(hr))
    {
        printf("ERROR - Could not create the Filter Graph Manager.");
        return;
    }
    IMediaControl *pControl=NULL;
    IMediaEvent *pEvent=NULL;
    hr = pGraph->lpVtbl->QueryInterface(pGraph,&IID_IMediaControl, (void**)&pControl);
    hr = pGraph->lpVtbl->QueryInterface(pGraph,&IID_IMediaEvent, (void**)&pEvent);
    hr = pGraph->lpVtbl->RenderFile(pGraph,L"C:/Users/User/Desktop/AudioPlayer/trn.wav", NULL);
    hr = pControl->lpVtbl->Run(pControl);
    long evCode = 0;
    pEvent->lpVtbl->WaitForCompletion(pEvent,INFINITE, &evCode);
    pControl->lpVtbl->Release(pControl);
    pEvent->lpVtbl->Release(pEvent);
    pGraph->lpVtbl->Release(pGraph);
    CoUninitialize();
}

I have been searching for around two weeks trying to find an answer, but to no help. I think its something to do with linkers, but I have no idea on what I'm doing wrong because I'm fairly new to C and Visual Studio.


Solution

  • The core issue here is that the interface (IID) and class (CLSID) IDs are declared, but not defined. The compiler happily proceeds, delegating the ID lookup to the linker. Since the IDs aren't defined nor exported by any of the supplied import libraries, the linker gives up with an LNK2001 error.

    To resolve the issue there are two options: Either have the header files also define the IDs, or pass the import library (strmiids.lib) that exports them to the linker.

    The first option has the advantage of working with any compiler. It requires an #include <initguid.h> directive ahead of including any header file that would otherwise merely declare the IDs. <initguid.h> does two things: It defines the INITGUID preprocessor symbol, and subsequently includes <guiddef.h>. It is the latter that changes the preprocessor symbols to define the respective IDs, properly marked as __declspec(selectany) to prevent multiply-defined symbol linker errors.

    #include <initguid.h>
    #include <dshow.h>
    #include <stdio.h>
    #include <Windows.h>
    
    ...
    

    This should resolve all linker errors.

    The other option is to pass the import library strmiids.lib to the linker. This can be done in source code using a #pragma comment(lib, "strmiids") directive, or by passing the import library on the linker command line.

    The downside of this is that it needs to be toolchain-specific: Only MSVC and Clang support #pragma comment(lib, ...), and Clang and GCC use different naming for import libraries (libfoo vs. foo.lib). Unless you have a specific reason to use the import library you should go with the first option.


    Aside: Since #include <initguid.h> works for any scenario, why do we have options? And why do we need the <initguid.h> toggle at all?

    This is probably down to historic events, from a time before the __declspec(selectany) modifier was introduced (Visual Studio 2012). Prior to that, every symbol could be defined at most once. If more than one compilation unit defined the same symbol, the linker would generate a LNK2005, and ultimately fail to produce an artifact.

    In those times, client code would generally have a single (usually otherwise empty) compilation unit that had an #include <initguid.h> directive, followed by other headers that declared (and defined) the IDs. The remaining compilation units merely included the header files to declare the IDs, without the <initguid.h> toggle.

    After the __declspec(selectany) modifier was introduced, symbols (such as the IDs) could be defined any number of times, so long as the definitions are identical (which the SDK ensures). So today, you can simply #include <initguid.h> in every compilation unit, and things work just fine.

    At least that's my interpretation of history derived from a look into the <guiddef.h> file of the Windows SDK.