如何使用 Java 或 Java Native Access 阅读本地打开的 MS Word 文档的文本

How to read the text of a locally open MS Word document using Java or Java Native Access

我在读取本地打开的 MSWord 文档的文本时遇到问题。据我了解,使用以下方法,给定文档的路径,我可以在文档中执行任何操作。

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

但在我的例子中,我有一个本地打开的 word 对象的句柄 (WinDef.HWND)。而且我无法从中获取 local path。我已经给出了我正在尝试的代码,但我无法实现我想要的。请给出我如何实现上述解决方案的任何指针。

请注意,下面给出了WINWORD.EXE的路径。和 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);
    }
}


我不太确定你能否实现你想要的,但我会尽我所能回答你的问题,让你更接近目标。

有两种获取文件信息的方法:一种更通用Java/JNA,另一种需要您查看进程内存内部space。我将解决第一个问题。

与其处理 window 句柄,不如获取进程 ID,这样以后使用起来更容易。这相对简单:

IntByReference pidPtr = new IntByReference();
com.sun.jna.platform.win32.User32.INSTANCE.GetWindowThreadProcessId(hWnd, pidPtr);
int pid = pidPtr.getValue();

(注意,您可能应该有自己的 User32 接口扩展上面的接口,这样您就可以只使用 class 而不必像我那样完全限定 JNA 版本。 )

现在,有了 PID,有几个选项可以尝试获取路径。

  1. 如果你很幸运并且用户直接打开了文件(而不是使用文件>打开),你可以恢复他们使用的命令行,并且它可能有路径。您可以从 WMI class Win32_Process 中检索它。您可以在我的项目 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 中找到实现的完整代码。)

  2. 如果命令行检查不成功,您可以搜索该进程打开了哪些文件句柄。最简单的方法是下载 Handle 实用程序(但您的所有用户都必须这样做)然后只需执行命令行 handle -p 1234。这将列出该进程持有的打开文件。

  3. 如果您不能依赖您的用户下载 Handle,您可以尝试自己实现相同的代码。这是一个未记录的 API 使用 NtQuerySystemInformation。请参阅 JNA 项目 Issue 657 以获取示例代码,该代码将迭代操作系统的所有句柄,从而允许您查看文件。鉴于您已经知道 PID,您可以通过跳过代码的其余部分来缩短 SYSTEM_HANDLE sh = info.Handles[i]; 之后的迭代,除非 sh.ProcessID 与您的 pid 匹配。如该期所述,列出的代码大部分不受支持且危险。不能保证它会在 Windows.

  4. 的未来版本中工作

最后,您可以看到您可以使用进程内存做什么。有了 PID,您可以打开进程来获取句柄:

HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, pid);

然后你可以用EnumProcessModules; for each module use GetModuleInformation to retrieve a MODULEINFO结构枚举它的模块。这为您提供了一个指向内存的指针,您可以随心所欲地探索它。当然,要准确了解在什么偏移处找到什么信息,需要您正在探索的可执行文件(Word、写字板等,以及适当的版本)的 API。并且您需要管理员权限。此探索留作 reader.

的练习。