1

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 .

https://github.com/java-native-access/jna/blob/master/contrib/msoffice/src/com/sun/jna/platform/win32/COM/util/office/Wordautomation_KB_313193_Mod.java

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);
    }
}


  • Have you tried [Apache POI](https://poi.apache.org/) or something similar? – dpr Sep 25 '19 at 12:29
  • Thanks for the reply. [link](https://poi.apache.org/)**Apache POI** I could use once I get the binary of the text of the Document. Now I have only a ```WinDef.HWND``` which is a handler to the document , this have a ```com.sun.jna.Pointer```. But I am not sure how to get the text from the pointer or get the path out of the document . Any help or direction will be very much appreciated . – Arunabh Dash Sep 25 '19 at 16:52
  • I'm not clear what you mean by "Local Path". Is this the full path from which the process was opened? The current working directory? The path of the file currently open in the document? – Daniel Widdis Sep 25 '19 at 18:07
  • I mean the path of the file currently open in the document . You could see in the following example from line 66-68 then have given the local file path ``` Helper.extractClasspathFileToReal("/com/sun/jna/platform/win32/COM/util/office/resources/jnatest.doc", demoDocument); msWord.getDocuments().Open(demoDocument.getAbsolutePath()); ``` Similarly if I will get the path or binary I could do all shorts of operation on it. Letme knw if more input is needed . https://github.com/java-native-access/jna/blob/master/contrib/msoffice/src/com/sun/jna/platform/win32/COM/util/office/MSOfficeWordDemo.java – Arunabh Dash Sep 25 '19 at 18:15
  • It's possible to do with an undocumented API. Or via commandline with a Windows Sysinternals free download. Which do you prefer? – Daniel Widdis Sep 25 '19 at 18:32
  • Actually... it's not. You want the file path of the document that's currently open. We can find that, but then you won't be able to open it separately because its file handle is locked by that open document. – Daniel Widdis Sep 25 '19 at 18:37
  • I can tell you how to get the path, but that won't help you because you can't open it while opened. But can't you just do `msWord.getActiveDocument()` in the existing code you linked? – Daniel Widdis Sep 25 '19 at 18:54
  • You could see in the code, without setting the path I can't get a MSWord object . Thus I am not able to do a `msWord.getActiveDocument()`. One more input, I just want to read the document . So if you could point out how could I read from the `WinDef.HWND` or 'com.sun.jna.Pointer' that will be helpful . – Arunabh Dash Sep 25 '19 at 19:11

1 Answers1

1

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.

  1. 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.)

  2. 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.

  3. 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.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
  • Thanks a lot @Daniel Widdis .The solution you suggested it worked :). Could you please help me in solving another problem using JNA. https://stackoverflow.com/questions/58026937/not-able-to-read-contains-from-a-open-command-prompt-using-java-native-access – Arunabh Dash Oct 03 '19 at 04:50
  • I suggested three solutions. I'm curious which one worked. :) (Also feel free to accept the answer if it is satisfactory). – Daniel Widdis Oct 03 '19 at 20:08
  • 1
    I used the first approach . In my case user will always open the file from it's directory. So that works for me. – Arunabh Dash Oct 09 '19 at 09:29