ATL COM - EnumWindows callback function not working with IDispatch::Invoke - c++

I am trying to implement a wrapper function for EnumWindows API function to be included in my wrapper dll, so it can be used with many scripting languages.
First of all, I had to test it using VBScript. I implemented a wrapper function and a callback function for EnumWindows with the help of my useful researches, but it is not working as I want.
Here is the code I currently have:
Wrapper functions for EnumWindows and EnumWindowsProc callback:
BOOL CALLBACK EnumWindowsProc(__in HWND hWnd, __in LPARAM lParam) {
LPENUMWINDOWSPARAMS pewParams;
VARIANT vhWnd, vResult; HRESULT HR = S_OK;
VariantInit(&vhWnd);
VariantInit(&vResult);
vhWnd.vt = VT_I4;
vhWnd.lVal = (LONG)(LONG_PTR)hWnd;
pewParams = reinterpret_cast<LPENUMWINDOWSPARAMS>(lParam);
// ^ PASSING MY PARAMETERS THROUGH LPARAM
CComVariant varArgs[2] = { &vResult, &vhWnd };
DISPPARAMS Parameters = { &varArgs[0], NULL, 2, 0 };
pewParams->DISPATCH->Invoke(0, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &Parameters, &vResult, NULL, NULL);
// ^ I SUSPECT SOMETHING IS WRONG WITH THIS INVOKE METHOD. BUT IT RETURNS S_OK.
if (vResult.vt != VT_BOOL) { HR = DISP_E_TYPEMISMATCH; }
pewParams->CallbackResult.vt = VT_ERROR;
pewParams->CallbackResult.scode = HR;
if (HR == S_OK)
return (vResult.boolVal == VARIANT_TRUE ? TRUE : FALSE);
else
return FALSE;
}
STDMETHODIMP CWinAPI::WinAPI_EnumWindows(VARIANT EnumFunc, int lParam, int *Result) {
ENUMWINDOWSPARAMS ewParams; HRESULT HR = S_OK;
switch (EnumFunc.vt)
{
case VT_DISPATCH:
ewParams.DISPATCH = EnumFunc.pdispVal;
break;
case VT_VARIANT | VT_BYREF:
if (EnumFunc.pvarVal->vt == VT_DISPATCH) { ewParams.DISPATCH = EnumFunc.pvarVal->pdispVal; }
break;
default: return DISP_E_TYPEMISMATCH;
}
ewParams.lParam = reinterpret_cast<LPVARIANT>(&lParam);
*Result = (int)EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&ewParams));
HR = ewParams.CallbackResult.scode;
return HR;
}
ENUMWINDOWSPARAMS structure:
typedef struct tagENUMWINDOWSPARAMS {
LPDISPATCH DISPATCH;
LPVARIANT lParam;
VARIANT CallbackResult;
} ENUMWINDOWSPARAMS, *PENUMWINDOWSPARAMS, *LPENUMWINDOWSPARAMS;
Here is the testing VBScript I am currently using:
Dim WINAPI: Set WINAPI = WScript.CreateObject("WinAPIWrapperLib.WINAPI")
Function EnumWindowsProc(HWND, lParam)
WScript.Echo "Handle to the window: 0x" + CStr(UCase(Hex(HWND)))
EnumWindowsProc = True
End Function
Dim Result: Result = WINAPI.WinAPI_EnumWindows(GetRef("EnumWindowsProc"), 0)
WScript.Echo "EnumWindows returned " + CStr(Result)
One thing works, EnumWindows is returning True.
But, instead what I expect, I am not getting handles of windows echoed.
And when I call GetLastError, it returns ERROR_SUCCESS.
Thanks in advance for your kind help.

I fixed the problem, nothing special done, it's only a small change:
Changed varArgs's type from CComVariant to VARIANT like this:
VARIANT varArgs[2] = { vResult, vhWnd };
Changed DISPPARAMS as below:
DISPPARAMS Parameters = {};
Parameters.cArgs = 2;
Parameters.rgvarg = varArgs;
Invoke method is still the same:
pewParams->DISPATCH->Invoke(0, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &Parameters, &vResult, NULL, NULL);
Any good suggestions are further appreciated.

Related

How can DOM elements be retrieved from a C++ program?

I would need to get all elements of webpage displayed in IE from a c++ program.
I tried to see with spy++ but there's only the IEFrame.
So I'm thinking about using the developement tool (F12 in IE), I heard there's a way to automat it, a good idea ?
Thanks
You can get an IHtmlDocument2 reference from an IE's window handle, even out-of-process. This is documented here https://support.microsoft.com/en-us/help/249232/how-to-get-ihtmldocument2-from-a-hwnd, but not really supported by Microsoft.
However it looks like it still works today, I've tested it with a Windows 10 box, and IE is now a frozen app, so not going to change any time soon.
Once you have the proper HWND for Internet Explorer, than you can get the DOM with a code like this. Make sure IE and your program run at the same security level
The DOM is the same as when you're coding IE inprocess (host, activex, etc.), however for security reasons, some things may not work :
void DoSomeDomOperations(HWND hwnd)
{
UINT msg = RegisterWindowMessage(L"WM_HTML_GETOBJECT");
LRESULT result = 0;
SendMessageTimeout(hwnd, msg, NULL, NULL, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&result);
if (!result)
return;
// get main document object
IHTMLDocument2 *doc = NULL;
ObjectFromLresult(result, IID_IHTMLDocument2, NULL, (void**)&doc);
if (!doc)
return;
// get document's url
BSTR url = NULL;
doc->get_URL(&url);
wprintf(L"url:%s\n", url);
SysFreeString(url);
// get body element
IHTMLElement *element = NULL;
doc->get_body(&element);
if (element)
{
BSTR text = NULL;
element->get_innerText(&text);
wprintf(L"text:%s\n", text);
SysFreeString(text);
element->Release();
}
// etc.
// etc.
doc->Release();
}
And here is a full sample console app that scans all current IE processes running:
BOOL CALLBACK GetIEServerWindowProc(HWND hwnd, LPARAM lParam)
{
// enumerate all child windows to find IE's COM server
wchar_t className[100];
GetClassName(hwnd, className, 100);
if (!wcscmp(className, L"Internet Explorer_Server"))
{
*((HWND*)lParam) = hwnd;
return FALSE;
}
return TRUE;
}
HWND GetIEServerWindow(HWND hwnd)
{
HWND serverHwnd = NULL;
EnumChildWindows(hwnd, GetIEServerWindowProc, (LPARAM)&serverHwnd);
return serverHwnd;
}
struct IEServer
{
DWORD processId;
HWND serverHwnd;
};
BOOL CALLBACK GetIEProcessServerWindowProc(HWND hwnd, LPARAM lParam)
{
DWORD processId = ((IEServer*)lParam)->processId;
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (pid == processId)
{
HWND serverHwnd = GetIEServerWindow(hwnd);
if (serverHwnd)
{
((IEServer*)lParam)->serverHwnd = serverHwnd;
return FALSE;
}
}
return TRUE;
}
HWND GetIEProcessServerWindow(DWORD processId)
{
IEServer ie = { processId, NULL };
EnumWindows(GetIEProcessServerWindowProc, (LPARAM)&ie);
return ie.serverHwnd;
}
void EnumerateIEProcesses()
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (h == INVALID_HANDLE_VALUE)
return;
PROCESSENTRY32 process;
process.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(h, &process))
{
do
{
// we only consider IE processes
if (!wcscmp(process.szExeFile, L"iexplore.exe"))
{
HWND serverHwnd = GetIEProcessServerWindow(process.th32ProcessID);
if (serverHwnd)
{
DoSomeDomOperations(serverHwnd);
}
}
} while (Process32Next(h, &process));
}
CloseHandle(h);
}
int main()
{
CoInitialize(NULL);
EnumerateIEProcesses();
CoUninitialize();
return 0;
}

Drag and drop from my app's ListView to external apps (such as Windows Explorer)

I have a a ListView containing a list of files:
hList = CreateWindowEx(0, WC_LISTVIEW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 500, 400, hWnd, (HMENU)ID_LISTVIEW, hInst, NULL);
Let's say it contains a row c:\temp\hello.txt.
How to enable drag and drop of this file from my application's ListView to external applications (such as Windows Explorer) as "Copy"?
The GUI part of the question might be obvious (or not?) via:
case WM_NOTIFY:
{
...
case LVN_BEGINDRAG:
But here this question is about the actual sending of the file to external applications, such as Windows Explorer. How to do this?
Implement IDropSource, IDropSourceNotify (optional) and IDataObject and call DoDragDrop:
If you are developing an application that can act as a data source for an OLE drag-and-drop operation, you must call DoDragDrop when you detect that the user has started an OLE drag-and-drop operation.
The DoDragDrop function enters a loop in which it calls various methods in the IDropSource and IDropTarget interfaces. (For a successful drag-and-drop operation, the application acting as the data source must also implement IDropSource, while the target application must implement IDropTarget.)
SHCreateDataObject can provide a IDataObject instance for you but you often end up having to code your own because the shell provided implementation is not perfect.
IDragSourceHelper can help you to get a fancy drag image.
See also:
Dragging a shell object, part 1: Getting the IDataObject
What a drag: Dragging a virtual file (HGLOBAL edition)
What a drag: Dragging a virtual file (IStream edition)
What a drag: Dragging a virtual file (IStorage edition)
Here is some code that implements all that is required to perform such a ListView file drag&drop. First some includes:
#define CINTERFACE
#define COBJMACROS
#include "ShObjIdl.h"
#include "ShlObj.h"
#include "oleidl.h"
Then this in the WinMain function, to initialize OLE operations.
OleInitialize(NULL);
InitCommonControls();
Then, the IDropSource part:
typedef struct __DSV_TDropSource {
IDropSource This;
IDropSourceVtbl Func;
ULONG RefCnt;
} __DSV_TDropSource;
HRESULT WINAPI __DSV_QueryInterface(IDropSource *This, REFIID riid, void **ppvObject)
{
IUnknown *punk = NULL;
if (riid == IID_IUnknown)
{
punk = (IUnknown*)This;
}
else if (riid == IID_IDropSource)
{
punk = (IUnknown*)This;
}
*ppvObject = punk;
if (punk)
{
IUnknown_AddRef(punk);
return S_OK;
}
else {
return E_NOINTERFACE;
}
}
ULONG WINAPI __DSV_AddRef(IDropSource *This)
{
__DSV_TDropSource *pThis = (__DSV_TDropSource*)This;
return pThis->RefCnt++;
}
ULONG WINAPI __DSV_Release(IDropSource *This)
{
__DSV_TDropSource *pThis = (__DSV_TDropSource*)This;
LONG iRes = (LONG)pThis->RefCnt - 1;
if (iRes < 1) { iRes = 0; }
pThis->RefCnt = iRes;
if (iRes == 0) { free(pThis); }
return iRes;
}
HRESULT WINAPI __DSV_QueryContinueDrag(IDropSource *This, BOOL fEscapePressed, DWORD grfKeyState)
{
if (fEscapePressed) { return DRAGDROP_S_CANCEL; }
if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON))) { return DRAGDROP_S_DROP; }
return S_OK;
}
HRESULT WINAPI __DSV_GiveFeedback(IDropSource *This, DWORD dwEffect)
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}
IDropSource* CreateDropSource()
{
__DSV_TDropSource *pResu = (__DSV_TDropSource*)malloc(sizeof(__DSV_TDropSource));
if (!pResu) { return 0; }
pResu->This.lpVtbl = &(pResu->Func);
pResu->Func.QueryInterface = __DSV_QueryInterface;
pResu->Func.AddRef = __DSV_AddRef;
pResu->Func.Release = __DSV_Release;
pResu->Func.QueryContinueDrag = __DSV_QueryContinueDrag;
pResu->Func.GiveFeedback = __DSV_GiveFeedback;
pResu->RefCnt = 1;
return (IDropSource*)pResu;
}
void** GetFileUiObject(TCHAR *ptFile, REFIID riid)
{
void** pInterfaceResu = 0;
IShellFolder *pFolder;
PIDLIST_RELATIVE pFile;
PIDLIST_ABSOLUTE pITEMDLIST_File;
HRESULT iResu;
pITEMDLIST_File = ILCreateFromPath(ptFile);
if (!pITEMDLIST_File)
return 0;
iResu = SHBindToParent(pITEMDLIST_File, IID_IShellFolder, (void**)&pFolder, (PCUITEMID_CHILD*)&pFile);
if (iResu != S_OK)
return 0;
const ITEMIDLIST* pArray[1] = { pFile };
iResu = IShellFolder_GetUIObjectOf(pFolder, NULL, 1, pArray, riid, NULL, (void**)&pInterfaceResu);
if (iResu != S_OK)
return 0;
IShellFolder_Release(pFolder);
return pInterfaceResu;
}
Lastly, this should be performed in the message loop:
case WM_NOTIFY:
pdi = (NMLVDISPINFO*) lParam;
nmlv = (NMLISTVIEW*) lParam;
switch (pdi->hdr.code)
{
case LVN_BEGINDRAG:
wstring fName = L"C:\\test.txt";
IDataObject *pObj;
IDropSource *pSrc;
pObj = (IDataObject*)GetFileUiObject(LPWSTR(fName.c_str()), IID_IDataObject);
if (!pObj)
break;
pSrc = CreateDropSource();
if (!pSrc)
{
IDataObject_Release(pObj);
break;
}
DWORD dwEffect;
DoDragDrop(pObj, pSrc, DROPEFFECT_COPY | DROPEFFECT_LINK, &dwEffect);
IDropSource_Release(pSrc);
IDataObject_Release(pObj);
break;

Return multiple values from a function in COM Interfaces

I did a research and implemented a wrapper function for GetWindowRect API, and it works fine. But it can currently only return window coordinates via a SAFEARRAY. But I am thinking about how can I also return its actual return value.
I declared it like this:
[id(10)] HRESULT WinAPI_GetWindowRect([in] VARIANT hWnd, [out, retval] SAFEARRAY(VARIANT) *lpRect);
STDMETHOD(WinAPI_GetWindowRect)(VARIANT hWnd, SAFEARRAY **lpRect);
And implementation:
STDMETHODIMP CWinAPI::WinAPI_GetWindowRect(VARIANT hWnd, SAFEARRAY **lpRect {
CComSafeArray<VARIANT> CCSA_RECT;
RECT Rect;
HRESULT HResult;
if (!lpRect) { return E_INVALIDARG; }
*lpRect = nullptr;
int Result = (int)GetWindowRect(VariantToHWND(hWnd), &Rect);
// ^ I ALSO WANT TO RETURN THIS RESULT TOO
if (Result != 0)
{
HResult = CCSA_RECT.Create(4, 0);
assert(HResult == S_OK);
CCSA_RECT[0] = Rect.left;
CCSA_RECT[1] = Rect.top;
...
}
else
{ ... }
}
But, as I already know, there cannot be multiple [out, retval] values, so any suggestions on how to return this value (Result) are highly appreciated.
UPDATE
When I try to return it like this:
[id(10)] HRESULT WinAPI_GetWindowRect([in] VARIANT hWnd, [out] SAFEARRAY(VARIANT) *lpRect, [out, retval] VARIANT *Result);
STDMETHOD(WinAPI_GetWindowRect)(VARIANT hWnd, SAFEARRAY **lpRect, VARIANT *Result);
Assigning it like:
Result->intVal = (int)GetWindowRect(VariantToHWND(hWnd), &Rect);
I am getting a Type mismatch error in below VBScript line when try to use it:
Dim lpRect, Result: Result = WINAPI.WinAPI_GetWindowRect(AutoItX3.WinGetHandle("[CLASS:ConsoleWindowClass]"), lpRect)
I want this SAFEARRAY to be [out] parameter and Result to be return value.
I solved the problem to a some extent by using a VARIANT as [out] parameter like this:
[id(10)] HRESULT WinAPI_GetWindowRect([in] VARIANT hWnd, [out] VARIANT *Result, [out, retval] SAFEARRAY(VARIANT) *lpRect);
STDMETHOD(WinAPI_GetWindowRect)(VARIANT hWnd, VARIANT *Result, SAFEARRAY **lpRect);
New implementation:
STDMETHODIMP CWinAPI::WinAPI_GetWindowRect(VARIANT hWnd, VARIANT *Result, SAFEARRAY **lpRect) {
CComSafeArray<VARIANT> CCSA_RECT;
RECT Rect;
HRESULT HResult;
if (!lpRect) { return E_INVALIDARG; }
*lpRect = nullptr;
VariantInit(Result);
Result->vt = VT_I2;
Result->intVal = (int)GetWindowRect(VariantToHWND(hWnd), &Rect);
if (Result->intVal != 0)
{...}
else
{...}
}
This has been tested using the following VBScript:
Dim AutoItX3: Set AutoItX3 = WScript.CreateObject("AutoItX3.Control")
Dim WINAPI: Set WINAPI = WScript.CreateObject("WinAPIWrapperLib.WINAPI")
Dim lpRect, Result: lpRect = WINAPI.WinAPI_GetWindowRect(AutoItX3.WinGetHandle("[CLASS:ConsoleWindowClass]"), Result)
WScript.Echo "GetWindowRect returned: " + CStr(Result)
If CInt(Result) <> 0 Then
For iNum = 0 To UBound(lpRect)
WScript.Echo CStr(lpRect(iNum))
Next
End If
WScript.Echo(CStr(WINAPI.WinAPI_GetLastErrorMessage))
But, I still cannot use SAFEARRAY as an [out] only parameter, using it as [out] only parameter and using Result as [out, retval] member in following way:
[id(10)] HRESULT WinAPI_GetWindowRect([in] VARIANT hWnd, [out] SAFEARRAY(VARIANT) *lpRect, [out, retval] VARIANT *Result);
still causes the Type mismatch error.

Handle of DC C++ Windows

I want to get the pixels of every physical screen individually, but MSDN says GetDC needs a window to know which screen to get the handle of. Is there any way of telling it which handle to use without using a window or mouse?
So I can call EnumDisplayDevices and as suggested loop through to find how many monitors are attached and the information of each: Problem is I am using the code provided by Microsoft:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd144942(v=vs.85).aspx
Using this code it has errors on the EnumDisplayDevices call "Too many arguments in function call"
BOOL GetDisplayMonitorInfo(int nDeviceIndex, LPSTR lpszMonitorInfo)
{
FARPROC EnumDisplayDevices;
HINSTANCE hInstUser32;
DISPLAY_DEVICE DispDev;
char szSaveDeviceName[33];
BOOL bRet = TRUE;
HRESULT hr;
hInstUser32 = LoadLibrary("c:\\windows\User32.DLL");
if (!hInstUser32) return FALSE;
EnumDisplayDevices = (FARPROC)GetProcAddress(hInstUser32, "EnumDisplayDevicesA");
if (!EnumDisplayDevices) {
FreeLibrary(hInstUser32);
return FALSE;
}
ZeroMemory(&DispDev, sizeof(DispDev));
DispDev.cb = sizeof(DispDev);
if (EnumDisplayDevices(NULL, nDeviceIndex, &DispDev, 0))
{
hr = StringCchCopy(szSaveDeviceName, 33, DispDev.DeviceName);
if (FAILED(hr))
{
}
EnumDisplayDevices(szSaveDeviceName, 0, &DispDev, 0);
hr = StringCchCopy(lpszMonitorInfo, 129, DispDev.DeviceString);
if (FAILED(hr))
{
// TODO: write error handler
}
}
else {
bRet = FALSE;
}
FreeLibrary(hInstUser32);
return bRet;
}
This was handled in KB117428 because of C to C++ code porting (i.e. that code was originally written in C).
The proposed solution is to properly handle the typedefs
typedef BOOL (WINAPI *EDDType)(LPCSTR,DWORD,PDISPLAY_DEVICEA,DWORD);
BOOL GetDisplayMonitorInfo(int nDeviceIndex, LPSTR lpszMonitorInfo)
{
EDDType EnumDisplayDevices;
HINSTANCE hInstUser32;
DISPLAY_DEVICE DispDev;
char szSaveDeviceName[33];
BOOL bRet = TRUE;
HRESULT hr;
hInstUser32 = LoadLibrary("c:\\windows\\User32.DLL"); // You forgot the double backslash
if (!hInstUser32) return FALSE;
EnumDisplayDevices = (EDDType)GetProcAddress(hInstUser32, "EnumDisplayDevicesA");

getElementsByTagName with IHTMLDocument3 randomly returns nothing

I'm trying to fill some form input fields in internet explorer from a c++ program but I'm facing a random bug that I hope is because of my code:
UINT msg = RegisterWindowMessage("WM_HTML_GETOBJECT");
LRESULT result = 0;
SendMessageTimeout(hwnd, msg, NULL, NULL, SMTO_ABORTIFHUNG, 10000, (PDWORD_PTR)&result);
if (!result)
return;
// get main document object
IHTMLDocument3 *doc = NULL;
ObjectFromLresult(result, IID_IHTMLDocument3, NULL, (void**)&doc);
if (!doc)
return;
VARIANT varint, varstr;
varint.vt = VT_I4;
varstr.vt = VT_BSTR;
IHTMLElementCollection* pElemCollections=NULL;
if (FAILED(doc->getElementsByTagName(L"input", &pElemCollections)))
return;
long nelm;
pElemCollections->get_length(&nelm);
...
At this last line and with the same HWND at the same page I sometimes get the good number or input fields and often get 0 for nelm.
Do you see something wrong in my code or is it a bug ?
Note that I verified that the HWND is correct, and the return are never called.
Thanks
I have no more problem by doing that:
UINT msg = RegisterWindowMessage("WM_HTML_GETOBJECT");
LRESULT result = 0;
SendMessageTimeout(hwnd, msg, NULL, NULL, SMTO_ABORTIFHUNG, 10000, (PDWORD_PTR)&result);
if (!result)
return;
// get main document object
IHTMLDocument3 *doc = NULL;
ObjectFromLresult(result, IID_IHTMLDocument3, NULL, (void**)&doc);
if (!doc)
return;
CComVariant varint;
CComVariant varstr;
IHTMLElementCollection* pElemCollections=NULL;
CComBSTR name(L"input")
if (FAILED(doc->getElementsByTagName(name, &pElemCollections)))
return;
long nelm;
pElemCollections->get_length(&nelm);
...