Link to home
Start Free TrialLog in
Avatar of jbirk
jbirk

asked on

Get data from flexgrid in another process

Please help with this perplexing problem.  I searched on past questions here on EE and it looked like the questions were always abandoned.  So I guess I'm not sure if it's possible, but you would think it would be.  I only want to read the data, not set any data.  From what I saw people were doing stuff with IUnknown pointers and AtlAxGetControl.  But these methods didn't seem to work if the control was in another process.

I can get the data by hooking the textout messages from the window, but I have to scroll around to get all the data, and that involves knowing where to click in the window and where to grab the text from...  not fun.  So I'd really like to be able to get the info directly from the control using it's GetTextArray function.

Thanks!
-Josh
Avatar of DanRollins
DanRollins
Flag of United States of America image

Knowing that it is pretty common for control designers to stash useful info in the Window structure, I used Spy++ to examine an MsFlexGrid control.  It's User Data DWORD looked suspiciously like an address.

I examined that address and 8 bytes above it, I found four bytes that match the IDispatch* that my MFC test program was using:

     HWND hWnd= m_ctlGrid.m_hWnd;
     DWORD* p= (DWORD*) ::GetWindowLong(hWnd, GWL_USERDATA);

     p++; //add 8;
     p++; //add 8;
     LPDISPATCH pDisp= (LPDISPATCH)*p;

Tracing the code, a call to m_ctlGrid.GetTextArray(n) ends up with MFC using this same address when it calls InvokeHelper which ultimately calls ::Invoke to get work done.

=-=-=-=-=-=-=-=-=-=-
Doing the above would be an undocumented technique, but that is not always forbidden (for instance, if this is an in-house tool that you are writing).

I did not try this on a Grid in another process.   But once you work out how to use this in a process-local control, you can write a System hook that will put you inside of the other process's address space and should be able to control that window like a puppet.  The technique of using a hook to access controls in another process, including a detailed example, is covered in this Question:

https://www.experts-exchange.com/questions/20309208/Reading-a-covered-minmized-window.html

Good luck!
-- Dan
hi jbirk,
Are you making progress?
-- Dan
Avatar of jbirk
jbirk

ASKER

Well, I was away for the weekend, so I'm still looking into it today.

If I go to the trouble of hooking into the other process, wouldn't I be able to just use the AtlAxGetControl function from the hook and pass the interface to my process?  That's what I'm trying first anyway, though I ran into an unrelated problem while trying to test this.

I'll get back to you after I get a full test run.

-Josh
Avatar of jbirk

ASKER

test
Avatar of jbirk

ASKER

I'm having difficulty getting the dll to grab the message sent from the controlling process.  I'm injecting the dll using a call from madshi's library madCodeHook.  It gets injected into the process space of my target app.  I tried sending a message from the initialization of the dll and it got sent ok, but for some reason I can't get it to receive any messages from the controlling app.  I tried using the SetWindowsHookEx function like you mentioned on that question you referred to, but it doesn't seem to be working.

Maybe it's because the messages are getting sent to a window launched from the process and not to the process itself?  I'm sending the message to the HWND, but the injectDll function takes a HANDLE to the controlling process not an HWND.

Anyway, here's my call to SetWindowsHookEx:
ghHook = SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)HookProcGetMsg,this->m_hInstance,0);

Also, I thought maybe it was the instance I'm sending it.  In your example you had it sending hinstDLL, but I'm not sure where that gets set.  I'm using MFC, so 'this' is an object of type CWinApp.  What value should be getting sent in this argument? (is this->m_hInstance correct?)

Keep in mind that I'm using Windows 2000 for initial coding and testing, and that I want this to work on win9x as well as NT4,2k,and xp.

Thanks for your help so far,
Josh
>>but the injectDll function takes a HANDLE to the ...
I don't know of any function named injectDll, so I can't help you there.

>>but for some reason I can't get it to receive any messages from the controlling app.

How can you be sure?  What is your methodolgy for testing?  Placing a breakpoint is not likely to work.

Working with hooks can be tricky.  The code I presented in that link is a complete system for doing so.  All I can say is that the hooking mechanism described and illustrated there *does* work if you use the exact code and follow all of the steps:  The controlling app sends a message and the hooking DLL sees it (we never actually solved that user's problem about painting to off-screen bitmaps), but the rest of the mechanism works.

Note that one tricky facet of hook developement is that occasionally, the old DLL stays stuck in memory when you try to build the new one.  I recall needing to reboot occasionally to be sure I was working with a new. modified, copy of the DLL.

>>...sending hinstDLL, but I'm not sure where that gets set.  
This value was obtained from DllMain, as shown in the code.

>>and that I want this to work on win9x as
That is exactly why you need to use a Window Hook.  A different, simpler method may be available if you know that you will only run in Win2K.  See The September 1997 Q&A Win32 column in MSJ):

http://www.microsoft.com/msj/defaultframe.asp?page=/msj/0997/c++0997.htm

(however, I'm not sure that thius will allow you to access the IDISPATCH... but it is a good resource for background info).

-- Dan
Avatar of jbirk

ASKER

>>but the injectDll function takes a HANDLE to the ...
>I don't know of any function named injectDll, so I can't help you there.
It's provided in madshi's madCodeHook library.  www.madshi.net

>>...sending hinstDLL, but I'm not sure where that gets set.  
>This value was obtained from DllMain, as shown in the code.

OK, I see it sent as a parameter:
BOOL APIENTRY DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved )

But I'm using MFC and don't have such a parameter cause I don't see DLLMain.  I'm running my code in the constructor:
CWinDllApp::CWinDllApp()

>>but for some reason I can't get it to receive any messages from the controlling app.

>How can you be sure?  What is your methodolgy for testing?  Placing a breakpoint is not likely to work.
Yes, I know a breakpoint doesn't work.  To test it I'm sending a simple message back to the controlling function, which I already know is working (since it works from the constructor).  I even tried putting the postmessage call at the beginning of the messagehandler (before the checks to see if it's a message we want), and it's never calling postmessage:
LRESULT CALLBACK HookProcGetMsg(int nCode, WPARAM wParam, LPARAM lParam)
{
MSG* pMsg= (MSG*)lParam;
::PostMessage( controllerHWND, gnMyRegisteredMsg, 1, (LPARAM)0 );


I'm calling the SetWindowsHookEx function from the constructor of the dll which is injected into the process space of the target application.  I didn't see how you were using the dll in the question you linked to.  How is the DLL getting called?

-Josh
Avatar of jbirk

ASKER

OK, for now I gave up on doing this with process communication and just did what you suggested in the constructor, and wrote the results to a file.  I had to add a dispatch driver to call InvokeHelper, which is what the flexgrid calls for all of it's functions.  Anyway, here's the code I use for initial testing:

DWORD* p= (DWORD*) ::GetWindowLong(flexGridHWND, GWL_USERDATA);
p++; //add 8;
p++; //add 8;
LPDISPATCH pDisp= (LPDISPATCH)*p;
long row,col;
COleDispatchDriver iDriver;
iDriver.AttachDispatch(pDisp);
// getrows()
iDriver.InvokeHelper(0x4, DISPATCH_PROPERTYGET, VT_I4, (void*)&row, NULL);
// getcols()
iDriver.InvokeHelper(0x5, DISPATCH_PROPERTYGET, VT_I4, (void*)&col, NULL);
ofs.open("c:\\textout.txt",ios::ate|ios::out);
ofs<<"rows:"<<row<<",cols:"<<col<<endl;
CString result;
static BYTE parms[] = VTS_I4;
for (int i=0;i<row;i++)
     for (int j=0;j<col;j++)
     {
          iDriver.InvokeHelper(0x37, DISPATCH_PROPERTYGET, VT_BSTR, (void*)&result, parms, col*i+j);
          ofs<<i<<","<<j<<"--"<<result<<endl;
     }
iDriver.DetachDispatch();
ofs.close();

Using this method works!  Amazing.  Is it at all reliable for flexgrids?  The next step is to setup the process communication, so I can call this from the controlling application and get the results there.

I actually e-mail madshi on the hook part, and he suggested not to use messages like I was, but to use memory mapped files and a thread instead.  I tend to agree with him, and I'll look into a memory mapped solution.

-Josh
>>How is the DLL getting called?
It is a dependency of the little test program (the controller).  So when the test app gets loaded, it gets loaded.  When the DLL gets loaded, it installs the hook (in DllMain).  That triggers a cascade of call in which the DLL is attached to all processes (dwReason is DLL_PROCESS_ATTACH)

One word to thr wise... Mkae the Hook DLL as simple as possible.  That's why I avoided using MFC in that DLL.  You don't want to bring in the MFC footprint into every single process on the system.  

>>but to use memory mapped files and a thread instead.  

The Window message passing should work (it does in the test system), but a pipe, shared memory file, or other IPC should also work.  However, there is probably no need to complicate things with an additional thread (KISS principle: Keep It Simple, Sweetheart)...

-- Dan
Avatar of jbirk

ASKER

OK, I was able to get it to work by hooking the PeekMessage and GetMessage api calls with madshi's HookCode function instead of using SetWindowsHookEx.  I probably could have gotten it to work with your code too, but since I already have this dll injected for another reason, it's easier to do it this way.

Anyway, your solution got me to the data.  Are you sure that this method of advancing the pointer will be reliable?  Will it work on different systems, etc?  That's my only concern...

HWND hWnd= m_ctlGrid.m_hWnd;
DWORD* p= (DWORD*) ::GetWindowLong(hWnd, GWL_USERDATA);
p++; //add 8;
p++; //add 8;
LPDISPATCH pDisp= (LPDISPATCH)*p;

That seems like kind of a kludge.  Can't I use QueryInterface or anything like that?

Thanks,
Josh
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jbirk

ASKER

OK, that's kind of what I thought.  It didn't seem like any of the functions most objects supply for interfacing were supplied by flexgrid, so I guess that makes getting it's interface in the conventional means not likely.

Thanks for coming up with the kludge.  Now I just need to test it on different systems...

-Josh