I have a problem statement of reading the text of locally open MSWord document. What I understand, using the following approach, given the path of the document, I can perform any operation in the document .
But in my case I have a Handle (WinDef.HWND) to the locally opened word object . And I am not able to get the local path from it. I have given the code which I am trying out and I am not able to achieve what I looking for . Please give the any pointer how I can achieve solution of the above .
Please note that the following gives the path of WINWORD.EXE . And
System.out.println("File Path: "+desktop.getFilePath());
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.DesktopWindow;
import com.sun.jna.platform.FileUtils;
import com.sun.jna.platform.WindowUtils;
import com.sun.jna.platform.WindowUtils.NativeWindowUtils;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.Kernel32Util;
import com.sun.jna.platform.win32.WinUser;
import com.sun.jna.win32.StdCallLibrary;
import java.util.List;
public class NativeWordpadExtractor {
public static void main(String ar[]){
executeNativeCommands();
}
public static void executeNativeCommands(){
NativeExtractor.User32 user32 = NativeExtractor.User32.INSTANCE;
user32.EnumWindows(new WinUser.WNDENUMPROC() {
int count = 0;
@Override
public boolean callback(WinDef.HWND hWnd, Pointer arg1) {
byte[] windowText = new byte[512];
user32.GetWindowTextA(hWnd, windowText, 512);
String wText = Native.toString(windowText);
// get rid of this if block if you want all windows regardless of whether
// or not they have text
if (wText.isEmpty()) {
return true;
}
if("SampleTextForScreenScrapping_Word - WordPad".equals(wText)){
System.out.println("Got the 'Wordpad'" + hWnd + ", class " + hWnd.getClass() +"getPointer"+ hWnd.getPointer()+ " Text: " + wText);
//WinDef.HWND notePadHwnd = user32.FindWindowA("Wordpad",null );
byte[] fileText = new byte[1024];
System.out.println("fileText : " + WindowUtils.getWindowTitle(hWnd));
List<DesktopWindow> desktops=WindowUtils.getAllWindows(true);
// Approach 1) For getting a handle to the Desktop object . I am not able to achieve result with this.
for(DesktopWindow desktop:desktops){
System.out.println("File Path: "+desktop.getFilePath());
System.out.println("Title : "+desktop.getTitle());
}
System.out.println("fileText : " + WindowUtils.getAllWindows(true));
// Approach 2) For getting a handle to the native object .
// This is also not working
WinDef.HWND editHwnd = user32.FindWindowExA(hWnd, null, null, null);
byte[] lParamStr = new byte[512];
WinDef.LRESULT resultBool = user32.SendMessageA(editHwnd, NativeExtractor.User32.WM_GETTEXT, 512, lParamStr);
System.out.println("The content of the file is : " + Native.toString(lParamStr));
return false;
}
System.out.println("Found window with text " + hWnd + ", total " + ++count + " Text: " + wText);
return true;
}
}, null);
}
interface User32 extends StdCallLibrary {
NativeExtractor.User32 INSTANCE = (NativeExtractor.User32) Native.loadLibrary("user32", NativeExtractor.User32.class);
int WM_SETTEXT = 0x000c;
int WM_GETTEXT = 0x000D;
int GetWindowTextA(WinDef.HWND hWnd, byte[] lpString, int nMaxCount);
boolean EnumWindows(WinUser.WNDENUMPROC lpEnumFunc, Pointer arg);
WinDef.HWND FindWindowA(String lpClassName, String lpWindowName);
WinDef.HWND FindWindowExA(WinDef.HWND hwndParent, WinDef.HWND hwndChildAfter, String lpClassName, String lpWindowName);
WinDef.LRESULT SendMessageA(WinDef.HWND paramHWND, int paramInt, WinDef.WPARAM paramWPARAM, WinDef.LPARAM paramLPARAM);
WinDef.LRESULT SendMessageA(WinDef.HWND editHwnd, int wmGettext, long l, byte[] lParamStr);
int GetClassNameA(WinDef.HWND hWnd, byte[] lpString, int maxCount);
}
}
I'm not quite sure you can achieve what you want, but I'll do what I can to answer your questions to get you closer to the goal.
There are two ways to get the file information: one more generic with Java/JNA and the the other requiring you to peer inside the process memory space. I'll address the first one.
Rather than dealing with a window handle, let's get the Process ID, which is easier to use later. That's relatively straightforward:
IntByReference pidPtr = new IntByReference();
com.sun.jna.platform.win32.User32.INSTANCE.GetWindowThreadProcessId(hWnd, pidPtr);
int pid = pidPtr.getValue();
(Of note, you should probably have your own User32
interface extend the one above so you can just use the one class and not have to fully qualify the JNA version like I did.)
Now, armed with the PID, there are a few options to try to get the path.
If you're lucky and the user opened the file directly (rather than using File>Open), you can recover the commandline they used, and it will likely have the path. You can retrieve this from the WMI class Win32_Process
. Full code you can find in my project OSHI in the WindowsOperatingSystem class or you can try using Runtime.getRuntime().exec()
to use the commandline WMI version: wmic path Win32_Process where ProcessID=1234 get CommandLine
, capturing the result in a BufferedReader
(or see OSHI's ExecutingCommand
class for an implementation.)
If the command line check is unsuccessful you can search for which file handles are open by that process. The easiest way to do that is to download the Handle utility (but all your users would have to do this) and then just execute the command line handle -p 1234
. This will list open files held by that process.
If you can't rely on your users downloading Handle, you can try to implement the same code yourself. This is an undocumented API using NtQuerySystemInformation
. See the JNA project Issue 657 for sample code which will iterate all of an operating system's handles, allowing you to look at the files. Given that you already know the PID you can shortcut the iteration after SYSTEM_HANDLE sh = info.Handles[i];
by skipping the remainder of the code unless sh.ProcessID
matches your pid. As stated in that issue, the code listed is mostly unsupported and dangerous. There is no guarantee it will work in future versions of Windows.
Finally, you can see what you can do with process memory. Armed with the PID, you could open the Process to get a handle:
HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, pid);
Then you could enumerate its modules with EnumProcessModules; for each module use GetModuleInformation to retrieve a MODULEINFO structure. This gives you a pointer to memory that you can explore to your heart's content. Of course, accurately knowing at what offsets to find what information requires the API for the executable you're exploring (Word, WordPad, etc., and the appropriate version.) And you need admin rights. This exploration is left as an exercise for the reader.