Assuming I have a class that implements the DShellWindowsEvents interface and registers it with shell windows; The OnWindowRegistered method is called whenever a new IE or Explorer window is opened with a cookie value as the first parameter. Using this cookie value, I should be able to call FindWindowSW and retrieve the window that was just registered. However, it always returns S_FALSE with no values returned.
Here is the relevant code:
HRESULT MyShellWindows::OnWindowRegistered(LONG cookie)
{
CComPtr<IDispatch> p_browser_disp;
CComVariant v_cookie(cookie), v_empty;
long window_handle = 0;
HRESULT res = m_shell->FindWindowSW(
&v_cookie,
&v_empty,
SWC_BROWSER,
&window_handle,
SWFO_COOKIEPASSED|SWFO_NEEDDISPATCH,
&p_browser_disp
);
assert(res == S_OK); // returns S_FALSE.
}
The result is always S_FALSE and neither an IDispatch pointer nor a window handle is returned. I tried several combinations for the flags and I tried Apartment and Free Threading. I even tried to save the cookie and defer the call to avoid re-entrance into the IShellWindows object. Edit: I also tried numerous combinations of variant types directly and by reference as the cookie value, IIRC all VT_I* and VT_U* types plus some more directly and by reference.
So, here is the question:
How can I get a valid result (S_OK and a window handle or an IDispatch pointer) from a FindWindowSW call from a cookie?
Please don't post if you never tried. Thanks.
Related
Recently, I am working on programming with COM object. The code is as follows.
CoInitialize(NULL);
HRESULT hr = NULL;
CLSID clsid;
LPOLESTR pProgID;
LPCOLESTR lpsz = L"{417976B7-917D-4F1E-8F14-C18FCCB0B3A8}";
hr = CLSIDFromString(lpsz, &clsid);
hr = ProgIDFromCLSID(clsid, &pProgID);
IUnknown *pUnknown;
hr = CoCreateInstance(clsid, NULL, CLSCTX_ENABLE_CLOAKING, IID_IUnknown, reinterpret_cast<void**>(&pUnknown));
CoUninitialize();
I have got a problem when I call CoCreateInstance with CLSCTX_ENABLE_CLOAKING. It fails and I've got a error "REGDB_E_CLASSNOTREG Class not registered". If anyone know solution, please inform me. Thanks in advance.
Although the current doc is not clear about these flags, when you use CoCreateInstance, you must specify the type of server used to manage the object, so COM can activate it (it's usually about inprocess vs out of process). Other flags such as CLSCTX_ENABLE_CLOAKING are optional.
The constants can be CLSCTX_INPROC_SERVER, CLSCTX_INPROC_HANDLER,
CLSCTX_LOCAL_SERVER, CLSCTX_REMOTE_SERVER or any combination of these
values. The constant CLSCTX_ALL is defined as the combination of all
four.
CLSCTX_ALL means "I don't care whether it's inprocess, out-of-process, remote, etc.", but activate it.
I have implemented the creation of a shortcut as defined in this article on Shell Links by MSDN
MSDN Shell Links
Here is what I implemented
CreateLink - Uses the Shell's IShellLink and IPersistFile interfaces
to create and store a shortcut to the specified object.
Returns the result of calling the member functions of the interfaces.
Parameters:
lpszPathObj - Address of a buffer that contains the path of the object,
including the file name.
lpszPathLink - Address of a buffer that contains the path where the
Shell link is to be stored, including the file name.
lpszDesc - Address of a buffer that contains a description of the
Shell link, stored in the Comment field of the link
properties.
HRESULT CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc)
{
HRESULT hres;
IShellLink* psl;
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
// has already been called.
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres))
{
IPersistFile* ppf;
// Set the path to the shortcut target and add the description.
psl->SetPath(lpszPathObj);
psl->SetDescription(lpszDesc);
// Query IShellLink for the IPersistFile interface, used for saving the
// shortcut in persistent storage.
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres))
{
WCHAR wsz[MAX_PATH];
// Ensure that the string is Unicode.
MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH);
// Add code here to check return value from MultiByteWideChar
// for success.
// Save the link by calling IPersistFile::Save.
hres = ppf->Save(wsz, TRUE);
ppf->Release();
}
psl->Release();
}
return hres;
I call it as:
r = CreateLink("c:\\TT\\TT.exe", "C:\\Users\\Public\\Desktop\\TT.lnk", "TT Program");
This works great on a WIN7 machine but fails on a WIN10
This is the code line that is failing:
hres = ppf->Save(wsz, TRUE);
hres == AccessDenied;
I have found other system calls that work on a Win7 but fails on Win10 and it usually comes down to some undocumented change.
Has Win10 tightened privileges on the DeskTop?
Any suggestions on addressing this on a Win10 machine?
Thanks.
I'd like to just know if there is a well-established standard way to ensure that one's process doesn't leak COM based resources (such as IMalloc'd objects)?
Take the following code as an example:
HRESULT STDMETHODCALLTYPE CShellBrowserDialog::OnStateChange(__RPC__in_opt IShellView *ppshv, ULONG uChange)
{
TRACE("ICommDlgBrowser::OnStateChange\n");
if (uChange == CDBOSC_SELCHANGE)
{
CComPtr<IDataObject> data;
if (ppshv->GetItemObject(SVGIO_SELECTION, IID_IDataObject, (void**)&data) == S_OK )
{
UINT cfFormat = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
FORMATETC fmtetc = { cfFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stgmed;
if (data->GetData(&fmtetc, &stgmed) == S_OK)
{
TCHAR path[MAX_PATH];
// check if this single selection (or multiple)
CIDA * cida = (CIDA*)stgmed.hGlobal;
if (cida->cidl == 1)
{
const ITEMIDLIST * pidlDirectory = (const ITEMIDLIST *)(((LPBYTE)cida) + cida->aoffset[0]);
const ITEMIDLIST * pidlFile = (const ITEMIDLIST *)(((LPBYTE)cida) + cida->aoffset[1]);
ITEMIDLIST * pidl = Pidl_Concatenate(pidlDirectory, pidlFile);
// we now have the absolute pidl of the currently selected filesystem object
if (!SHGetPathFromIDList(pidl, path))
strcpy_s(path, _T("<this object has no path>"));
// free our absolute pidl
Pidl_Free(pidl);
}
else if (cida->cidl > 1)
strcpy_s(path, _T("{multiple selection}"));
else
strcpy_s(path, _T("-"));
// trace the current selection
TRACE(_T(" Current Selection = %s\n"), path);
// release the data
ReleaseStgMedium(&stgmed);
}
}
}
return E_NOTIMPL;
}
So in the above code, I have at least three allocations that occur in code that I call, with only one of them being properly cleaned up automatically. The first is the acquisition of IDataObject pointer, which increments that object's reference count. CComPtr<> takes care of that issue for me.
But then there is IDataObject::GetData, which allocates an HGLOBAL for me. And a utility function Pidl_Concatenate which creates a PIDL for me (code left out, but you can imagine it does the obvious thing, via IMalloc::Alloc()). I have another utility Pidl_Free which releases that memory for me, but must be manually called [which makes the code in question full of exception safety issues (its utterly unsafe as its currently written -- I hate MS's coding mechanics - just asking for memory to fail to be released properly].
I will enhance this block of code to have a PIDL class of some sort, and probably a CIDA class as well, to ensure that they're properly deallocated even in the face of exceptions. But I would still like to know if there is a good utility or idiom for writing COM applications in C++ that can ensure that all IMallocs and AddRef/Dispose are called for that application's lifetime!
Implementing the IMallocSpy interface (see CoRegisterMallocSpy Function) may help get you some of the way.
Note that this is for debugging only, and be careful. There are cautionary tales on the web...
You can not free the global handle returned by IDataObject::GetData, otherwise other programs can not paste from the clipboard after the data is cleaned up.
Any pidl you get from shell needs to be freed using IMalloc::Free or ILFree (same effect once OLE32.DLL is loaded into the process). Exceptions are pointers to the middle of item list which can not be freed independently. If you are worried about exceptions, guard your code with try/catch/finally and put the free code in the finally block.
I'm working an application of which only one instance must exist at any given time. There are several possibilities to accomplish this:
Check running processes for one matching our EXE's name (unreliable)
Find the main window (unreliable, and I don't always have a main window)
Create a mutex with a unique name (GUID)
The mutex option seems to me the most reliable and elegant.
However, before my second instance terminates, I want to post a message to the already running instance. For this, I need a handle to the thread (or the process) that owns the mutex.
However, there seems to be no API function to get the creator/owner of a given mutex. Am I just overlooking it? Is there another way to get to this thread/process? Is there another way to go about this?
Update: This guy simply broadcast a message to all running processes. I guess that's possible, but I don't really like it...
This should get you started on the original request to get a process that owns a mutex.
It's in C#, but the Win32 calls are the same.
class HandleInfo
{
[DllImport("ntdll.dll", CharSet = CharSet.Auto)]
public static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr VirtualAlloc(IntPtr address, uint numBytes, uint commitOrReserve, uint pageProtectionMode);
[DllImport("kernel32.dll", SetLastError=true)]
internal static extern bool VirtualFree(IntPtr address, uint numBytes, uint pageFreeMode);
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_HANDLE_INFORMATION
{
public int ProcessId;
public byte ObjectTypeNumber;
public byte Flags; // 1 = PROTECT_FROM_CLOSE, 2 = INHERIT
public short Handle;
public int Object;
public int GrantedAccess;
}
static uint MEM_COMMIT = 0x1000;
static uint PAGE_READWRITE = 0x04;
static uint MEM_DECOMMIT = 0x4000;
static int SystemHandleInformation = 16;
static uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
public HandleInfo()
{
IntPtr memptr = VirtualAlloc(IntPtr.Zero, 100, MEM_COMMIT, PAGE_READWRITE);
int returnLength = 0;
bool success = false;
uint result = NtQuerySystemInformation(SystemHandleInformation, memptr, 100, out returnLength);
if (result == STATUS_INFO_LENGTH_MISMATCH)
{
success = VirtualFree(memptr, 0, MEM_DECOMMIT);
memptr = VirtualAlloc(IntPtr.Zero, (uint)(returnLength + 256), MEM_COMMIT, PAGE_READWRITE);
result = NtQuerySystemInformation(SystemHandleInformation, memptr, returnLength, out returnLength);
}
int handleCount = Marshal.ReadInt32(memptr);
SYSTEM_HANDLE_INFORMATION[] returnHandles = new SYSTEM_HANDLE_INFORMATION[handleCount];
using (StreamWriter sw = new StreamWriter(#"C:\NtQueryDbg.txt"))
{
sw.WriteLine("# Offset\tProcess Id\tHandle Id\tHandleType");
for (int i = 0; i < handleCount; i++)
{
SYSTEM_HANDLE_INFORMATION thisHandle = (SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(
new IntPtr(memptr.ToInt32() + 4 + i * Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))),
typeof(SYSTEM_HANDLE_INFORMATION));
sw.WriteLine("{0}\t{1}\t{2}\t{3}", i.ToString(), thisHandle.ProcessId.ToString(), thisHandle.Handle.ToString(), thisHandle.ObjectTypeNumber.ToString());
}
}
success = VirtualFree(memptr, 0, MEM_DECOMMIT);
}
}
I don't think there is a trivial way to resolve the actual owner of a Mutex, but the process that owns it can create other secondary items whose lifetimes are tied to it. There are plenty of mechanisms that are suitable for calling back across-process without having a main window.
Register an object in the COM Running Object Table. Clients that are unable to take ownership of the Mutex can lookup the owner via the ROT and call back to the owner. A File Moniker should be suitable for registration here.
Create a chunk of shared memory containing location details for the owner process. From there, write into the buffer the process handle and thread handle of a thread that can receive windows messages, and then use PostThreadMessage() to send a notification. Any other competing process may open the shared memory for read-only to determine where to send a windows message.
Listen in the owner process on a Socket or Named Pipe. Probably overkill and not a good match for your needs.
Use a shared file with locking. I'm not fond of this because the owner will need to poll, and it won't gracefully handle N potential other processes that could be trying to contact the owner at the same time.
Here are reference links for the first two options.
IRunningObjectTable # MSDN ,
File Monikers # MSDN
Creating Named Shared Memory # MSDN
I have never really understood the rational behind using a Mutex which has no signaling capability. I would instead create an event (using CreateEvent) which has the same properties as creating a mutex (i.e. with a name it can return that the object already existed) but you can set the event flag in the new process, as long as the original process is waiting on the event flag it can be notified when it needs to wake itself up.
You could always do it the UNIX way and create a "pid" file, putting the process id of the currently running instance into that file. Then have the app delete the file when it exits.
When a new instance starts up it should verify that the process in the PID file is actually alive as well (in case the app exits abnormally and the file doesn't get deleted)
Create a shared memory area with the fixed name:
http://msdn.microsoft.com/en-us/library/aa366551%28VS.85%29.aspx
Then you can put any structure you like inside, including process id, HWND etc.
There's a portable option: create a socket on a port (with a fixed number) and wait (accept) on it. The second instance of the app will fail since the port is already taken. Then the second instance can connect to the socket of the primary instance and send any information desired.
I hope this helps...
I had similar problems. I am want a function that returns if a single instance of an app is running. Then another function to bring the app to the front. In which I must first deduce the HWND of the already running window.
FindWindow sucks big time. Window titles can change, another window could be using the same class and title, etc.
Then I thought maybe extra data could be stored with a mutex. But I dont see where user data can be stored in a mutex object or event object. But a mutex knows which thread it belongs to and thus which process it belongs to. But as you said, the api doesnt seem to exist.
Many new and complicated looking methods have been suggested here; with the exeception of simply using a file. So I want to add another method, temporary registry keys.
This method is easiest for me as I already built an hkey library. But the win32 registry api is pretty straight forward compared to the horrifying looking shared memory method.
I'm having an issue with using SHGetFileInfo on the public desktop and files in the public desktop. I'll focus on the actual desktop folder (CSIDL_COMMON_DESKTOPDIRECTORY or usually "C:\Users\Public\Desktop"). Also - I've started seeing this behavior only recently, but I can't pinpoint the exact change which made it faulty. The actual call to ShGetFileInfo has not changed (as far as I can tell).
So I have this (I've omitted intermediate error checks to shorten - the function calls return with success):
SHFILEINFOW info;
uint32_t return_value = 0;
uint32_t flags = SHGFI_TYPENAME|SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SYSICONINDEX;
uint32_t attributes = FILE_ATTRIBUTE_NORMAL;
wchar_t *path = L"C:\\Users\\Public\\Desktop";
return_value = SHGetFileInfoW(path, attributes, &info, sizeof(SHFILEINFOW), flags);
printf("[%ls] %u ", path, return_value);
This returns 0 as the return value. If I populate path using:
SHGetFolderPathW(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0, path)
I get the same result. But if I use the pidl, as in:
LPITEMIDLIST pidl = NULL;
SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, &pidl);
return_value = SHGetFileInfoW((LPCWSTR) pidl, attributes, &info, sizeof(SHFILEINFOW), flags | SHGFI_PIDL);
Then I get something which I expect, a handle to the system small icon list.
I can't tell what I'm doing wrong - and it only happens on this specific folder. I actually need icons for the items inside the directory, so using the pidl doesn't seem to be an option right now. Any ideas on what is the expected way to retrieve icons from the common desktop?
--
I should mention this behavior happens on Windows Vista - using the path populated by SHGetFolderPathW on XP works fine
I had the same problem. It can be fixed by calling the function CoInitialize from OLE32.DLL when the program starts.
CoInitialize(0);
return_value = SHGetFileInfoW(path, attributes, &info, sizeof(SHFILEINFOW), flags);