3f-lab и язык C: обращения к WinAPI

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

Зачем такая программа нужна? Во-первых, чтобы потренироваться в освоении 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
);

#endif


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

#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 планировал затратить на них никак не меньше двух ночей, а получилось за пару часов (хотя предварительно хорошенько подготовился теоретически). Так что пользуйтесь, рекомендую. Будут вопросы — помогу (если, конечно, до этого места кто-то дочитал).
  • +2
  • 28 февраля 2010, 10:56
  • yababay

Комментарии (3)

RSS свернуть / развернуть
+
0
Bay!
avatar

Markony

  • 28 февраля 2010, 20:33
+
0
Очень красивый ход с native метом
avatar

Gangsta

  • 01 марта 2010, 19:27
+
0
Спасибо
avatar

yababay

  • 01 марта 2010, 20:51

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.