The MIDL compiler generates code for a proxy/stub with registration routines that write to HKEY_LOCAL_MACHINE. Is there any way (preferably without hacking the MIDL-generated code and without bypassing all that generated code in favor of custom code) to register a MIDL-generated p/s in HKEY_CURRENT_USER?
Also: Will this work if both the p/s and the COM server are registered per-user like this? I just found (after a very frustrating 48 hours) that a p/s registered machine-wide will not work correctly if the COM server is registered per-user. Specifically, the asynchronous call logic (ICallFactory::CreateCall) of the p/s will fail under these circumstances.
Using RegOverridePredefKey is the right answer.
Then register with:
regsvr32 /n /i:user C:\src\myCode.dll
With "/i:user", regsvr32 calls your the "DllInstall" function instead of DllRegisterServer.
Example implementation of DllInstall:
extern "C" STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine)
{
HRESULT hr = E_FAIL;
static const wchar_t szUserSwitch[] = L"user";
if (pszCmdLine != NULL)
{
if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0)
{
ATL::AtlSetPerUserRegistration(true); // is this really needed??
}
}
LSTATUS status = RegCreateKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hkcu_classes, &disposition);
if (status == ERROR_SUCCESS)
{
status = RegOverridePredefKey(HKEY_CLASSES_ROOT, hkcu_classes);
}
hr = HRESULT_FROM_NT(status);
if (SUCCEEDED(hr))
{
if (bInstall)
{
hr = DllRegisterServer();
if (FAILED(hr))
{
DllUnregisterServer();
}
}
else
{
hr = DllUnregisterServer();
}
}
}
Related
I have the following code (main part taken from MS SDK v7.1 Sample code, which demonstrates how to start an non-elevated process from an elevated one)-
The elevated and non-elevated process will be started on an (unsupervised) server, so user interaction is a no-go (except for the configuring part, if needed, of course).
The problem is, that the non-elevated process started through IShellDispatch2->ShellExecute is still elevated (expected was to be non-elevated).
This was confirmed by using IsUserAnAdmin() API.
Any ideas why the process created through ShellExecute() does have still elevated rights?
Some more relevant details: on my machine UAC is disabled.
The VS 2013 based application is manifested:
)
(manifesting can be disabled with /MANIFEST:no linker flag, if it will help in solving this).
I'm compiling it on Windows 7 x64 using VS 2013.
#include <shlwapi.h>
#include <shlobj.h>
#include <comutil.h>
#pragma comment(lib, "shlwapi.lib")
// link with (at least) OleAut32.lib shlwapi.lib comsupp.lib shell32.lib uuid.lib
// sorry for the bad formatting
int main(void)
{
std::wstring processName(L"myapp.exe"), processParams(L"-myAppParam");
LPWSTR processNamePtr = const_cast<LPWSTR>(processName.c_str());
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
IShellView *psv;
HRESULT hr = GetShellViewForDesktop(IID_PPV_ARGS(&psv));
if (SUCCEEDED(hr))
{
IShellDispatch2 *psd;
hr = GetShellDispatchFromView(psv, IID_PPV_ARGS(&psd));
if (SUCCEEDED(hr))
{
BSTR bstrProcessName = SysAllocString(processNamePtr);
hr = bstrProcessName ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
VARIANT vtEmpty = {}; // VT_EMPTY
LPWSTR processParamsPtr = const_cast<LPWSTR>(processParams.c_str());
_bstr_t bstrProcessParams(processParamsPtr);
VARIANT varParams;
varParams.vt = VT_BSTR;
varParams.bstrVal = bstrProcessParams;
char processDir[MAX_PATH + 1];
::GetCurrentDirectory(MAX_PATH, processDir);
_bstr_t bstrProcessDir(processDir);
VARIANT varProcessDir;
varProcessDir.vt = VT_BSTR;
varProcessDir.bstrVal = bstrProcessDir;
_bstr_t bstrOperation("open");
VARIANT varOperation;
varOperation.vt = VT_BSTR;
varOperation.bstrVal = bstrOperation;
hr = psd->ShellExecute(bstrProcessName,
varParams, // reinterpret_cast<_variant_t&>(bstrProcessParams),
varProcessDir,
varOperation,
vtEmpty);
SysFreeString(bstrProcessName);
SysFreeString(bstrProcessParams);
}
psd->Release();
}
psv->Release();
}
CoUninitialize();
}
} // main()
// use the shell view for the desktop using the shell windows automation to find the
// desktop web browser and then grabs its view
//
// returns:
// IShellView, IFolderView and related interfaces
HRESULT GetShellViewForDesktop(REFIID riid, void **ppv)
{
*ppv = NULL;
IShellWindows *psw;
HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
if (SUCCEEDED(hr))
{
HWND hwnd;
IDispatch* pdisp;
VARIANT vEmpty = {}; // VT_EMPTY
if (S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, (long*)&hwnd, SWFO_NEEDDISPATCH, &pdisp))
{
IShellBrowser *psb;
hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
if (SUCCEEDED(hr))
{
IShellView *psv;
hr = psb->QueryActiveShellView(&psv);
if (SUCCEEDED(hr))
{
hr = psv->QueryInterface(riid, ppv);
psv->Release();
}
psb->Release();
}
pdisp->Release();
}
else
{
hr = E_FAIL;
}
psw->Release();
}
return hr;
} // GetShellViewForDesktop()
// From a shell view object gets its automation interface and from that gets the shell
// application object that implements IShellDispatch2 and related interfaces.
HRESULT GetShellDispatchFromView(IShellView *psv, REFIID riid, void **ppv)
{
*ppv = NULL;
IDispatch *pdispBackground;
HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
if (SUCCEEDED(hr))
{
IShellFolderViewDual *psfvd;
hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
if (SUCCEEDED(hr))
{
IDispatch *pdisp;
hr = psfvd->get_Application(&pdisp);
if (SUCCEEDED(hr))
{
hr = pdisp->QueryInterface(riid, ppv);
pdisp->Release();
}
psfvd->Release();
}
pdispBackground->Release();
}
return hr;
} // GetShellDispatchFromView()
On my machine UAC is disabled.
Right there is your problem. By disabling UAC you stop the system from creating processes as standard user. If the logged on user is an administrator, then processes run with a fully privileged token.
With UAC disabled, the explorer process that runs the shell is started using the full token of the interactive user, which it seems is the token of an admin user. And so when the shell starts a new process with IShellDispatch2->ShellExecute that new process runs under the same user token.
You'll need to enable UAC to allow the system to create processes with standard user tokens. When you enable UAC, the shell's explorer process will run as standard user and so IShellDispatch2->ShellExecute will create new processes running as standard user.
I have a fairly complex requirement. My STA COM object is implemented in a DLL (can't move it to out-of-process EXE). By the means of DllSurrogate I am hosting my object in a dllhost.exe process. My object has an UI attached to it (a plain modeless dialog) but I need the PreTranslateAccelerator mechanism in order for some shortcuts to work, etc. Since COM activates my object and hosts it in the default dllhost.com, I am obviously not controlling the message pump.
Is there still a way to pre-translate messages in this scenario? I doubt COM has foreseen such a specific scenario but maybe I am missing something.
Okay here it is. I hope I didn't leave out anything important. Basically, I have created a custom CMyComCreator instead of the default one. Instead of just creating a COM object and returning an interface pointer, I spin a worker UiThread. I use MyData structure to pass data across threads. Once the worker thread has finished setting up, I use the CComGITPtr to transfer the marshalled interface pointer from the UiThread back to the main. The consumers (out-of-process) end up with interface pointers that talk directly to the UiThread bypassing the main thread. You may think of CMyDialog as a modeless dialog which sends a PostQuitMessage on destruction to terminate the message loop. That's all. May look cumbersome but it works good.
struct MyData
{
ATL::CComGITPtr<IUnknown> Unk;
ATL::CEvent Event;
HRESULT hr;
MyData() : hr(E_OUTOFMEMORY), Event(FALSE, FALSE) { }
};
static CMessageLoop * MessageLoop;
class CMyComCreator
{
public:
static HRESULT WINAPI CreateInstance(
_In_opt_ void* pv,
_In_ REFIID riid,
_COM_Outptr_ LPVOID* ppv)
{
ATLASSERT(ppv != NULL);
if (ppv == NULL)
return E_POINTER;
*ppv = NULL;
HRESULT hRes = E_OUTOFMEMORY;
MyData* data = NULL;
ATLPREFAST_SUPPRESS(6014 28197)
/* prefast noise VSW 489981 */
ATLTRY(data = _ATL_NEW MyData)
ATLPREFAST_UNSUPPRESS()
if (data != NULL)
{
HANDLE thread = (HANDLE)_beginthreadex(NULL, 0, UiThread, (void *)data, 0, NULL);
if (thread)
{
WaitForSingleObject(data->Event, INFINITE);
CloseHandle(thread);
hRes = data->hr;
if (SUCCEEDED(hRes))
{
ATL::CComPtr<IUnknown> unk;
hRes = data->Unk.CopyTo(&unk);
if (SUCCEEDED(hRes))
{
hRes = unk->QueryInterface(riid, ppv);
}
}
}
delete data;
}
return hRes;
}
};
typedef CMyComCreator _CreatorClass;
static unsigned __stdcall UiThread(void * param)
{
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
MyData * data = (MyData *)param;
ATL::CComObject<CMyDialog> * bb;
data->hr = ATL::CComObject<CMyDialog>::CreateInstance(&bb);
ATL::CComPtr<IUnknown> unk((IDispatch *) bb);
data->Unk = unk;
unk.Release();
data->Event.Set();
if (SUCCEEDED(data->hr))
{
CMessageLoop theLoop;
MessageLoop = &theLoop;
int nRet = theLoop.Run();
MessageLoop = NULL;
}
CoUninitialize();
return 0;
}
I needed to pack everything in a single DLL.
In which case, DllSurrogate is not the only way of doing this. There's also Rundll32:
INFO: Windows Rundll and Rundll32 Interface
This would allow you to run your own message loop inside the DLL's EntryPoint and have complete control over message processing, including PreTranslateMessage. You can copy the message loop logic from an ATL EXE server.
Bear in mind, there's still 32-bit and 64-bit version of "RunDll32.exe" in every 64-bit Windows OS. Use the one which matches the bit-ness of your DLL.
I tried to code an icon handler in c++ but i have some issues to do this. I followed the turoriel from codeproject
The difference with this tutorial is i wanna code my dll with an ATL project from the wizard of visual studio (2013). So i create a new ATL project and i had a COM +1 class to this project (this the code of the header).
The problem is it seams that my dll is attach but is detach right after.
I'll put some code and more explication bellow :
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
_AtlModule.InitLibId();
//_AtlModule.RegisterAppId();
}
return _AtlModule.DllMain(dwReason, lpReserved);;
}
This is my entry point of my dll. I did a lot of try and test on this entry point. I tried to write some log in function of the dwReason and this function is launch only when i compile. One time with the attribute DLL_PROCESS_ATTACH and an other time with the attribute DLL_PROCESS_DETACH. After that nothing seams to work even when i set my registry to call this dll in the IconHandler of my file.
I'll put my dllmain.h, my handler.h, my idl file and my rgs file. If you need more code to help me i'll put them later.
dllmain.h
class CQIIconDllModule : public ATL::CAtlDllModuleT< CQIIconDllModule >
{
public :
DECLARE_LIBID(LIBID_QIIconDllLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_QIHANDLER, "{7FFCD43D-9EB3-4F76-940C-98C333FB8A99}")
};
extern class CQIIconDllModule _AtlModule;
IconHandler.h
// QIHandler.h : Declaration of the CQIHandler
#pragma once
#include "resource.h" // main symbols
#include <ShlObj.h>
#include "QIIconDll_i.h"
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif
using namespace ATL;
// CQIHandler
class ATL_NO_VTABLE CQIHandler :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CQIHandler, &CLSID_QIHandler>,
public IQIHandler,
public IPersistFile,
public IExtractIcon
{
public:
CQIHandler()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_QIHANDLER)
DECLARE_NOT_AGGREGATABLE(CQIHandler)
BEGIN_COM_MAP(CQIHandler)
COM_INTERFACE_ENTRY(IQIHandler)
COM_INTERFACE_ENTRY(IPersistFile)
COM_INTERFACE_ENTRY(IExtractIcon)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
// IPersistFile
STDMETHODIMP GetClassID( CLSID* ) { return E_NOTIMPL; }
STDMETHODIMP IsDirty() { return E_NOTIMPL; }
STDMETHODIMP Save( LPCOLESTR, BOOL ) { return E_NOTIMPL; }
STDMETHODIMP SaveCompleted( LPCOLESTR ) { return E_NOTIMPL; }
STDMETHODIMP GetCurFile( LPOLESTR* ) { return E_NOTIMPL; }
STDMETHODIMP Load( LPCOLESTR wszFile, DWORD )
{
USES_CONVERSION;
lstrcpyn ( m_szFilename, W2CT(wszFile), MAX_PATH );
return S_OK;
}
// IExtractIcon
STDMETHODIMP GetIconLocation( UINT uFlags, LPTSTR szIconFile, UINT cchMax,
int* piIndex, UINT* pwFlags );
STDMETHODIMP Extract( LPCTSTR pszFile, UINT nIconIndex, HICON* phiconLarge,
HICON* phiconSmall, UINT nIconSize );
protected:
TCHAR m_szFilename[MAX_PATH]; // Full path to the file in question.
};
OBJECT_ENTRY_AUTO(__uuidof(QIHandler), CQIHandler)
my idl file
// QIIconDll.idl : IDL source for QIIconDll
//
// This file will be processed by the MIDL tool to
// produce the type library (QIIconDll.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(a817e7a2-43fa-11d0-9e44-00aa00b6770a),
dual,
pointer_default(unique)
]
interface IComponentRegistrar : IDispatch
{
[id(1)] HRESULT Attach([in] BSTR bstrPath);
[id(2)] HRESULT RegisterAll();
[id(3)] HRESULT UnregisterAll();
[id(4)] HRESULT GetComponents([out] SAFEARRAY(BSTR)* pbstrCLSIDs, [out] SAFEARRAY(BSTR)* pbstrDescriptions);
[id(5)] HRESULT RegisterComponent([in] BSTR bstrCLSID);
[id(6)] HRESULT UnregisterComponent([in] BSTR bstrCLSID);
};
[
object,
uuid(1A80BA8B-2932-4EB4-AA88-5216F92BBA33),
pointer_default(unique)
]
interface IQIHandler : IUnknown{
};
[
uuid(17AD604F-FDD1-453C-A2D2-EAD3FCC42AB5),
version(1.0),
custom(a817e7a1-43fa-11d0-9e44-00aa00b6770a,"{D32D9690-E6A4-44D8-A949-5F39D35269F8}")
]
library QIIconDllLib
{
importlib("stdole2.tlb");
[
uuid(D32D9690-E6A4-44D8-A949-5F39D35269F8)
]
coclass CompReg
{
[default] interface IComponentRegistrar;
};
[
uuid(7FFCD43D-9EB3-4F76-940C-98C333FB8A99)
]
coclass QIHandler
{
[default] interface IQIHandler;
};
};
rgs file :
HKCR
{
NoRemove CLSID
{
ForceRemove {D32D9690-E6A4-44D8-A949-5F39D35269F8} = s 'CompReg Class'
{
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
TypeLib = s '{17AD604F-FDD1-453C-A2D2-EAD3FCC42AB5}'
Version = s '1.0'
}
}
}
rgs handler file :
HKCR
{
NoRemove CLSID
{
ForceRemove {7FFCD43D-9EB3-4F76-940C-98C333FB8A99} = s 'QIHandler Class'
{
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
TypeLib = s '{17AD604F-FDD1-453C-A2D2-EAD3FCC42AB5}'
Version = s '1.0'
}
}
NoRemove qifile
{
NoRemove DefaultIcon = s '%%1'
NoRemove ShellEx
{
ForceRemove IconHandler = s '{7FFCD43D-9EB3-4F76-940C-98C333FB8A99}'
}
}
}
I hope i was clear in my explanation. If you need more information to response at my question just tell me i'll be fast with my answer. I'll thank you to take your time to help me.
Florian
EDIT :
See my test on my dllmain. cpp
BOOL ret = false;
FILE *file;
fopen_s(&file, "test.txt","a+"); /* apend file (add text to
a file or create a file if it does not exist.*/
if (dwReason == DLL_PROCESS_ATTACH)
{
fprintf(file,"%s","Initialize AtlModule\n"); /*writes*/
_AtlModule.InitLibId();
HRESULT hr = _AtlModule.RegisterAppId();
if (SUCCEEDED(hr))
{
fprintf(file,"%s","Registrer app succeeded \n"); /*writes*/
hr = _AtlModule.RegisterServer(TRUE, &CLSID_QIHandler);
if (SUCCEEDED(hr))
{
fprintf(file,"%s","RegisterServer succeeded \n"); /*writes*/
hr = _AtlModule.UpdateRegistryAppId(TRUE);
if (SUCCEEDED(hr))
{
fprintf(file,"%s","UpdateRegistryAppId succeeded \n"); /*writes*/
ret = true;
}
}
}
}
#ifdef _DEBUG
else if (dwReason == DLL_PROCESS_DETACH)
{
_AtlModule.UpdateRegistryAppId(FALSE);
_AtlModule.Term();
}//*/
#endif
if (dwReason == DLL_PROCESS_DETACH && lpReserved != NULL)
{
ret = true;
}
else if (dwReason == DLL_PROCESS_DETACH)
{
fprintf(file,"%s","Execption ! \n"); /*writes*/
}
fclose(file); /*done!*/
return ret;
The result in my test.txt :
Initialize AtlModule
Registrer app succeeded
RegisterServer succeeded
UpdateRegistryAppId succeeded
Execption !
Sombody here have a idee ?
I didn't found any solution to work my solution. So i write my handler in C# even if this is not the best way to do that because that call some native function.
I used the follow tutorial from code-project : NET-Shell-Extensions-Shell-Icon-Handlers
I hope this tutorial gonna help some people. If someone have the answer to my preview question i will be glad to know what i have to do.
Florian
Can anyone Please tell me how to get Model name of Windows Machine.
I am new to Windows VC++.
For Example i have an IBM ThinkCenter M50 running on Windows. Here the Model name is "Think Center M50". I want to get this from the System using some API.
Thanks in Advance,
Shashi Kiran G M
Alternatively, you could use the registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SystemInformation
also: HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\BIOS (Win7 or later only)
The SystemManufacturer and SystemProductName entries should do it. Saves using WMI, which i try to avoid at all costs for performance reasons.
As Ben suggests, you'll need to use WMI for this.
The class you're looking for is Win32_ComputerSystem, which contains a read-only Model property of type string that returns the product name that a manufacturer gives to a computer.
I'll leave writing the C++ code to make this WMI call as an exercise for the reader.
Do note Ben's caveat as well: not all manufacturers publish this information in the BIOS. It's very likely that IBM does, so your test case should work out fine, but this is not a universal assumption that you are justified in making. Applications should not rely on this property containing a particular value.
With the help of the Microsoft example code, I was able to create this method.
#include <Wbemidl.h>
#pragma comment(lib, "wbemuuid.lib")
std::pair<CString,CString> getComputerManufacturerAndModel() {
// Obtain the initial locator to Windows Management on a particular host computer.
IWbemLocator *locator = nullptr;
IWbemServices *services = nullptr;
auto hResult = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&locator);
auto hasFailed = [&hResult]() {
if (FAILED(hResult)) {
auto error = _com_error(hResult);
TRACE(error.ErrorMessage());
TRACE(error.Description().Detach());
return true;
}
return false;
};
auto getValue = [&hResult, &hasFailed](IWbemClassObject *classObject, LPCWSTR property) {
CString propertyValueText = "Not set";
VARIANT propertyValue;
hResult = classObject->Get(property, 0, &propertyValue, 0, 0);
if (!hasFailed()) {
if ((propertyValue.vt == VT_NULL) || (propertyValue.vt == VT_EMPTY)) {
} else if (propertyValue.vt & VT_ARRAY) {
propertyValueText = "Unknown"; //Array types not supported
} else {
propertyValueText = propertyValue.bstrVal;
}
}
VariantClear(&propertyValue);
return propertyValueText;
};
CString manufacturer = "Not set";
CString model = "Not set";
if (!hasFailed()) {
// Connect to the root\cimv2 namespace with the current user and obtain pointer pSvc to make IWbemServices calls.
hResult = locator->ConnectServer(L"ROOT\\CIMV2", nullptr, nullptr, 0, NULL, 0, 0, &services);
if (!hasFailed()) {
// Set the IWbemServices proxy so that impersonation of the user (client) occurs.
hResult = CoSetProxyBlanket(services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE);
if (!hasFailed()) {
IEnumWbemClassObject* classObjectEnumerator = nullptr;
hResult = services->ExecQuery(L"WQL", L"SELECT * FROM Win32_ComputerSystem", WBEM_FLAG_FORWARD_ONLY |
WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, &classObjectEnumerator);
if (!hasFailed()) {
IWbemClassObject *classObject;
ULONG uReturn = 0;
hResult = classObjectEnumerator->Next(WBEM_INFINITE, 1, &classObject, &uReturn);
if (uReturn != 0) {
manufacturer = getValue(classObject, (LPCWSTR)L"Manufacturer");
model = getValue(classObject, (LPCWSTR)L"Model");
}
classObject->Release();
}
classObjectEnumerator->Release();
}
}
}
if (locator) {
locator->Release();
}
if (services) {
services->Release();
}
CoUninitialize();
return { manufacturer, model };
}
Here is a concrete example:
I create a IWeBrowser2 interface by calling wb.CoCreateInstance(CLSID_InternetExplorer, 0, CLSCTX_SERVER);. This gives me a marshaled interface from my process into whichever of the running iexplore.exe processes happens to contain this browser tab in my thread A.
Now I use the IGlobalInterfaceTable to get a cookie for this interface, pass it to my thread B and request the marshaled interface from there.
Question: Do I get a proxy to the proxy in my thread A or directly to the instance in the IE process?
It seems sensible to me that I will get a direct proxy to the instance with its own reference to it, however:
If I end my thread A, the cookie I created there becomes invalid and I can't retrieve (and close) the interface pointers to the web browsers I created any more. This does not make sense unless there is a thunk in that thread that is destroyed when the thread quits.
Edit: Oh, both threads are STA.
I finally had some time to figure out what is happening, so I wrote a short test to see what is going on.
// MarshalTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
enum { WM_THEREYOUGO = WM_USER+1, WM_THANKYOU, WM_YOURWELCOME };
DWORD WINAPI TheOtherThread(DWORD * main_thread_id)
{
MSG msg = { 0 };
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
assert(SUCCEEDED(hr));
{
// create web browser
CComPtr<IWebBrowser2> wb;
hr = wb.CoCreateInstance(CLSID_InternetExplorer, 0, CLSCTX_SERVER);
assert(SUCCEEDED(hr) && wb);
// navigate
hr = wb->Navigate2(&CComVariant(_T("stackoverflow.com")), &CComVariant(0), &CComVariant(_T("")), &CComVariant(), &CComVariant());
assert(SUCCEEDED(hr));
hr = wb->put_Visible(VARIANT_TRUE);
assert(SUCCEEDED(hr));
// Marshal
DWORD the_cookie = 0;
{
CComPtr<IGlobalInterfaceTable> com_broker;
hr = com_broker.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
assert(SUCCEEDED(hr));
hr = com_broker->RegisterInterfaceInGlobal(wb, __uuidof(IWebBrowser2), &the_cookie);
}
// notify main thread
PostThreadMessage(*main_thread_id, WM_THEREYOUGO, the_cookie, NULL);
// message loop
while(GetMessage(&msg, 0, 0, 0)) {
if(msg.hwnd == NULL) {
// thread message
switch(msg.message) {
case WM_THANKYOU:
PostQuitMessage(0);
break;
}
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
CoUninitialize();
PostThreadMessage(*main_thread_id, WM_YOURWELCOME, 0, NULL);
return msg.wParam;
}
int _tmain(int argc, _TCHAR* argv[])
{
MSG msg = {0};
DWORD main_thread_id = GetCurrentThreadId();
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
assert(SUCCEEDED(hr));
{
DWORD ThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)TheOtherThread, &main_thread_id, 0, &ThreadId);
DWORD the_cookie = 0;
CComPtr<IWebBrowser2> wb, wb2;
while(GetMessage(&msg, 0, 0, 0)) {
if(msg.hwnd == NULL) {
// thread message
switch(msg.message) {
case WM_THEREYOUGO:
// we got the cookie.
the_cookie = msg.wParam;
// get the browser. This should work.
{
CComPtr<IGlobalInterfaceTable> com_broker;
hr = com_broker.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
assert(SUCCEEDED(hr));
hr = com_broker->GetInterfaceFromGlobal(the_cookie, __uuidof(IWebBrowser2), (void**)&wb);
assert(SUCCEEDED(hr) && wb);
}
// do something with it.
hr = wb->put_FullScreen(VARIANT_TRUE);
assert(SUCCEEDED(hr));
// signal the other thread.
PostThreadMessage(ThreadId, WM_THANKYOU, 0, NULL);
break;
case WM_YOURWELCOME:
// the other thread has ended.
PostQuitMessage(0);
break;
}
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// the other thread has ended. Try getting the interface again.
{
CComPtr<IGlobalInterfaceTable> com_broker;
hr = com_broker.CoCreateInstance(CLSID_StdGlobalInterfaceTable);
assert(SUCCEEDED(hr));
hr = com_broker->GetInterfaceFromGlobal(the_cookie, __uuidof(IWebBrowser2), (void**)&wb2);
//assert(SUCCEEDED(hr) && wb2); // this fails, hr == E_INVALIDARG.
// clean up, will not be executed.
if(SUCCEEDED(hr)) {
hr = com_broker->RevokeInterfaceFromGlobal(the_cookie);
}
}
// try using it
if(wb2) {
hr = wb2->put_FullScreen(VARIANT_FALSE);
assert(SUCCEEDED(hr));
} else if(wb) {
// this succeeds
hr = wb->put_FullScreen(VARIANT_FALSE);
assert(SUCCEEDED(hr));
}
CloseHandle(hThread);
}
CoUninitialize();
return msg.wParam;
}
The bottom line is this:
Ending the thread that registered the interface invalidates the cookie.
The already marshaled interface stays valid. (In this case, that is.)
This means that I get a proxy to the IE process instead of to the other thread's object.
You already got a proxy on thread A since you asked for an out-of-process server. What happens next depends on the kind of apartment that thread A lives in, the argument to CoInitializeEx(). If it is MTA you will definitely get the same proxy in thread B, assuming it is MTA as well. The added reference count should keep it alive if Thread A exits. If it is STA then I'm not 100% sure but think you ought to get a new one. Easy to test btw, just use the one from thread A and you'll get RPC_E_WRONGTHREAD if a new one would have to be created.
I don't have a great explanation for why the thread A exit kills the proxy for thread B. Unless you call IGlobalInterfaceTable::RevokeInterfaceFromGlobal(). Which you'd normally do.