3f-lab и язык C: обращения к WinAPI
Предыдущий топик был посвящен взаимодействию Java и C. Были рассмотрены одна из самых «неудобоваримых» Java-технологии JNI и настройка 3f-lab на компиляцию C-программ под Linux в среде MinGW. Обилие терминов и теории наверняка оттолкнуло многих читателей: «А где примеры?». Ну что-ж, примеры так примеры. Рассмотрим Java-программу, которая умеет выключать компьютер посредством WinAPI.
Зачем такая программа нужна? Во-первых, чтобы потренироваться в освоении JNI на очень наглядном и достаточно простом примере. Во-вторых, эта программа — один из элементов борьбы с Counter Strike и прочими играми, которую я веду во вверенном мне кабинете информатики.

Сражаясь за порядок на уроках с обнаглевшими школьниками, я задумал и реализовал распределенную систему: на каждый ученический компьютер устанавливается RPC-клиент, который с некоторой периодичностью делает следующее:
* Отсылает на сервер список текущих процессов
* Проверяет, не пора ли
— выключить компьютер,
— выгнать принудительно пользователя из системы,
— сменить пароль
— убить какой-нибудь «незаконный» процесс.
Пока реализована лишь функция выключения ученических компьютеров по команде с учительского. Для начала это было реализовано на самом примитивном уровне. Поскольку Java умеет вызывать команды ОС, выключить компьютер из Java-программы можно так:
Думаю, тут объяснять ничего не надо. Но это решение из разряда Quick&Dirty. Для выключения компьютера лучше всего использовать функцию WinAPI, к которой можно обратиться с помощью JNI. Вот как это делается.
1. Пишем класс, в котором предусматриваем native-метод. Native-методы — это как раз то, что предполагается реализовать на C или другом низкоуровневом языке. Допустим, будет рассматриваемый класс находиться в ${LAB_HOME}/_projects/JNI_SAMPLE/util:
2. Создаем Makefile, в котором предусмотрены необходимые операции (ant здесь не подходит). Частично эти команды позаимствованы с сайта MinGW, частично адаптированы мной к среде 3f-lab:
3. Создаем (копипастим) заголовочный файл в соответствии с FAQ MinGW. В принципе можно всё из этого файла вычистить кроме двух первых и последней строк.
4. Находим заголовочные файлы JDK для Windows (${JDK_HOME}/include) и помещаем их в /_bin/mingw/include. Вот здесь интересный момент. Дело в том, что всё здесь описываемое происходит под Linux (несмотря на то, что компилируем программу для Windows). В JDK для Linux есть собственные заголовочные файлы JNI. Они лежат в /_bin/java/sun/include. Но если попытаться их задействовать — вылезет море ошибок, т.к. программы-то для винды/ Ее главный заголовочный JNI-файл хоть и называется точно так же — jni.h — но устроен совсем не так, как линуксёвый.
5. Сам код для выключения компьютера копипастим с сайта Microsoft по этой ссылке. Я на его анализе останавливаться не буду. И так здесь всё сложно. Скажу только, что вылизан он «как котовы яйца», компилируется влет, не требует никакого линкажа, короче говоря можно копировать даже не задумываясь о том, как там всё работает.
6. Собираем из кусочков программу на C (win32teacher.c):
7. Вводим в консоли
Я, признаться, очень удивился, когда вся эта шняга, напоминающая бред обкуренного индуса, откомпилировалась без ошибок с первого раза и в каталоге появился файл win32teacher.dll. Но еще больше выпал в осадок, когда чуть ли не с первого раза всё это заработало под Windows и сделало именно то, что требовалось:

Вот о таких случаях и говорится — «Удача — награда за смелость». Ввязываясь в эксперименты с JNI планировал затратить на них никак не меньше двух ночей, а получилось за пару часов (хотя предварительно хорошенько подготовился теоретически). Так что пользуйтесь, рекомендую. Будут вопросы — помогу (если, конечно, до этого места кто-то дочитал).
Зачем такая программа нужна? Во-первых, чтобы потренироваться в освоении JNI на очень наглядном и достаточно простом примере. Во-вторых, эта программа — один из элементов борьбы с Counter Strike и прочими играми, которую я веду во вверенном мне кабинете информатики.

Сражаясь за порядок на уроках с обнаглевшими школьниками, я задумал и реализовал распределенную систему: на каждый ученический компьютер устанавливается RPC-клиент, который с некоторой периодичностью делает следующее:
* Отсылает на сервер список текущих процессов
* Проверяет, не пора ли
— выключить компьютер,
— выгнать принудительно пользователя из системы,
— сменить пароль
— убить какой-нибудь «незаконный» процесс.
Пока реализована лишь функция выключения ученических компьютеров по команде с учительского. Для начала это было реализовано на самом примитивном уровне. Поскольку Java умеет вызывать команды ОС, выключить компьютер из Java-программы можно так:
try{
Runtime.getRuntime().exec("shutdown -t 0 -s -f");
}
catch(java.io.IOException ioex){}Думаю, тут объяснять ничего не надо. Но это решение из разряда Quick&Dirty. Для выключения компьютера лучше всего использовать функцию WinAPI, к которой можно обратиться с помощью JNI. Вот как это делается.
1. Пишем класс, в котором предусматриваем native-метод. Native-методы — это как раз то, что предполагается реализовать на C или другом низкоуровневом языке. Допустим, будет рассматриваемый класс находиться в ${LAB_HOME}/_projects/JNI_SAMPLE/util:
package com.michaelbelyakov1967.projects.SCHOOL_BLOCKER.util;
public class Main{
public native void halt(String msg, int timeout);
static{System.loadLibrary("win32teacher");}
public static void main(String[] args){
new Main().halt("Astalavista, baby", 10); // Сообщение пользователю и время задержки
}
}2. Создаем Makefile, в котором предусмотрены необходимые операции (ant здесь не подходит). Частично эти команды позаимствованы с сайта MinGW, частично адаптированы мной к среде 3f-lab:
#
#
#
MINGWGCC=/_bin/mingw/bin/i386-mingw32-gcc
JNI=/_bin/java/sun/bin/javah
JAVAC=/_bin/java/sun/bin/javac
all: jni gcc
gcc:
${MINGWGCC} -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at \
-I/_bin/mingw/include/java \
-I/_bin/mingw/include/java/win32 \
-shared -o win32teacher.dll \
win32teacher.c
jni:
${JAVAC} -cp /_classes -d /_classes Main.java
${JNI} -verbose -jni -classpath /_classes \
com.michaelbelyakov1967.projects.SCHOOL_BLOCKER.util.Main
3. Создаем (копипастим) заголовочный файл в соответствии с FAQ MinGW. В принципе можно всё из этого файла вычистить кроме двух первых и последней строк.
#ifndef MINGW_DLL_H__
#define MINGW_DLL_H__
struct STRUCT_DLL {
int count_int;
int* ints;
};
int func_dll(
int an_int,
char* string_filled_in_dll,
struct STRUCT_DLL* struct_dll
);
#endif4. Находим заголовочные файлы JDK для Windows (${JDK_HOME}/include) и помещаем их в /_bin/mingw/include. Вот здесь интересный момент. Дело в том, что всё здесь описываемое происходит под Linux (несмотря на то, что компилируем программу для Windows). В JDK для Linux есть собственные заголовочные файлы JNI. Они лежат в /_bin/java/sun/include. Но если попытаться их задействовать — вылезет море ошибок, т.к. программы-то для винды/ Ее главный заголовочный JNI-файл хоть и называется точно так же — jni.h — но устроен совсем не так, как линуксёвый.
5. Сам код для выключения компьютера копипастим с сайта Microsoft по этой ссылке. Я на его анализе останавливаться не буду. И так здесь всё сложно. Скажу только, что вылизан он «как котовы яйца», компилируется влет, не требует никакого линкажа, короче говоря можно копировать даже не задумываясь о том, как там всё работает.
6. Собираем из кусочков программу на C (win32teacher.c):
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include "win32teacher.h"
#include "com_michaelbelyakov1967_projects_SCHOOL_BLOCKER_util_Main.h"
BOOL systemShutdown( LPTSTR lpMsg, int t)
{
HANDLE hToken; // handle to process token
TOKEN_PRIVILEGES tkp; // pointer to token structure
BOOL fResult; // system shutdown flag
// Get the current process token handle so we can get shutdown
// privilege.
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return FALSE;
// Get the LUID for shutdown privilege.
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
&tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1; // one privilege to set
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Get shutdown privilege for this process.
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
(PTOKEN_PRIVILEGES) NULL, 0);
// Cannot test the return value of AdjustTokenPrivileges.
if (GetLastError() != ERROR_SUCCESS)
return FALSE;
// Display the shutdown dialog box and start the countdown.
fResult = InitiateSystemShutdown(
NULL, // shut down local computer
lpMsg, // message for user
t, // time-out period, in seconds
FALSE, // ask user to close apps
TRUE); // reboot after shutdown
if (!fResult)
return FALSE;
// Disable shutdown privilege.
tkp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
(PTOKEN_PRIVILEGES) NULL, 0);
return TRUE;
}
JNIEXPORT void JNICALL Java_com_michaelbelyakov1967_projects_SCHOOL_1BLOCKER_util_Main_halt (JNIEnv *env, jobject obj, jstring msg, jint n){
systemShutdown((*env)->GetStringUTFChars(env, msg, 0), n);
}
7. Вводим в консоли
makeЯ, признаться, очень удивился, когда вся эта шняга, напоминающая бред обкуренного индуса, откомпилировалась без ошибок с первого раза и в каталоге появился файл win32teacher.dll. Но еще больше выпал в осадок, когда чуть ли не с первого раза всё это заработало под Windows и сделало именно то, что требовалось:

Вот о таких случаях и говорится — «Удача — награда за смелость». Ввязываясь в эксперименты с JNI планировал затратить на них никак не меньше двух ночей, а получилось за пару часов (хотя предварительно хорошенько подготовился теоретически). Так что пользуйтесь, рекомендую. Будут вопросы — помогу (если, конечно, до этого места кто-то дочитал).

Комментарии (3)
RSS свернуть / развернутьMarkony
Gangsta
yababay
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.