How do you Initialize a Nonregistered COM DLL? - winapi

Recently, some of our clients lost their XAudio2_7.dll from their C:/Windows/System32 directory, and now they can't hear any audio. Reinstalling DirectX or registering the dll normally would be sufficient enough to fix this problem, but we're looking for a solution that does not require admin rights. This is for Windows 7. Applications are written in CPP, and some of them are 32 bit and the rest are 64 bit.
There is a local copy of XAudio2_7.dll within the same directory of the exe, but that is not loaded unless that dll is registered since it's a COM dll. Registering for the current user (using "Regsvr32.exe /n /i:user XAudio2_7.dll") does not work since the dll does not implement a required interface "DllInstall".
One approach I've tried is to statically link a .lib of XAudio instead of using a dynamic link. Microsoft does not distribute XAudio2_7.lib with DirectX SDK. "No versions of the DirectX SDK contain the xaudio2.lib import library. DirectX SDK versions use COM to create a new XAudio2 object." msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.xaudio2.xaudio2create%28v=vs.85%29.aspx
Granted, this suggests that it may not be possible to use a static XAudio library even if I created one since it sounds like it depends on external objects defined in other libraries. It was still worth testing in case my hypothesis is incorrect.
Following the steps mentioned in this link: adrianhenke.wordpress.com/2008/12/05/create-lib-file-from-dll/
I didn't get very far with this method. Outside of the articles being dated, there were a couple problems with this. The MS link within the blog mentions this is for 32-bit dlls. Even for 32-bit dlls, dumpbin didn't work since it only exported the interface functions.
Dump of file C:\XAudioTest\32Bit\XAudio2_7.dll
File Type: DLL
Section contains the following exports for xaudio2.dll
00000000 characteristics
4C064181 time date stamp Wed Jun 02 07:33:21 2010
0.00 version
1 ordinal base
4 number of functions
4 number of names
ordinal hint RVA name
1 0 00030AA0 DllCanUnloadNow
2 1 00031150 DllGetClassObject
3 2 00031470 DllRegisterServer
4 3 000314D0 DllUnregisterServer
Summary
C000 .data
1000 .no_bbt
4000 .reloc
1000 .rsrc
7B000 .text
Later, I found this quote from another MSDN article. "The COM standard requires that COM DLLs export DllCanUnloadNow, DllGetClassObject, DllRegisterServer and DllUnregisterServer. Typically they will export nothing else. This means that you cannot get COM object or method information using dumpbin.exe." msdn.microsoft.com/en-us/library/aa446532.aspx
I've also tried using a third party program that claims it's able to create a .lib from a COM dll. www.binary-soft.com/dll2lib/dll2lib.htm
Although this did generate a 32-bit .lib file, compiling with the lib file generated unresolved external symbols. This utility does come with methods to handle unresolved symbols, but entering anything within Symbol Finder or Advanced Conversion Options would crash. Looking in the latest release notes (3.00), I found that they added support for Vista and no mentions for Windows 7. Also this doesn't work for 64-bit dlls.
I've tried another approach is to change the initialization sequence to use a nonregistered COM DLL described in this link: Use COM Object from DLL without register
The initialization code looks similar to this:
HMODULE audioLib = LoadLibrary(TEXT("XAudio2_7.dll"));
if (audioLib == NULL)
{
debugf(TEXT("Failed to create COM object. Unable to load XAudio2_7.dll library. GetLastError: %d"), GetLastError());
return;
}
typedef HRESULT (WINAPI* Function_DllGCO) (REFCLSID, REFIID, LPVOID*);
Function_DllGCO processAddress = (Function_DllGCO)GetProcAddress(audioLib, "DllGetClassObject");
if (processAddress == NULL)
{
debugf(TEXT("COM DLL failed to find the process address to interface function 'DllgetClassObject' within the XAudio2_7.dll. GetLastError: %d"), GetLastError());
return;
}
class __declspec(uuid("{5a508685-a254-4fba-9b82-9a24b00306af}")) xAudioGUID;
REFCLSID classID = __uuidof(xAudioGUID);
class __declspec(uuid("{00000001-0000-0000-c000-000000000046}")) classFactoryGUID;
REFIID classFactoryID = __uuidof(classFactoryGUID);
IClassFactory* ClassFactory = NULL;
if (processAddress(classID, classFactoryID, reinterpret_cast<LPVOID*>(&ClassFactory)) != S_OK)
{
debugf(TEXT("Failed to execute function pointer to DLLGetClassObject. GetLastError: %d"), GetLastError());
return;
}
class __declspec(uuid("{00000000-0000-0000-C000-000000000046}")) unknownGUID;
REFIID unknownID = __uuidof(unknownGUID);
if (ClassFactory->CreateInstance(NULL, unknownID, reinterpret_cast<void**>(&ClassFactory)) != S_OK)
{
debugf(TEXT("Class factory for XAudio2_7 failed to create an object instance. GetLastError: %d"), GetLastError());
ClassFactory->Release();
return;
}
if( XAudio2Create( &XAudio2, 0, AUDIO_THREAD) != S_OK ) //Fails here with error number: 1008 (An attempt was made to reference a token that does not exist.)
{
debugf( NAME_Init, TEXT( "Failed to create XAudio2 interface: GetLastError: %d" ), GetLastError());
return;
}
//Do other things
All WinAPI function calls passed excluding the XAudio2Create function. I'm uncertain why XAudio2Create is not using the object created from the factory, and I don't know what I need to do to get it to use that object. I'm still investigating what I can do here, but it's difficult to debug closed source libraries.
Before I knew about COM DLLs, a method I've tried is to use DLL-Redirection to force an application to use a particular DLL. Using the process described here: DLL redirection using manifests
XAudio2Create still failed. I don't have a strong strategy in identifying what's wrong with the manifest. I also haven't found much up to date documentation regarding manifests for dll redirection. From the sounds of this is this mostly used to load a particular DLL version. I don't think DLL redirection is the method I need since there is already a local copy of XAudio2_7 within the same directory of the exe, which means it should take precedence over the XAudio2_7 within the system directory according to: msdn.microsoft.com/en-us/library/windows/desktop/ms682586%28v=vs.85%29.aspx
Note: I removed the hyper links for some addresses since I don't have enough reputation points to post more than 2 links.

Have you tried registration-free activation?

Related

How can I filter a breakpoint on an exported function of a module, based on the name of other modules that call that function?

I am debugging a process in Visual Studio 2017 that loads many modules, I have set a function breakpoint on the CreateFile of Kernel32.dll.
This breakpoint hits many times, but I want to break the execution if a specific module makes call to the CreateFile function.
Passing unwanted breaks with F5 is not possible (there are too many of them).
There is no any code, all are just assemblies.
I want to see when "bcript.dll" (one of items in modules window) calls "CreateFile" from "Kernel32.dll".
As I didn't find any solution for my question, I decided to use other tools than Visual Studio, then I found some useful tools that I want to share:
1- In my case I used windows hooks:
void CInstallerDlg::OnBnClickedInstall()
{
if (m_hHook == INVALID_HANDLE_VALUE)
{
CString strTarget;
GetDlgItem(IDC_TARGET)->GetWindowText(strTarget);
HMODULE hDll = LoadLibrary(L"Dll.dll");
if (hDll)
{
HOOKPROC hProc = (HOOKPROC)GetProcAddress(hDll, (char*)1);
if (hProc)
{
FARPROC SetTarge = GetProcAddress(hDll, (char*)2);
if (SetTarge)
{
((void (CALLBACK *) (CString))SetTarge)(strTarget);
m_hHook = SetWindowsHookEx(WH_CALLWNDPROC, hProc, hDll, 0);
}
}
}
}
}
2- sysinternals have lots of analyzing tools.
3- winapioverride32 allows monitoring and overriding api calls.
4- snowman is a PE decompiler.
5- LordPE: download link allows to view and edit binary data in PE sections, along with general details about PE.

When dynamically loading a windows dll how can I tell the name of a missing dependent module

I sometimes have a problem where dynamic libraries fail to load at customer sites. This is usually because their system is configured wrong.
I need to be able to get the name of the dependent module that is missing so I can log it, and make fixing their systems much easier.
How can I accomplish this?
Note that I need an answer I can put in my code, this means I cannot use Dependency Checker, or Process Monitor, or any other tool to work out the problem.
I really do need a way to do it programatically.
The fact that Dependency Checker can do it means there is a way.
begin from win7 ntdll.dll export next api:
struct FAILUREDATA
{
NTSTATUS status;
WCHAR DllName[0x20];
WCHAR FunctionName[0x20];
};
extern "C" NTSYSCALLAPI FAILUREDATA* NTAPI LdrGetFailureData();
ntdll.dll Ldr-subsystem log failure in 2 case - GetProcAddress fail (in this case FunctionName filled) or if DLL load fail. but with one exception - if top-level (i.e. LibFileName which you use in LoadLibrary[Ex] call) not found - failure not logged. but if dependent DLL not found (or fail initialize) - this error will be logged and name of dependent DLL recorded in FAILUREDATA.DllName (if it longer than 31 symbol - it will be truncated) - usual status in this case STATUS_DLL_NOT_FOUND or STATUS_DLL_INIT_FAILED. also if top-dll found but fail initialize - this also will be logged. if some function will be not resolved during dll load - the FunctionName will be valid and usual status in this case - STATUS_ENTRYPOINT_NOT_FOUND or STATUS_ORDINAL_NOT_FOUND
unfortunately LdrGetFailureData not included in ntdll[p].lib - so need use GetProcAddress for get it. you can declare next global data:
static union {
FAILUREDATA* (NTAPI *LdrGetFailureData)();
PVOID pvLdrGetFailureData;
};
and on start call
pvLdrGetFailureData = GetProcAddress(GetModuleHandle(L"ntdll"), "LdrGetFailureData");
then implement next function:
void OnLdrFail(PCWSTR TopDllName)
{
if (LdrGetFailureData)
{
FAILUREDATA* pfd = LdrGetFailureData();
if (NTSTATUS status = pfd->status)
{
DbgPrint("%x loaded DLL <%S> fail DLL <%S> %S\n", status, TopDllName, pfd->DllName, pfd->FunctionName);
}
else
{
// in case loaded(top) DLL not found
DbgPrint("%x loaded DLL <%S>\n", GetLastError(), TopDllName);
}
}
}
in place DbgPrint of course implement your real logging
and you call OnLdrFail after LoadLibraryW fail. say like this
#define CLEAR_FAILURE_DATA() if (LdrGetFailureData) LdrGetFailureData()->status = 0
CLEAR_FAILURE_DATA();
HMODULE hmod = LoadLibraryW(lpLibFileName);
if (!hmod)
{
OnLdrFail(lpLibFileName);
}
because how i say Ldr not fill FAILUREDATA in case lpLibFileName not found - it not clear previous state of this structure - so need do this yourself (here can be previous error saved, unrelated to current call) (however if dependent of lpLibFileName not found or any dll initialization fail - this will be logged)
for example:
A.DLL dependent from B.DLL and B.DLL not found will be next log
c0000135 loaded DLL <A.DLL> fail DLL <B.DLL>
if DllMain from B.DLL return FALSE
c0000142 loaded DLL <A.DLL> fail DLL <B.DLL>
if DllMain from A.DLL return FALSE
c0000142 loaded DLL <A.DLL> fail DLL <A.DLL>
if A.DLL import SomeFunc from B.DLL but B.DLL not export it
c0000139 loaded DLL <A.DLL> fail DLL <Unknown> SomeFunc

Specify a "back-up" load path with Load-Time Dynamic Linking?

When using 'Load-Time Dynamic Linking'(using an import library .lib in compilation that loads a dll when the module is loaded) in a Win32 application, is it possible to affect the search order?
The goal is to have a library loaded using the normal search order, with a back-up path if one is not found on the normal path.
Example: foo.lib is linked in the project. At load time:
If foo.dll is present in the System path or any of the other paths mentioned in Dynamic-Link Library Search Order then it will be loaded.
Else mySpecifiedPath/foo.dll will be loaded.
Clarification: The back-up dll should only be loaded if no version of the dll is found in the standard LoadLibrary search paths.
Leaving the old answer at the bottom and adding a new one at the top:
The combination of delay-loading and the delay-load Failure Hook as described at provides a nice way to handle this.
Register the failure hook that loads the back-up dll before using any symbol from the library.
#include <Windows.h>
#include "foo.h"
#include <delayimp.h>
//access to set the failure hook
ExternC PfnDliHook __pfnDliFailureHook2;
const char* dllName = "foo.dll";
const char* dllBackupPath = "pathToBackup\\foo.dll";
FARPROC WINAPI DelayLoadHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
//if the failure was failure to load the designated dll
if (dliNotify == dliFailLoadLib &&
strcmp(dllName, pdli->szDll) == 0 &&
pdli->dwLastError == ERROR_MOD_NOT_FOUND)
{
printf("loading back-up dll\n");
//return the successfully loaded back-up lib,
//or 0, the LoadLibrary fails here
HMODULE lib = LoadLibraryA(dllBackupPath);
return (FARPROC)lib;
}
return 0;
}
int main()
{
//set the Failure Hook
__pfnDliFailureHook2 = &DelayLoadHook;
//when this function is called the library will be loaded
//from standard paths. If it is not found the Failure Hook
//set above will be called.
int test = ::intReturningFuncFromFooDll();
printf("%d", test);
getchar();
return 0;
}
===========old answer below==============
Looks like what I was looking for is delay-loading, as mentioned in the comment by IInspectable. I found information regarding this:
Linker Support for Delay-Loaded DLLs
Here is some code demonstrating the usage I mentioned in the original post:
//load the dll using the normal search
HMODULE lib = LoadLibrary( L"foo.dll" );
//if unsuccessful, try a specified path
if (lib == NULL)
{
LoadLibrary( L"mySpecifiedPath/foo.dll" );
}
if (lib == NULL)
{
//make sure that the library is not used,
//or exit the application, as it was not found
}
Thanks for the help!
edit to old answer: This dynamic loading would be used before any symbol from the library is used. The delay loaded fills out the symbol addresses using the module loaded here the first time a symbol from the library is accessed.
Looking at the Dynamic-Link Library Search Order documentation, it should be obvious, that the final location searched is the PATH environment variable. Applying that knowledge, the easiest solution that meets all your requirements is to append the backup location to the PATH variable.
This could be done in two ways:
An external launcher: The CreateProcess API call allows an application, to pass a custom environment to the new process through the lpEnvironment argument. The current process' environment block can be queried by calling GetEnvironmentStrings.
Enable delay-loading of the desired DLL(s) using the /DELAYLOAD linker option, and modify the process' environment at application startup. After retrieving the PATH environment variable (using GetEnvironmentVariable), it can be modified and updated in the environment block by calling SetEnvironmentVariable.

Including DirectShow library into Qt for video thumbnail

I'm trying to implement http://msdn.microsoft.com/en-us/library/dd377634%28v=VS.85%29.aspx on Qt, to generate a poster frame/thumbnail for video files.
I have installed both Windows Vista and Windows 7 SDK. I put:
#include "qedit.h"
in my code (noting there is also one in C:\Qt\2010.04\mingw\include), I add:
win32:INCLUDEPATH += $$quote(C:/WindowsSDK/v6.0/Include)
to my *.pro file. I compile and get " error: sal.h: No such file or directory". Finding this in VC++ I add
win32:INCLUDEPATH += $$quote(C:/Program Files/Microsoft Visual Studio 10.0/VC/include)
And now have 1400 compile errors. So, I abandon that and just add:
win32:LIBS += C:/WindowsSDK/v7.1/Lib/strmiids.lib
to my *.pro file and try to run (without including any headers):
IMediaDet mediadet;
But then I get "error: IMediaDet: No such file or directory".
#include "qedit.h"
gives me the same error (it looks like it's pointing to the Qt version) and
#include "C:/WindowsSDK/v6.0/Include/qedit.h"
goes back to generating 1000's of compile errors.
Sigh, so much trouble for what should be 10 lines of code...
Thanks for your comments and help
Since you say you are "a C++/Qt newbie" then I suspect that the real issue may be that you are attempting to load the library yourself rather than simply linking your application to it?
To link an external library into your application with Qt all you need to do is modify the appropriate .pro file. For example if the library is called libfoo.dll you just add
LIBS += -L/path/to/lib -lfoo
You can find more information about this in the relevant section of the qmake manual. Note that qmake commonly employs Unix-like notation and transparently does the right thing on Windows.
Having done this you can include the library's headers and use whatever classes and functions it provides. Note that you can also modify the project file to append an include path to help pick up the headers eg.
INCLUDEPATH += /path/to/headers
Again, more information in the relevant section of the qmake manual.
Note that both these project variables work with relative paths and will happily work with .. to mean "go up a directory" on all platforms.
Note that qedit.h requires dxtrans.h, which is part of DirectX9 SDK.
You can find dxtrans.h in DirectX SDK from August 2006. Note that dxtrans.h is removed from newer DirectX SDKs.
Do you have access to the source of the external library? The following assumes that you do.
What I do when I need to extract a class from a library with only functions resolved, is to use a factory function in the library.
// Library.h
class SomeClass {
public:
SomeClass(std::string name);
// ... class declaration goes here
};
In the cpp file, I use a proxy function outside the extern "C" when my constructor requires C++ parameters (e.g. types such as std::string), which I pass as a pointer to prevent the compiler from messing up the signature between C and C++. You can avoid the extra step if your constructor doesn't require parameters, and call new SomeClass() directly from the exported function.
// Library.cpp
#include "Library.h"
SomeClass::SomeClass(std::string name)
{
// implementation details
}
// Proxy function to handle C++ types
SomeClass *ImplCreateClass(std::string* name) { return new SomeClass(*name); }
extern "C"
{
// Notice the pass-by-pointer for C++ types
SomeClass *CreateClass(std::string* name) { return ImplCreateClass(name); }
}
Then, in the application that uses the library :
// Application.cpp
#include "Library.h"
typedef SomeClass* (*FactoryFunction)(std::string*);
// ...
QLibrary library(QString("MyLibrary"));
FactoryFunction factory = reinterpret_cast(library.resolve("CreateClass"));
std::string name("foobar");
SomeClass *myInstance = factory(&name);
You now hold an instance of the class declared in the library.

DSOFramer closing Excel doc in another window. If unsaved data in file, dsoframer fails to open with "Attempt to access invalid address"

I'm using Microsoft's DSOFramer control to allow me to embed an Excel file in my dialog so the user can choose his sheet, then select his range of cells; it's used with an import button on my dialog.
The problem is that when I call the DSOFramer's OPEN function, if I have Excel open in another window, it closes the Excel document (but leaves Excel running). If the document it tries to close has unsaved data, I get a dialog boxclosing Excel doc in another window. If unsaved data in file, dsoframer fails to open with a messagebox: Attempt to access invalid address.
I built the source, and stepped through, and its making a call in its CDsoDocObject::CreateFromFile function, calling BindToObject on an object of class IMoniker. The HR is 0x8001010a The message filter indicated that the application is busy. On that failure, it tries to InstantiateDocObjectServer by classid of CLSID Microsoft Excel Worksheet... this fails with an HRESULT of 0x80040154 Class not registered. The InstantiateDocObjectServer just calls CoCreateInstance on the classid, first with CLSCTX_LOCAL_SERVER, then (if that fails) with CLSCTX_INPROC_SERVER.
I know DSOFramer is a popular sample project for embedding Office apps in various dialog and forms. I'm hoping someone else has had this problem and might have some insight on how I can solve this. I really don't want it to close any other open Excel documents, and I really don't want it to error-out if it can't close the document due to unsaved data.
Update 1: I've tried changing the classid that's passed in to Excel.Application (I know that class will resolve), but that didn't work. In CDsoDocObject, it tries to open key HKEY_CLASSES_ROOT\CLSID\{00024500-0000-0000-C000-000000000046}\DocObject, but fails. I've visually confirmed that the key is not present in my registry; The key is present for the guide, but there's no DocObject subkey. It then produces an error message box: The associated COM server does not support ActiveX document embedding. I get similar (different key, of course) results when I try to use the Excel.Workbook programid.
Update 2: I tried starting a 2nd instance of Excel, hoping that my automation would bind to it (being the most recently invoked) instead of the problem Excel instance, but it didn't seem to do that. Results were the same. My problem seems to have boiled down to this: I'm calling the BindToObject on an object of class IMoniker, and receiving 0x8001010A (RPC_E_SERVERCALL_RETRYLATER) The message filter indicated that the application is busy. I've tried playing with the flags passed to the BindToObject (via the SetBindOptions), but nothing seems to make any difference.
Update 3: It first tries to bind using an IMoniker class. If that fails, it calls CoCreateInstance for the clsid as a fallback method. This may work for other MS Office objects, but when it's Excel, the class is for the Worksheet. I modified the sample to CoCreateInstance _Application, then got the workbooks, then called the Workbooks::Open for the target file, which returns a Worksheet object. I then returned that pointer and merged back with the original sample code path. All working now.
#Jinjin
You can use the #import directive to import your Excel's OLB file. this should generate (and automatically include an Excel .tlh file which contains the structures for _Application (and the rest you need)). Ideally, you should find an OLB file that matches the earliest Excel version that you wish to support. The one on your local system is probably in c:\Program Files\Microsoft Office\Office12 (presuming you have Office 2007 installed). It may be named Excel.olb, or XL5EN32.OLB (different, obviously if you haven't installed the US English verion of Excel.
So, copy the .olb file to your project source directory, then at the top of the source file, add a line for #import "XL5EN32.olb".
Yes, opens older versions. Best way to guarantee that this will be the case is to find an OLB file (mentioned in item 1 above) that is from an installation of Excel that is the earliest version you wish to support. I use an Excel9.olb from Office 2000. Works fine with my testing of Excel versions all the way to the latest from Office 2007.
Yes, you should use dsoframer normally after making these changes.
I'm afraid I probably can't do that due to restrictions of my employer. However, if you take the "stock" dsoframer project, make the changes described in part 1 of this post, and the changes I described in my earlier post, you have pretty much recreated exactly what I have.
#Jinjin: did you put the import statement (#import "XL5EN32.olb") in the cpp file where you are using the Excel::_Application? If not, do that... can't just add it to the project. If you have already done that, try also adding this statement to the cpp file where you are using those mappings #import "Debug\XL5EN32.tlh". The tlh file is a header that is generated by running the #import; you should find it in your Debug directory (presuming you're performing a Debug build).
Renaming _Application to Application (and the others) is not the right way to go. The _Application structure is the one that has the mappings. That is why you are not finding the app->get_Workbooks.
What file are you looking in that you are finding Application but not _Application?
Assuming you are using the DSOFRAMER project, you need to add this code to dsofdocobj.cpp in the CreateFromFile function, at around line 348:
CLSID clsidExcelWS;
hr = CLSIDFromProgID(OLESTR("Excel.Sheet"),clsidExcelWS);
if (FAILED(hr)) return hr;
if (clsid == clsidExcelWS)
{
hr = InstantiateAndLoadExcel(pwszFile, &pole);
if (FAILED(hr)) return hr;
}
else
{
<the IMoniker::BindToObject call and it's failure handling from the "stock" sample goes here>
}
Then, define the following new member function in CDsoDocObject:
////////////////////////////////////////////////////////////////////////
// CDsoDocObject::InstantiateAndLoadExcel (protected)
//
// Create an instance of Excel and load the target file into its worksheet
//
STDMETHODIMP CDsoDocObject::InstantiateAndLoadExcel(LPWSTR pwszFile, IOleObject **ppole)
{
IUnknown *punkApp=NULL;
Excel::_Application *app=NULL;
Excel::Workbooks *wbList=NULL;
Excel::_Workbook *wb;
CLSID clsidExcel;
HRESULT hr = CLSIDFromProgID(OLESTR("Excel.Application"), &clsidExcel);
if (FAILED(hr))
return hr;
hr = CoCreateInstance(clsidExcel, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&punkApp);
if (SUCCEEDED(hr))
{
hr = punkApp->QueryInterface(__uuidof(Excel::_Application),(LPVOID *)&app);
if (SUCCEEDED(hr))
{
hr = app->get_Workbooks(&wbList);
VARIANT vNoParam;
VariantInit(&vNoParam);
V_VT(&vNoParam) = VT_ERROR;
V_ERROR(&vNoParam) = DISP_E_PARAMNOTFOUND;
VARIANT vReadOnly;
VariantInit(&vReadOnly);
V_VT(&vReadOnly) = VT_BOOL;
V_BOOL(&vReadOnly) = VARIANT_TRUE;
BSTR bstrFilename = SysAllocString(pwszFile);
hr = wbList->Open(bstrFilename, vNoParam,vNoParam,vNoParam,vNoParam,vReadOnly,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,vNoParam,0,&wb);
if (SUCCEEDED(hr))
hr = wb->QueryInterface(IID_IOleObject, (void**)ppole);
VariantClear(&vReadOnly);
VariantClear(&vNoParam);
SysFreeString(bstrFilename);
}
}
if (wb != NULL) wb->Release();
if (wbList != NULL) wbList->Release();
if (app != NULL) app->Release();
if (punkApp != NULL) punkApp->Release();
return hr;
}

Resources