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;
}
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);