2012-11-12 14 views
7

This post przedstawia rozwiązanie umożliwiające pobranie listy uruchomionych procesów w systemie Windows. W istocie:Wyświetl listę procesów w systemie Windows w sposób bezpieczny dla zestawów znaków

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

następnie odczytuje dane wejściowe.

Wygląda i działa świetnie, ale zastanawiałem się, czy istnieje możliwość, że zestaw znaków użyty przez listę zadań może nie być domyślnym zestawem znaków i że to połączenie może się nie powieść?

Na przykład this other question about a different executable pokazuje, że może to powodować pewne problemy.

Jeśli tak, czy istnieje sposób określenia, jaki zestaw znaków będzie odpowiedni?

+0

Czy jest tu pytanie? Spróbowałeś i zobaczysz? –

+0

@JimGarrison Dostałem ostrzeżenie od FindBugs o * "poleganiu na domyślnym kodowaniu" * w InputStreamReader i nie mam pojęcia, czy to może spowodować problem, czy nie. Więc szukałem i znalazłem drugi post, który wydaje się mówić, że może. Właśnie to chcę sprawdzić. Na moim komputerze ten kod działa dobrze. – assylias

+0

Dodam to jako komentarz, a nie pytanie, ponieważ moja niepewność jest dość duża. Powiedziałbym jednak, że zestaw znaków używany przez takie narzędzie systemowe będzie domyślnym ustawieniem dla instalacji systemu operacyjnego. Zapytanie o to locale i użycie go do interpretacji strumienia wyjściowego wydaje się być najbardziej ogólnym podejściem. Ale jeśli istnieją również lokalizacje, musisz odwrócić inżynierię pól, które mogą ulec zmianie, aby je przeanalizować. Wszystko to zależy od tego, czy narzędzie, o którym mowa, zostało napisane w ten sposób w różny sposób. – eh9

Odpowiedz

11

może złamać to na 2 części:

  1. Część okna
    z Java jesteś wykonywania polecenia systemu Windows - zewnętrznie do JVM w "Windows ziemi".Gdy klasa wykonawcza java wykonuje polecenie systemu Windows, korzysta z biblioteki DLL dla konsol &, tak więc pojawia się w oknach tak, jakby polecenie działało w konsoli. P: Kiedy uruchamiam C: \ windows \ system32 \ tasklist.exe w konsoli , jakie jest kodowanie znaków ("strona kodowa" w terminologii Windows) wyniku?

    • okna "chcp" polecenia bez argumentów podaje numer aktywnej strony kodowej dla konsoli (na przykład 850 dla Multilingual-Latin-1, 1252 do Latin-1). Patrz: Windows Microsoft Code Pages, Windows OEM Code Pages, Windows ISO Code Pages
      Domyślna strona kodowa systemu jest początkowo ustawiona zgodnie z ustawieniami regionalnymi systemu (wpisz systeminfo, aby to zobaczyć, lub Panel sterowania-> Region i język).
    • Windows OS/funkcja .NET getACP() również daje ta informacja

  2. The udział Java:
    Jak dekodować strumień java bajt z kodu Windows strona "x" (np. 850 lub 1252)?

    • pełne odwzorowanie liczby stronie kodu systemu Windows i ich odpowiedniki zestawu znaków Java może pochodzić z here - Code Page Identifiers (Windows)
    • Jednakże, w praktyce, jedną z następujących prefiksów może być dodany w celu osiągnięcia odwzorowanie:
      „” (none) dla ISO, "IBM" lub "x-IBM" dla OEM, "windows-" LUB "x-windows-" dla Microsoft/Windows.
      E.g. ISO-8859-1 lub IBM850 lub Windows-1252

Pełny Rozwiązanie:

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

Dzięki za Q! - było zabawne.

+0

To świetnie - skopiowałem aplikację 'notepad.exe' i przekazałem ją do' 0aéèçê.exe' i uruchomiłem. Mój oryginalny kod nie powiódł się (pokazuje kwadratowe znaki). Twoja wersja wypisała odpowiedni ciąg znaków (ze stroną kodową 850). – assylias

0

Istnieje znacznie lepszy sposób sprawdzenia uruchomionych procesów, a nawet uruchomienia polecenia systemu operacyjnego za pośrednictwem java: Process i ProcessBuilder.

Jeśli chodzi o zestaw znaków, zawsze można zapytać system operacyjny o obsługiwane zestawy znaków i uzyskać numer Encoder lub Decoder zgodnie z potrzebami.

[Edytuj] Złam to; nie ma sposobu, aby dowiedzieć się, w którym kodowaniu bajtów danego ciągu są, więc jedynym wyjściem jest uzyskanie tych bajtów, przesunięcie kolejności w razie potrzeby (jeśli kiedykolwiek jesteś w takim środowisku, w którym proces może dać ci tablicę bajtów w innej kolejności, użyj ByteBuffer, aby sobie z tym poradzić) i użyj wielu obsługiwanych CharsetDecoderów do zdekodowania bajtów do sensownego wyjścia.

To przesada i wymaga oszacowania, że ​​dane wyjście może być w UTF-8, UTF-16 lub innym kodowaniu. Ale pod co najmniej możesz dekodować dane wyjściowe za pomocą jednego z możliwych zestawów znaków, a następnie spróbuj użyć przetworzonego wyjścia do swoich potrzeb.

Ponieważ mówimy o procesie uruchamianym przez ten sam system operacyjny, w którym działa JVM, jest całkiem możliwe, że twoje wyjście będzie w jednym z kodowań Charset zwróconych przez metodę availableCharsets().

+0

Już używam procesu i wiem, jak określić zestaw znaków. Pytanie brzmi: jakiego zestawu znaków użyć. Mówisz "* zawsze możesz zapytać system operacyjny o obsługiwane zestawy znaków *": jak to robisz? Skąd mam wiedzieć, który z obsługiwanych zestawów znaków jest używany przez ten konkretny program? – assylias

+0

Używasz procesu, ale nie ProcessBuilder, który jest czystszy niż przy użyciu klasy Runtime. Właściwą metodą wywoływania dostępnych zestawów znaków jest Charset.availableCharsets(). Ale mimo to bezpieczniej byłoby przetestować zestaw znaków za pomocą metod zawartych w javadocs, które ci dałem - CharsetEncoder.canEncode(), detect(), etc ... – javabeats

+0

Przykro mi, ale nie rozumiem, jak to by działało. Czy możesz podać prosty przykład zastosowania swojej rekomendacji do mojego konkretnego przypadku użycia? – assylias

5

W rzeczywistości zestaw znaków używany przez tasklist jest zawsze inny niż domyślny system.

Z drugiej strony całkiem bezpiecznie jest używać wartości domyślnej, o ile moc wyjściowa jest ograniczona do ASCII. Zwykle moduły wykonywalne mają tylko nazwy ASCII w swoich nazwach.

Aby uzyskać poprawne łańcuchy, należy zamienić (ANSI) stronę kodową systemu Windows na stronę kodową OEM i przekazać ją jako zestaw znaków do InputStreamReader.

Wygląda na to, że nie ma kompleksowego odwzorowania między tymi kodowaniami. Poniższy mapowania można stosować:

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

Takie podejście pracował dla mnie z windows-1251 i IBM866 pary.

Aby uzyskać aktualne kodowanie OEM używane przez system Windows, można użyć funkcji GetOEMCP.Wartość zwracana zależy od ustawienia Język dla programów nieobsługujących kodu Unicode ustawienie Administracja zakładka w Panel sterowania dla regionu i języka. Aby zastosować zmianę, wymagane jest ponowne uruchomienie.


Istnieją dwa rodzaje kodowania w systemie Windows: ANSI i OEM.

To pierwsze jest używane przez aplikacje nie obsługujące kodu Unicode w trybie GUI.
Ten drugi jest używany przez aplikacje konsoli. Aplikacje konsoli nie mogą wyświetlać znaków, które nie mogą być reprezentowane w aktualnym kodowaniu OEM.

Od tasklist jest aplikacją trybu konsoli, jej wyjście jest zawsze w aktualnym kodowaniu OEM.

W przypadku systemów angielskich, para ta zwykle jest Windows-1252 i CP850.

Ponieważ jestem w Rosji, mój system ma następujące kodowania: Windows-1251 i CP866.
Gdybym przechwycić wyjście tasklist do pliku, plik nie może wyświetlać znaki cyrylicy poprawnie:

uzyskać ЏаЁўҐв zamiast Привет patrząc w Notatniku (Cześć!).
I µTorrent jest wyświetlany jako зTorrent.

Nie można zmienić kodowania używanego przez tasklist.


Istnieje jednak możliwość zmiany kodowania wyjściowego cmd. Jeśli przejdziesz na /u, wyświetli on wszystko w kodowaniu UTF-16.

cmd /c echo Hi>echo.txt 

Wielkość echo.txt wynosi 4: dwa bajty w bajtach dla Hi oraz dwa bajty do nowej linii (\r i \n).

cmd /u /c echo Hi>echo.txt 

Teraz wielkość echo.txt jest 8 bajtów: każdy znak jest reprezentowany z dwóch bajtów.

+0

Dziękuję za szczegółową i wyczerpującą odpowiedź - lepszą odpowiedź dla Glena Besta, ponieważ zapewnia ona pełny, działający przykład, więc wybrałem go, ale twój również był bardzo dobry. – assylias

3

Dlaczego nie używać systemu Windows API przez JNA, zamiast odradzać procesy? W ten sposób:

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

Napisałem podobną odpowiedź: elsewhere.

+0

Powyższe wypisuje tylko nazwę polecenia, a NIE całą linię poleceń. Czy istnieje proces pełnej linii poleceń? –

Powiązane problemy