Search code examples
ccomdialog

Accessing Common Item Dialog from Plain C


This is my first attempt ever at any sort of COM. I'm trying to launch an Open File Dialog in Folder mode. The code compiles but the application fails to start and I get an "Ordinal could not be located in the dynamic link library" error. Can anyone pinpoint what might be causing this? Any help would be greatly appreciated. NOTE: The code is based off my interpretation of the MSDN samples and documentation. Right now I understand it won't do anything, I'm just trying to get it to display the dialog.

Thank you!

HRESULT OpenDirectory()
{
    //Initialize the COM library
    COINIT Init = COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE;
    CoInitialize(NULL);

    //CoCreate Folder Open Dialog Object
    IFileDialog *pfd = NULL;
    CLSID OpenFileID = CLSID_FileOpenDialog;
    IID IFileDialog = IID_IFileDialog;
    HRESULT hr = CoCreateInstance(&OpenFileID, NULL, CLSCTX_INPROC_SERVER, &IFileDialog, (void**) ( &pfd));
    


    if (SUCCEEDED(hr))
    {
        IFileDialogEvents *pfde = NULL;
        hr = OpenDirEventHandler(&pfde);

        if (SUCCEEDED(hr))
        {
            //Hook the Event Handler
            DWORD dwHook;
            hr = pfd->lpVtbl->Advise(pfd, pfde, &dwHook);


            if (SUCCEEDED(hr))
            {
                //flags
                DWORD dwFlags;
                hr = pfd->lpVtbl->GetOptions(pfd, &dwFlags);

                if (SUCCEEDED(hr))
                {
                    hr = pfd->lpVtbl->SetOptions(pfd, dwFlags | FOS_FORCEFILESYSTEM | FOS_PICKFOLDERS);

                    if (SUCCEEDED(hr))
                    {
                        hr = pfd->lpVtbl->Show(pfd, NULL);

                        if (SUCCEEDED(hr))
                        {
                            IShellItem *psiResult;
                            
                            hr = pfd->lpVtbl->GetResult(pfd, &psiResult);

                            // We are just going to print out the 
                                          // name of the file for sample sake.
                            PWSTR pszFilePath = NULL;
                            hr = psiResult->lpVtbl->GetDisplayName(pfd, SIGDN_FILESYSPATH,
                                &pszFilePath);
                            
                            if (SUCCEEDED(hr))
                            {
                                TaskDialog(NULL,
                                    NULL,
                                    L"CommonFileDialogApp",
                                    pszFilePath,
                                    NULL,
                                    TDCBF_OK_BUTTON,
                                    TD_INFORMATION_ICON,
                                    NULL);
                                CoTaskMemFree(pszFilePath);
                            }
                            psiResult->lpVtbl->Release(psiResult);
                        }
                    }
                }

            }
            pfd->lpVtbl->Unadvise(pfd, dwHook);
        }
        pfde->lpVtbl->Release(pfde);
    }
    pfd->lpVtbl->Release(pfd);


    return 0;
}


HRESULT STDMETHODCALLTYPE FileOK(IFileDialog *pfd)
{
    return 0;
}

HRESULT STDMETHODCALLTYPE SelectionChange(IFileDialog *pfd)
{
    return 0;
}

HRESULT STDMETHODCALLTYPE DirOverwrite(IFileDialog *pfd)
{
    return 0;
}

 OpenDirEventHandler(IFileDialogEvents *pfde)
{
     pfde->lpVtbl->OnFileOk = &FileOK;
     pfde->lpVtbl->OnSelectionChange = &SelectionChange;
     pfde->lpVtbl->OnOverwrite = &DirOverwrite;
}


Solution

  • The "Ordinal could not be located in the dynamic link library" error is due to the fact that TaskDialog needs to be linked to common controls dll, which you can fix as explained here https://stackoverflow.com/a/4308532/403671, i.e. add this line to your code:

    #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    

    Or use an app.manifest file, as explained here: Enabling Visual Styles

    And then you must fully implement a COM interface that you expose to the world, you cannot just implement the method you need, and you must initialize the v-table properly, for example, like this:

    IFileDialogEvents pfde;
    
    // build the full v-table
    pfde.lpVtbl = malloc(sizeof(IFileDialogEventsVtbl)); // make sure you free sometimes in the future
    pfde.lpVtbl->AddRef = &OnAddRefOrRelease;
    pfde.lpVtbl->Release = &OnAddRefOrRelease;
    pfde.lpVtbl->QueryInterface = &OnQueryInterface;
    pfde.lpVtbl->OnFolderChange = &OnFolderChange;
    pfde.lpVtbl->OnFolderChanging = &OnFolderChanging;
    pfde.lpVtbl->OnSelectionChange = &OnSelectionChange;
    pfde.lpVtbl->OnOverwrite = &OnOverwrite;
    pfde.lpVtbl->OnFileOk = &OnFileOk;
    pfde.lpVtbl->OnTypeChange = &OnTypeChange;
    pfde.lpVtbl->OnShareViolation = &OnShareViolation;
    
    // hook the Event Handler
    DWORD dwHook;
    hr = pfd->lpVtbl->Advise(pfd, &pfde, &dwHook);
    

    Now, for each method, you must declare all the parameters as they are declared by the interface definition, including the "this" pointer as the first argument, something like this:

    HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialogEvents* this, IFileDialog* pfd, IShellItem* psiFolder)
    {
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialogEvents* this, IFileDialog* pfd)
    {
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialogEvents* this, IFileDialog* pfd, FDE_SHAREVIOLATION_RESPONSE* pResponse)
    {
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialogEvents* this, IFileDialog* pfd)
    {
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialogEvents* this, IFileDialog* pfd)
    {
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialogEvents* this, IFileDialog* pfd)
    {
        return S_OK;
    }
    
    HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialogEvents* this, IFileDialog* pfd, IShellItem* psi, FDE_OVERWRITE_RESPONSE* pResponse)
    {
        return S_OK;
    }
    
    ULONG STDMETHODCALLTYPE OnQueryInterface(IFileDialogEvents* this, REFIID riid, void** ppvObject)
    {
        if (riid == &IID_IFileDialogEvents)
        {
            *ppvObject = this;
            return S_OK;
        }
        return E_NOINTERFACE;
    }
    
    ULONG STDMETHODCALLTYPE OnAddRefOrRelease(IFileDialogEvents* this)
    {
        // this is a hack, ok for sample, implement it properly
        return 1;
    }
    

    And then, there's a bug in GetDisplayName, you must always pass "this" (here psiResult) as the first argument:

    hr = psiResult->lpVtbl->GetDisplayName(psiResult, SIGDN_FILESYSPATH, &pszFilePath);