19.06.01 Laufende Prozesse ermitteln/beenden

In diesem Teil der Kapitel-Serie JNI für Windows bekommen Sie eine Möglichkeit gezeigt, wie Sie mit JNI auf einem Windows-Betriebssystem alle laufenden Prozesse auslesen und ggf. auch beenden können.

Der Java Code

Zuerst einmal müssen Sie einen Prozess in Java abbilden können. Hierzu erstellen Sie eine eigene Klasse. Diese Klasse beinhaltet die Attribute id (ID des Prozesses), parentId (ID des übergeordneten Prozesses), threadCount (Anzahl der Threads dieses Prozesses) und name (Name des Prozesses).

package de.jbb.jni;

public class Process {

  private int id;
  private int parentId;
  private int threadCount;
  private String name;
  
  public Process() {}
  
  public Process(int id, int parentId, int threadCount, String name) {
    this.id = id;
    this.parentId = parentId;
    this.threadCount = threadCount;
    this.name = name;
  }
  
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public int getParentId() {
    return parentId;
  }
  public void setParentId(int parentId) {
    this.parentId = parentId;
  }
  public int getThreadCount() {
    return threadCount;
  }
  public void setThreadCount(int threadCount) {
    this.threadCount = threadCount;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

Die Klasse ProcessReader ist für das Auslesen und Beenden der Prozesse zuständig, welche in einer java.util.List zwischengespeichert werden. Um die Prozesse auszulesen, wird die private und native Methode readProcessesC angelegt. Diese Methode wird von der öffentlichen Methode readProcesses aufgerufen. Um der Liste einen neuen Prozess hinzuzufügen, kann die C-Bibliothek via JNI die Methode addProcess aufrufen. terminateProcess beendet einen Prozess.

package de.jbb.jni;

import java.util.List;
import java.util.ArrayList;

public class ProcessReader {

  public List<Process> processes;

  public ProcessReader() {
    processes = new ArrayList<Process>();
  }

  private native void readProcessesC();
  public native void terminateProcess(int id);

  private void addProcess(String name, int id, int idParent, int threadCnt) {
    processes.add(new Process(id, idParent, threadCnt, name));
  }

  public List<Process> readProcesses() {
  
    processes.clear();
    readProcessesC();
    return processes;
  }

  public List<Process> getProcesses() {
    return processes;
  }
}

Der C-Code

Beim Zugriff auf Windows-Funktionen wird die Header-Datei windows.h verwendet. Speziell für die Auswertung der Prozesse wird zusätzlich noch der Header tlhelp32.h benötigt. Diese müssen Sie als ersten Schritt in Ihre C-Library einbinden. Zusätzlich wird gleich der Kopf der readProcessC-Methode eingesetzt.

#include <windows.h>
#include <tlhelp32.h>
#include <jni.h>
#include "de_jbb_jni_ProcessReader.h"

JNIEXPORT void JNICALL Java_de_jbb_jni_ProcessReader_readProcessesC (JNIEnv *env, jobject procRead) {}

In der readProcessC-Methode benötigen Sie folgende Variablen:

  • HANDLE hProcessSnapHANDLE, um die offenen Prozesse zu durchlaufen
  • PROCESSENTRY32 pe32 – Hierbei handelt es sich um eine Structure, welche später einen Prozess repräsentiert
  • jclass procCls – Die Klasse des ProcessReader-Objekts
  • jmethodID addMethod – Die Methode des ProcessReaders um einen neuen Prozess zu registrieren
HANDLE hProcessSnap;
PROCESSENTRY32 pe32;
jclass procCls = (*env)->GetObjectClass(env, procRead);
jmethodID addMethod = (*env)->GetMethodID(env, procCls, "addProcess", "(Ljava/lang/String;III)V");

hProcessSnap wird durch den Rückgabewert der Funktion CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) initialisiert. Dieser Aufruf liefert eine Momentaufnahme (Snapshot) der Prozesse und gibt diese als HANDLE zurück.

hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

Zur Sicherheit sollten Sie anschließend überprüfen, ob der zurückgegebene Wert auch valide ist:

if (hProcessSnap == INVALID_HANDLE_VALUE)  {
  return;
}

Damit die Prozesse auch korrekt durchlaufen und angezeigt werden können, müssen Sie noch die Größe der PROCESSENTRY32 Structure (dwSize) korrekt setzen. Dies wird mit folgender Zeile erledigt:

pe32.dwSize = sizeof(PROCESSENTRY32);

Über eine while-Schleife werden die einzelnen Prozesse nun durchlaufen. Mit dem Aufruf Process32Next(hProcessSnap, &pe32)) speichern Sie die Daten des nächsten Prozesses in der pe32 Variablen. Dieser Ausdruck gibt false zurück, falls im HANDLE hProcessSnap keine weiteren Prozesse mehr vorhanden sind. Er kann also direkt in der Schleife verwendet werden. In der Schleife selbst wird die addProcess-Methode unseres ProcessReaders aufgerufen. Als Übergabeparameter dienen die Attribute szExeFile (Prozessname), th32ProcessID (Prozess ID), th32ParentProcessID (ID des übergeordneten Prozesses) und cntThreads (Anzahl der Threads) der Structure PROCESSENTRY32.

while (Process32Next(hProcessSnap, &pe32)) {
  (*env)->CallVoidMethod(env, procRead, addMethod, 
      (*env)->NewStringUTF(env, pe32.szExeFile),
      pe32.th32ProcessID,
      pe32.th32ParentProcessID,
      pe32.cntThreads);
}

Abschließend muss der Schnappschuss noch geschlossen werden:

CloseHandle(hProcessSnap);

Um einen Prozess anhand seiner ID zu beenden, reicht ein einfacher Einzeiler:

TerminateProcess(OpenProcess(PROCESS_ALL_ACCESS, FALSE, id), 0);

Anhand eines Parameters (id) wird ein Prozess geladen. Dieser wird der Funktion TerminateProcess mit dem gewünschten Exit-Code übergeben.

Die gesamte C-Bibliothek sieht so aus:

#include <windows.h>
#include <tlhelp32.h>
#include <jni.h>
#include "de_jbb_jni_ProcessReader.h"

JNIEXPORT void JNICALL Java_de_jbb_jni_ProcessReader_readProcessesC (JNIEnv *env, jobject procRead) {

  HANDLE hProcessSnap;
  PROCESSENTRY32 pe32;
  jclass procCls = (*env)->GetObjectClass(env, procRead);
  jmethodID addMethod = (*env)->GetMethodID(env, procCls, "addProcess", "(Ljava/lang/String;III)V");

  hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hProcessSnap == INVALID_HANDLE_VALUE)  {
    return;
  }

  pe32.dwSize = sizeof(PROCESSENTRY32);

  while (Process32Next(hProcessSnap, &pe32)) {
    (*env)->CallVoidMethod(env, procRead, addMethod, 
        (*env)->NewStringUTF(env, pe32.szExeFile),
        pe32.th32ProcessID,
        pe32.th32ParentProcessID,
        pe32.cntThreads);
  }

  CloseHandle(hProcessSnap);
  return;
}

JNIEXPORT void JNICALL Java_de_jbb_jni_ProcessReader_terminateProcess (JNIEnv *env, jobject procRead, jint id) {
  TerminateProcess(OpenProcess(PROCESS_ALL_ACCESS, FALSE, id), 0);
  return;
}

Ausgabe

Mit Hilfe einer simplen Main-Klasse können Sie sich das Resultat anzeigen lassen:

package de.jbb.jni;

import java.util.List;

public class ProcessReaderMain {

  static {
    System.loadLibrary("readprocess"); 
  }

  public static void main(String[] args) {

    ProcessReader pr = new ProcessReader();
    List<Process> processes = pr.readProcesses();
    for (Process p : processes) {
      System.out.println(p.getName() + ": " + p.getId() + "/" + p.getParentId() + "/" + p.getThreadCount());
    }
  }
}

Die Ausgabe könnte wie folgt aussehen:

[System Process]: 0/0/2
System: 4/0/95
smss.exe: 400/4/3
csrss.exe: 456/400/15
winlogon.exe: 480/400/19
services.exe: 524/480/17
lsass.exe: 536/480/23
svchost.exe: 748/524/23
svchost.exe: 792/524/11
svchost.exe: 860/524/65
svchost.exe: 932/524/6
svchost.exe: 1012/524/14
spoolsv.exe: 1120/524/12
cam.exe: 1252/524/2
db2jds.exe: 1280/524/3
db2sec.exe: 1300/524/2
jqs.exe: 1340/524/8
NotificationAgent.exe: 1368/524/5
NTRtScan.exe: 1392/524/18
...

2 Replies to “19.06.01 Laufende Prozesse ermitteln/beenden”

  1. KaiH
    Dieser Kommentar wurde vom Kapitel 00.04.01 Stefan Kiesel am 11.02.2010 hierher verschoben.

    Hallo Stefan,

    coole Seite … aber ich hätte da mal ne Frage zu einem Beispiel von dir …

    http://www.java-blog-buch.de/19-06-laufende-prozesse-ermittelnbeenden/

    Besteht die Möglichkeit, dass du dort ergänzt, wie man dieses kompiliert und zum laufen bekommt? Irgendwie krieg ich zwar inzwischen eine .h und auch eine exe von MinGW

    kompiliert mit :
    gcc -Wall -shared -I „C:\Program Files\Java\jdk1.6.0_18\include“ -I „C:\Program Files\Java\jdk1.6.0_18\include\win32“ -o readprocess readprocess.c

    Wenn man nun allerdings ein System.load auf den Pfad der neu erzeugten Exe-Datei versucht bzw. damit den ProzessReader aufzurufen findet man folgende Fehlermeldung :

    Exception in thread „main“ java.lang.UnsatisfiedLinkError: de.jbb.jni.ProcessReader.readProcessesC()V
    at de.jbb.jni.ProcessReader.readProcessesC(Native Method)
    at de.jbb.jni.ProcessReader.readProcesses(ProcessReader.java:24)
    at de.jbb.jni.ProcessReaderMain.main(ProcessReaderMain.java:15)

    Vielen Dank im Voraus.

    Mit freundlichen Grüßen

    Kai H.

  2. Stefan Kiesel
    Dieser Kommentar wurde vom Kapitel 00.04.01 Stefan Kiesel am 11.02.2010 hierher verschoben.

    Hallo Kai,

    erst einmal zur Info: Ich werde diese beiden Kommentare demnächst auf die zugehörige Seite ( http://www.java-blog-buch.de/19-06-laufende-prozesse-ermittelnbeenden/ ) verschieben.

    Um auf die Frage einzugehen: Es handelt sich bei diesem Kapitel definitiv um kein Einsteigerkapitel, weshalb vorausgesetzt wurde, dass man die Basics bereits beherrscht. Aus eben diesen Grund werde ich das Kapitel auch nicht entsprechend ergänzen. Selbstverständlich gibt es aber einen vorhergehenden Artikel, der die ersten Schritte erklärt: http://www.java-blog-buch.de/1902-hello-jni-world/ . Das sollte helfen 🙂 .

    Gruß
    Stefan

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.