【问题标题】:Get list of processes on Windows in a charset-safe way以字符集安全的方式获取 Windows 上的进程列表
【发布时间】:2012-11-12 17:52:45
【问题描述】:

This post 给出了一种在Windows 下检索正在运行的进程列表的解决方案。本质上是这样的:

String cmd = System.getenv("windir") + "\\system32\\" + "tasklist.exe";
Process p = Runtime.getRuntime().exec(cmd);
InputStreamReader isr = new InputStreamReader(p.getInputStream());
BufferedReader input = new BufferedReader(isr);

然后读取输入。

它看起来和工作都很棒,但我想知道 tasklist 使用的字符集是否可能不是默认字符集并且此调用可能会失败?

例如this other question about a different executable 表明它可能会导致一些问题。

如果是这样,有没有办法确定合适的字符集是什么?

【问题讨论】:

  • 这里有问题吗?你试过了吗?
  • @JimGarrison 我从 FindBugs 收到关于 InputStreamReader 中“依赖默认编码” 的警告,我不知道这是否会导致问题。所以我搜索并发现第二个帖子似乎说它可以。这就是我要检查的。在我的机器上,该代码运行良好。
  • 我会将其添加为评论而不是问题,因为我的不确定性相当大。也就是说,我认为像这样的系统实用程序使用的字符集将是操作系统安装的默认语言环境。查询该语言环境并使用它来解释输出流似乎是最通用的方法。但是,如果还存在本地化,则需要对可能更改的字段进行逆向工程,以便将它们解析出来。而这一切都取决于所讨论的实用程序是否一开始就以这种方式变化。

标签: java list character-encoding process


【解决方案1】:

可以分成两部分:

  1. 窗户部分
    从java你正在执行一个Windows命令——在“Windows Land”中的jvm外部。当 java Runtime 类执行 windows 命令时,它使用控制台的 DLL,因此在 windows 中看起来好像命令在控制台中运行
    问:当我运行 C:\windows\system32\tasklist.exe在控制台中,结果的字符编码(Windows 术语中的“代码页”)是什么?

    • windows 不带参数的“chcp”命令给出了控制台的活动代码页号(例如,850 用于 Multilingual-Latin-1,1252 用于 Latin-1)。见Windows Microsoft Code Pages、Windows OEM Code Pages、Windows ISO Code Pages
      默认系统代码页最初是根据您的系统区域设置设置的(键入 systeminfo 以查看此设置或控制面板-> 区域和语言)。
    • windows OS/.NET 函数getACP() 也提供此信息

  2. Java 部分:
    如何从“x”的 windows 代码页(例如 850 或 1252)解码 java 字节流?

    • windows代码页号和等效java字符集名称之间的完整映射可以从here - Code Page Identifiers (Windows)导出
    • 但是,实际上可以添加以下前缀之一来实现映射:
      ""(无)用于 ISO,“IBM”或“x-IBM”用于 OEM,“windows-”或“x-windows-”用于 Microsoft/Windows。
      例如。 ISO-8859-1 或 IBM850 或 windows-1252

完整解决方案:

    String cmd = System.getenv("windir") + "\\system32\\" + "chcp.com";
    Process p = Runtime.getRuntime().exec(cmd);
    // Use default charset here - only want digits which are "core UTF8/UTF16"; 
    // ignore text preceding ":"
    String windowsCodePage = new Scanner(
        new InputStreamReader(p.getInputStream())).skip(".*:").next();

    Charset charset = null;
    String[] charsetPrefixes = 
        new String[] {"","windows-","x-windows-","IBM","x-IBM"};
    for (String charsetPrefix : charsetPrefixes) {
        try {
            charset = Charset.forName(charsetPrefix+windowsCodePage);
            break;
        } catch (Throwable t) {
        }
    }
    // If no match found, use default charset
    if (charset == null) charset = Charset.defaultCharset();

    cmd = System.getenv("windir") + "\\system32\\" + "tasklist.exe";
    p = Runtime.getRuntime().exec(cmd);
    InputStreamReader isr = new InputStreamReader(p.getInputStream(), charset);
    BufferedReader input = new BufferedReader(isr);

    // Debugging output
    System.out.println("matched codepage "+windowsCodePage+" to charset name:"+
            charset.name()+" displayName:"+charset.displayName());
    String line;
    while ((line = input.readLine()) != null) {
           System.out.println(line);
    }

感谢您的提问! - 很有趣。

【讨论】:

  • 这很棒 - 我复制了 notepad.exe 应用程序并将其重新命名为 0aéèçê.exe 并启动它。我的原始代码失败(显示方形字符)。您的版本确实输出了正确的字符串(代码页为 850)。
【解决方案2】:

其实tasklist使用的字符集总是不同于系统默认的。

另一方面,只要输出限制为ASCII,使用默认值是相当安全的。通常可执行模块的名称中只有 ASCII 字符。

因此,要获得正确的字符串,您必须将 (ANSI) Windows 代码页转换为 OEM 代码页,并将后者作为字符集传递给 InputStreamReader。

这些编码之间似乎没有全面的映射。可以使用以下映射:

Map<String, String> ansi2oem = new HashMap<String, String>();
ansi2oem.put("windows-1250", "IBM852");
ansi2oem.put("windows-1251", "IBM866");
ansi2oem.put("windows-1252", "IBM850");
ansi2oem.put("windows-1253", "IBM869");

Charset charset = Charset.defaultCharset();
String streamCharset = ansi2oem.get(charset.name());
if (streamCharset) {
    streamCharset = charset.name();
}
InputStreamReader isr = new InputStreamReader(p.getInputStream(),
                                              streamCharset);

这种方法适用于 windows-1251 和 IBM866 对。

要获取 Windows 当前使用的 OEM 编码,您可以使用 GetOEMCP 函数。返回值取决于 Region and Language 控制面板中 Administrative 选项卡上的 Language for non-Unicode 程序 设置。需要重新启动才能应用更改。


Windows 上有两种编码:ANSI 和OEM。

前者由在 GUI 模式下运行的非 Unicode 应用程序使用。
后者由控制台应用程序使用。控制台应用程序无法显示当前 OEM 编码中无法表示的字符。

由于tasklist 是控制台模式应用程序,其输出始终采用当前的OEM 编码。

对于英文系统,这对通常是Windows-1252 和CP850。

由于我在俄罗斯,我的系统具有以下编码:Windows-1251 和 CP866。
如果我将tasklist 的输出捕获到文件中,该文件将无法正确显示西里尔字符:

在记事本中查看时,我得到的是 ЏаЁўҐв 而不是 Привет(嗨!)。
而µTorrent 则显示为зTorrent。

您无法更改tasklist 使用的编码。


但是可以更改cmd 的输出编码。如果你将/u 开关传递给它,它将以UTF-16 编码输出所有内容。

cmd /c echo Hi>echo.txt

echo.txt 的大小为 4 个字节:Hi 两个字节,新行两个字节(\r 和 \n)。

cmd /u /c echo Hi>echo.txt

现在echo.txt 的大小是 8 个字节:每个字符用两个字节表示。

【讨论】:

  • 感谢您提供详细而翔实的回答 - 我发现 Glen Best 的回答更好,因为它提供了一个完整的工作示例,因此我选择了它,但您的回答也非常好。
【解决方案3】:

为什么不通过JNA 使用Windows API,而不是生成进程?像这样:

import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Tlhelp32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.win32.W32APIOptions;
import com.sun.jna.Native; 

public class ListProcesses {
    public static void main(String[] args) {
        Kernel32 kernel32 = (Kernel32) Native.loadLibrary(Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
        Tlhelp32.PROCESSENTRY32.ByReference processEntry = new Tlhelp32.PROCESSENTRY32.ByReference();          

        WinNT.HANDLE snapshot = kernel32.CreateToolhelp32Snapshot(Tlhelp32.TH32CS_SNAPPROCESS, new WinDef.DWORD(0));
        try  {
            while (kernel32.Process32Next(snapshot, processEntry)) {             
                System.out.println(processEntry.th32ProcessID + "\t" + Native.toString(processEntry.szExeFile));
            }
        }
        finally {
            kernel32.CloseHandle(snapshot);
        }
    } 
}

我发布了类似的答案elsewhere。

【讨论】:

  • 上面只输出命令名而不是整个命令行。有没有获取进程完整的命令行?
【解决方案4】:

有更好的方法来检查正在运行的进程,甚至可以通过 java 运行 OS 命令:Process 和 ProcessBuilder。

关于字符集,您可以随时查询操作系统支持的字符集,根据需要获取Encoder或Decoder。

[编辑] 让我们分解一下;无法知道给定 String 的字节在哪种编码中,因此您唯一的选择是获取这些字节,根据需要改变顺序(如果您曾经处于这样的环境中,其中一个进程可以为您提供一个数组不同顺序的字节,使用 ByteBuffer 处理),并使用支持的多个 CharsetDecoder 将字节解码为合理的输出。

这太过分了,需要您估计给定的输出可能是 UTF-8、UTF-16 或任何其他编码。但至少您可以使用其中一种可能的字符集解码给定的输出,然后尝试使用处理后的输出来满足您的需要。

由于我们讨论的是由运行 JVM 本身的同一操作系统运行的进程,因此您的输出很可能是 availableCharsets() 方法返回的字符集编码之一。

【讨论】:

  • 我已经在使用进程并且我知道如何指定字符集。问题是:使用哪个字符集。你说“你总是可以向操作系统询问支持的字符集”:你是怎么做到的?我如何知道该特定程序使用了哪些受支持的字符集?
  • 您使用的是 Process,而不是 ProcessBuilder,它比使用 Runtime 类更干净。您需要调用以获取可用字符集的实际方法是 Charset.availableCharsets()。但即便如此,使用我给你的 javadocs 中的方法来测试 Charset 会更安全 - CharsetEncoder.canEncode()、detect() 等...
  • 很抱歉,但我不明白它是如何工作的。您能否举一个简单的例子说明您将如何将您的建议应用于我的特定用例?
  • 稍微改进了答案以解释我对这个问题的看法;所涉及的代码现在应该很容易想象,到那时我可以再举一个简单的例子,它没有什么用 - 首先你需要知道我提出的内容是否符合你的需求。
  • 如何使用Process查看正在运行的进程?它的唯一目的是表示通过ProcessBuilder.start() 或Runtime.exec() 从Java 创建的(子)进程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-12
  • 2014-10-16
  • 2021-04-27
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多