Glenn Linderman wrote:
At a different level of detail related to this example, I guess I'm
reading that ListView_GetItem is "just" a wrapper macro around
SendMessage, filling in the SendMessage parameters that are appropriate
for the LVM_GETITEM message. MSDN seems to confirm this, pointing out
that SendMessage could be called directly instead.
Yes. All these are defined in commctrl.h file. For example ListView_GetItem
looks like this (in VC++ 6.0):
#define ListView_GetItem(hwnd, pitem) \
(BOOL)SNDMSG((hwnd), LVM_GETITEM, 0, (LPARAM)(LV_ITEM FAR*)(pitem))
This particular call seems to have a structure that contains a pointer
to a buffer of 1024 bytes. If I understand correctly, that means that
two buffers must be allocated in the remote process: 1) the 1024 byte
buffer 2) the LV_ITEM structure.
Exactly
I don't know the expense of doing the allocation, or the exact
capabilities of the injected DLL or how one communicates with it, but it
would seem that messaging would be the expensive part, so that it would
be best to bundle a number of operations together when possible. Most
messaging would consist of 3 sub-steps: i) allocating and initializing
remote memory ii) SendMessage (or a wrapper macro or function) iii)
reading and discarding remote memory. I don't know if this requires 1
or 3 interactions with the injected DLL. I'll list it as 3
interactions, but if it is possible to make it one, that would likely
have better performance.
I don't know all the actuall consequences of both methods (remote memory
and DLL injection). I prefer remote/virtual memory thing, because as for me,
it is easier to comprehend.
I wouldn't worry that much about the perfomance. My experience from gui test
automation is that in most cases tested controls have a significant latency
(because they need time to be created, to refresh themselves and so on), so in
most cases you have to delay your testing code, so the tested application can
catch up.
The envisioned design "somehow" passes a request buffer to the injected
DLL in the other process. The request buffer would encode the following
operations to be performed by a small interpreter on the remote side.
i) allocating and initializing memory
A) allocate 1024 bytes for remote_buffer_1
B) allocate sizeof(LV_ITEM) bytes for remote_buffer_2
C) fill remote_buffer_2 offset 0 with sizeof(LV_ITEM) bytes included in
request... the request would include the max data and other structure
elements following.
D) set remote_buffer_2 offsetof(lv_item.pszText) to address of
remote_buffer_1
ii) calling ListView_GetItem
- if this must be done from the controlling process, then the buffer
addresses, at least for remote_buffer_2 in this case, must be returned
to the controlling process, so that it can call ListView_GetItem with
the remote handle, and the remote_buffer_2 address.
- alternately, if this can be done in the controlled process, by the
injected DLL, the request buffer should contain further information, and
probably it would be easiest to bypass ListView_GetItem and work
directly with SendMessage parameters... For this case, we add more
items to our request buffer:
E) set SendMessage parameters from buffer (this is a fixed number of
bytes, I didn't look it up). For this message, the LVM_GETITEM would be
the message code, wParam would be 0, and lParam could start as 0, but
see the next step
F) Set LPARAM to the adress of remote_buffer_2
G) Call SendMessage using the current parameters
H) Copy SendMessage status into return buffer
iii) reading buffers would be another, or a continued set of operations
in a request buffer
I) copy remote_buffer_1 into return buffer
J) copy remote_buffer_2 into return buffer (probably don't need it for
this operation, maybe would for others?)
K) deallocate remote_buffer_1
L) deallocate remote_buffer_2
If all the operations, A-L could be placed in a single request buffer,
and a single return buffer contained all the results, this would seem to
minimize the interactions with the remote process, which should give
reasonable performance. And a selection of operations similar to those
outlined for this message would seem to support most messages, whether
known in advance or not.
Now the ones directly supported by Win32::GUI would have the type
checking, marshalling, etc. all done implicitly. Those not directly
supported by Win32::GUI would have to be defined, in terms of the
operations made available in the request buffers, and type checking
would be less stringent at that level of implementation, but the typical
technique would be to code an message wrapper function that could do all
the dirty work, in terms of the available operations and subfunctions
that place operation parameters into the request buffer, and it
shouldn't be too complex. And if the technique reduces the number of
cross-process interactions, it should be reasonably fast.
Well, OK, so is this a pipe dream? Far-fetched? Far from the current
reality of Win32::GuiTest? Totally inappropriate? Let the discussions
begin!
It seams that you have a pretty complete and consistent vision on how to do it.
IMHO, it would be beneficial if you convert it to the code. It would have 2
advantages:
1. You yourself would be able to verify if your concept is consistent
2. Others would be able to bettern understand your idea.
Just one note. Yout talk about DLL injection and allocation of memory in remote
process together, which I think maybe confusing for some people. So let me make
it clear. DLL injection is one thing and allocation of memory in other process
address space is another. You can use either former or latter of them. You don't
have to use both methods together (you can, but you don't have to).
--
Piotr Kaluski
"It is the commitment of the individuals to excellence,
their mastery of the tools of their crafts, and their
ability to work together that makes the product, not rules."
("Testing Computer Software" by Cem Kaner, Jack Falk, Hung Quoc Nguyen)