Obtenga una lista de procesos en Windows de una manera segura para charset

Esta publicación proporciona una solución para recuperar la lista de procesos en ejecución en Windows. En esencia lo hace:

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

luego lee la entrada.

Se ve y funciona muy bien, pero me preguntaba si existe la posibilidad de que el juego de caracteres utilizado por la lista de tareas no sea el juego de caracteres predeterminado y que esta llamada podría fallar.

Por ejemplo, esta otra pregunta sobre un ejecutable diferente muestra que podría causar algunos problemas.

Si ese es el caso, ¿hay alguna manera de determinar cuál sería el juego de caracteres apropiado?

Puede dividir esto en 2 partes:

  1. La parte de ventanas
    Desde java está ejecutando un comando de Windows, externamente a jvm en “Windows land”. Cuando la clase Java Runtime ejecuta un comando de Windows, usa el DLL para consolas y aparece en Windows como si el comando se ejecutara en una consola
    P: Cuando ejecuto C: \ windows \ system32 \ tasklist.exe en una consola, ¿cuál es la encoding de caracteres (“página de códigos” en la terminología de Windows) del resultado?

    • El comando “chcp” de windows sin argumento proporciona el número de página de códigos activo para la consola (por ejemplo, 850 para Multilingual-Latin-1, 1252 para Latin-1). Consulte las páginas de códigos de Microsoft Windows , las páginas de códigos OEM de Windows , las páginas de códigos ISO de Windows
      La página de códigos del sistema predeterminada se configura originalmente según la configuración regional del sistema (escriba systeminfo para ver esto o Panel de control-> Región e idioma).
    • la función de Windows OS / .NET getACP () también proporciona esta información
  2. La parte de Java:
    ¿Cómo decodifico una secuencia de bytes java desde la página de códigos de Windows de “x” (por ejemplo, 850 o 1252)?

    • la correspondencia completa entre los números de página de códigos de Windows y los nombres de los juegos de caracteres java equivalentes se puede derivar desde aquí – Identificadores de página de códigos (Windows)
    • Sin embargo, en la práctica, se puede agregar uno de los siguientes prefijos para lograr la asignación:
      “” (ninguno) para ISO, “IBM” o “x-IBM” para OEM, “windows-” O “x-windows-” para Microsoft / Windows.
      Por ejemplo, ISO-8859-1 o IBM850 o Windows-1252

Solución completa:

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

Gracias por la Q! – fue divertido.

En realidad, el juego de caracteres utilizado por la tasklist de tasklist siempre es diferente del sistema predeterminado.

Por otro lado, es bastante seguro usar el valor predeterminado siempre que la salida esté limitada a ASCII . Por lo general, los módulos ejecutables tienen solo caracteres ASCII en sus nombres.

Entonces, para obtener las cadenas correctas, debe convertir (ANSI) la página de códigos de Windows en la página de códigos OEM y pasar el último como juego de caracteres a InputStreamReader .

Parece que no hay una correspondencia completa entre estas codificaciones. El siguiente mapeo puede ser utilizado:

 Map ansi2oem = new HashMap(); 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); 

Este enfoque funcionó para mí con windows-1251 y el par IBM866 .

Para obtener la encoding OEM utilizada por Windows, puede usar la función GetOEMCP . El valor de retorno depende del idioma para la configuración de progtwigs no Unicode en la pestaña Administrativo en el panel de control de Región e Idioma . Se requiere reiniciar para aplicar el cambio.


Hay dos tipos de codificaciones en Windows: ANSI y OEM .

El primero es utilizado por aplicaciones no Unicode que se ejecutan en modo GUI.
El último es utilizado por las aplicaciones de la consola. Las aplicaciones de consola no pueden mostrar caracteres que no se pueden representar en la encoding OEM actual.

Como tasklist es una aplicación en modo consola, su resultado siempre está en la encoding OEM actual.

Para los sistemas en inglés, el par suele ser Windows-1252 y CP850 .

Como estoy en Rusia, mi sistema tiene las siguientes codificaciones: Windows-1251 y CP866 .
Si capturo la salida de la tasklist de tasklist en un archivo, el archivo no puede mostrar los caracteres cirílicos correctamente:

Obtengo ЏаЁўҐв lugar de Привет (¡Hola!) Cuando se ven en el Bloc de notas.
Y µTorrent se muestra como зTorrent .

No puede cambiar la encoding utilizada por la tasklist de tasklist .


Sin embargo, es posible cambiar la encoding de salida de cmd . Si pasa /u cambia a él, dará salida a todo en la encoding UTF-16.

 cmd /c echo Hi>echo.txt 

El tamaño de echo.txt es de 4 bytes: dos bytes para Hi y dos bytes para la nueva línea ( \r y \n ).

 cmd /u /c echo Hi>echo.txt 

Ahora el tamaño de echo.txt es de 8 bytes: cada personaje se representa con dos bytes.

¿Por qué no usar la API de Windows a través de JNA , en lugar de procesos de generación? Me gusta esto:

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

Publiqué una respuesta similar en otro lugar .

Hay una forma mucho mejor de verificar los procesos en ejecución, o incluso ejecutar el comando del SO a través de java: Process y ProcessBuilder .

En cuanto a Charset, siempre puede consultar el sistema operativo sobre los juegos de caracteres admitidos y obtener un codificador o decodificador de acuerdo con sus necesidades.

[Editar] Vamos a descomponerlo; no hay forma de saber en qué encoding están los bytes de una cadena dada, por lo que su única opción es obtener esos bytes, cambiar el orden según sea necesario (si alguna vez está en un entorno en el que un proceso puede proporcionarle una variedad de bytes en diferentes ordenamientos, use ByteBuffer para tratar con eso), y use los múltiples CharsetDecoders compatibles para decodificar los bytes a un resultado razonable.

Es excesivo y requiere estimar que una salida determinada podría estar en UTF-8, UTF-16 o cualquier otra encoding. Pero al menos puede decodificar la salida dada utilizando uno de los Charsets posibles, y luego tratar de usar la salida procesada para sus necesidades.

Dado que estamos hablando de un proceso ejecutado por el mismo sistema operativo en el que se ejecuta la JVM, es muy posible que su salida esté en una de las codificaciones Charset devueltas por el método availableCharsets ().