2012-11-12 2 views
7

This post fornisce una soluzione per recuperare l'elenco dei processi in esecuzione in Windows. In sostanza lo fa:Ottieni un elenco di processi su Windows in un modo sicuro da charset

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

quindi legge l'input.

Sembra e funziona benissimo, ma mi chiedevo se esiste la possibilità che il set di caratteri utilizzato dall'elenco di attività potrebbe non essere il set di caratteri predefinito e che questa chiamata potrebbe non riuscire?

Ad esempio this other question about a different executable indica che potrebbe causare alcuni problemi.

Se è il caso, c'è un modo per determinare quale sarebbe il set di caratteri appropriato?

+0

C'è una domanda qui? Hai provato e vedi? –

+0

@JimGarrison Ho ricevuto un avviso da FindBugs su * "affidamento sulla codifica predefinita" * in InputStreamReader e non ho idea se questo potrebbe causare un problema o meno. Così ho cercato e trovato il secondo post che sembra dire che potrebbe. Questo è quello che voglio controllare. Sulla mia macchina quel codice funziona bene. – assylias

+0

Aggiungerò questo come commento piuttosto che come una domanda perché la mia incertezza è abbastanza grande. Detto questo, penserei che il set di caratteri usato da un'utilità di sistema come quella sarebbe quello delle impostazioni internazionali predefinite per l'installazione del sistema operativo. Interrogare per quella locale e usarlo per interpretare il flusso di output sembra essere l'approccio più generale. Ma se sono presenti anche delle localizzazioni, è necessario invertire la progettazione dei campi che potrebbero cambiare in modo da analizzarli. E tutto dipende dal fatto che l'utilità in questione sia stata scritta per variare in questo modo in primo luogo. – eh9

risposta

11

Può rompere questo in 2 parti:

  1. L'finestre parte
    da Java si sta eseguendo un comando di Windows - esternamente alla JVM in "terra di Windows".Quando java Runtime class esegue un comando windows, usa la DLL per le console & quindi appare a windows come se il comando è in esecuzione su una console
    Q: Quando eseguo C: \ windows \ system32 \ tasklist.exe in una console , qual è la codifica dei caratteri ("code page" nella terminologia di Windows) del risultato?

    • finestre "chcp" comando con alcun argomento dà il numero di tabella codici attiva per la console (ad esempio 850 per multilingue-Latin-1, 1252 per il latino-1). Vedi Windows Microsoft Code Pages, Windows OEM Code Pages, Windows ISO Code Pages
      La tabella codici di sistema predefinita è originariamente impostata in base alle impostazioni locali del sistema (digitare systeminfo per vedere questo o Pannello di controllo-> Regione e lingua).
    • il sistema operativo Windows/funzione .NET getACP() dà anche questa informazione

  2. La parte java:
    Come faccio a decodificare un flusso di byte Java dal codice di Windows pagina di "x" (ad es. 850 o 1252)?

    • la piena corrispondenza tra i numeri di pagina di codice di Windows e nomi charset java equivalenti può essere derivata da here - Code Page Identifiers (Windows)
    • Tuttavia, in pratica uno dei seguenti prefissi possono essere aggiunti per ottenere la mappatura:
      "" (nessuno) per ISO, "IBM" o "x-IBM" per OEM, "windows-" OR "x-windows-" per Microsoft/Windows.
      E.g. ISO-8859-1 o IBM850 o Windows-1252

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

Grazie per il Q! - era divertente.

+0

Questo è fantastico: ho copiato l'applicazione 'notepad.exe' e l'ho rimandata a' 0aéèçê.exe' e l'ho lanciata. Il mio codice originale non è riuscito (mostrando caratteri quadrati). La tua versione ha prodotto la stringa corretta (con codepage 850). – assylias

0

C'è un modo molto migliore per controllare i processi in esecuzione, o anche per eseguire il comando del sistema operativo tramite java: Process e ProcessBuilder.

Come per il set di caratteri, è sempre possibile chiedere al sistema operativo i set di caratteri supportati e ottenere uno Encoder o Decoder in base alle proprie esigenze.

[Modifica] Let's down; non c'è modo di sapere in quale codifica sono i byte di una determinata stringa, quindi la tua unica scelta è quella di ottenere quei byte, spostare l'ordine come necessario (se ti trovi mai in un tale ambiente in cui un processo può darti una serie di byte in ordine diverso, utilizzare ByteBuffer per gestirlo) e utilizzare i vari CharsetDecoder supportati per decodificare i byte in un output ragionevole.

È eccessivo e richiede di stimare che un dato risultato possa essere in UTF-8, UTF-16 o qualsiasi altra codifica. Ma a almeno è possibile decodificare l'output specificato utilizzando uno dei possibili set di caratteri, quindi provare a utilizzare l'output elaborato per le proprie esigenze.

Poiché si tratta di un processo eseguito dallo stesso sistema operativo in cui è in esecuzione la JVM stessa, è possibile che l'output si trovi in ​​una delle codifiche Charset restituite dal metodo availableCharsets().

+0

Sto già utilizzando un processo e so come specificare un set di caratteri. La domanda è: quale charset usare. Dichiari "* puoi sempre chiedere al sistema operativo i set di caratteri supportati *": come si fa? Come faccio a sapere quale dei set di caratteri supportati è utilizzato da quel programma specifico? – assylias

+0

Si sta utilizzando un processo, ma non un ProcessBuilder, che è più pulito rispetto all'utilizzo della classe Runtime. Il metodo effettivo che devi chiamare per ottenere i set di caratteri disponibili è Charset.availableCharsets(). Ma anche così, sarebbe più sicuro testare un Charset usando i metodi nel javadoc che ti ho dato - CharsetEncoder.canEncode(), detect(), ecc ... – javabeats

+0

Mi dispiace ma non capisco come funzionerebbe. Potresti fare un semplice esempio di come applicheresti la tua raccomandazione al mio caso d'uso specifico? – assylias

5

In realtà, il set di caratteri utilizzato da tasklist è sempre diverso dal valore predefinito di sistema.

D'altra parte, è abbastanza sicuro utilizzare l'impostazione predefinita finché l'output è limitato a ASCII. Solitamente i moduli eseguibili hanno solo caratteri ASCII nei loro nomi.

Quindi, per ottenere le stringhe corrette, è necessario convertire (codice ANSI) la tabella codici di Windows nella tabella codici OEM e passare quest'ultima come set di caratteri a InputStreamReader.

Sembra che non ci sia una mappatura completa tra queste codifiche. La seguente mappatura può essere utilizzato:

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

Questo approccio ha funzionato per me con windows-1251 e IBM866 coppia.

Per ottenere la codifica OEM corrente utilizzata da Windows, è possibile utilizzare la funzione GetOEMCP.Il valore di ritorno dipende Lingua per programmi non Unicode impostazione sul amministrativa scheda in Regione e pannello di controllo Lingua. Il riavvio è richiesto per applicare la modifica.


Ci sono due tipi di codifiche su Windows: ANSI e OEM.

Il primo è utilizzato da applicazioni non Unicode in esecuzione in modalità GUI.
Quest'ultimo è utilizzato dalle applicazioni della console. Le applicazioni della console non possono visualizzare caratteri che non possono essere rappresentati nella codifica OEM corrente.

Poiché tasklist è l'applicazione in modalità console, il suo output è sempre nell'attuale codifica OEM.

Per i sistemi inglesi, la coppia è in genere Windows-1252 e CP850.

Dato che sono in Russia, il mio sistema ha le seguenti codifiche: Windows-1251 e CP866.
Se mi cattura di uscita del tasklist in un file, il file non può visualizzare caratteri cirillici correttamente:

ricevo ЏаЁўҐв invece di Привет se visti in Blocco note (Hi!).
E µTorrent viene visualizzato come зTorrent.

Non è possibile modificare la codifica utilizzata da tasklist.


Tuttavia è possibile modificare la codifica di uscita cmd. Se passi lo switch /u, verrà visualizzato tutto nella codifica UTF-16.

cmd /c echo Hi>echo.txt 

Le dimensioni echo.txt è 4 byte: due byte per Hi e due byte per nuova linea (\r e \n).

cmd /u /c echo Hi>echo.txt 

Ora la dimensione del echo.txt è 8 byte: ogni carattere è rappresentato con due byte.

+0

Grazie per la tua risposta dettagliata e informativa - Trovo migliore la risposta di Glen Best nel senso che fornisce un esempio operativo completo, quindi l'ho selezionato, ma anche il tuo è stato molto buono. – assylias

3

Perché non utilizzare l'API di Windows tramite JNA, anziché i processi di spawning? Come questo:

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

Ho inviato una risposta simile elsewhere.

+0

Quanto sopra mostra solo il nome del comando e NON l'intera riga di comando. È lì per ottenere il processo completo a linea di comando? –