如何使用 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 文档的文本时遇到问题。据我了解,使用以下方法,给定文档的路径,我可以在文档中执行任何操作。
但在我的例子中,我有一个本地打开的 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,有几个选项可以尝试获取路径。
如果你很幸运并且用户直接打开了文件(而不是使用文件>打开),你可以恢复他们使用的命令行,并且它可能有路径。您可以从 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 中找到实现的完整代码。)
如果命令行检查不成功,您可以搜索该进程打开了哪些文件句柄。最简单的方法是下载 Handle 实用程序(但您的所有用户都必须这样做)然后只需执行命令行 handle -p 1234
。这将列出该进程持有的打开文件。
如果您不能依赖您的用户下载 Handle,您可以尝试自己实现相同的代码。这是一个未记录的 API 使用 NtQuerySystemInformation
。请参阅 JNA 项目 Issue 657 以获取示例代码,该代码将迭代操作系统的所有句柄,从而允许您查看文件。鉴于您已经知道 PID,您可以通过跳过代码的其余部分来缩短 SYSTEM_HANDLE sh = info.Handles[i];
之后的迭代,除非 sh.ProcessID
与您的 pid 匹配。如该期所述,列出的代码大部分不受支持且危险。不能保证它会在 Windows.
的未来版本中工作
最后,您可以看到您可以使用进程内存做什么。有了 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.
的练习。
我在读取本地打开的 MSWord 文档的文本时遇到问题。据我了解,使用以下方法,给定文档的路径,我可以在文档中执行任何操作。
但在我的例子中,我有一个本地打开的 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,有几个选项可以尝试获取路径。
如果你很幸运并且用户直接打开了文件(而不是使用文件>打开),你可以恢复他们使用的命令行,并且它可能有路径。您可以从 WMI class
Win32_Process
中检索它。您可以在我的项目 OSHI in the WindowsOperatingSystem class or you can try usingRuntime.getRuntime().exec()
to use the commandline WMI version:wmic path Win32_Process where ProcessID=1234 get CommandLine
, capturing the result in aBufferedReader
(or see OSHI'sExecutingCommand
class 中找到实现的完整代码。)如果命令行检查不成功,您可以搜索该进程打开了哪些文件句柄。最简单的方法是下载 Handle 实用程序(但您的所有用户都必须这样做)然后只需执行命令行
handle -p 1234
。这将列出该进程持有的打开文件。如果您不能依赖您的用户下载 Handle,您可以尝试自己实现相同的代码。这是一个未记录的 API 使用
NtQuerySystemInformation
。请参阅 JNA 项目 Issue 657 以获取示例代码,该代码将迭代操作系统的所有句柄,从而允许您查看文件。鉴于您已经知道 PID,您可以通过跳过代码的其余部分来缩短SYSTEM_HANDLE sh = info.Handles[i];
之后的迭代,除非sh.ProcessID
与您的 pid 匹配。如该期所述,列出的代码大部分不受支持且危险。不能保证它会在 Windows. 的未来版本中工作
最后,您可以看到您可以使用进程内存做什么。有了 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.
的练习。