Легкий и кроссплатформенный способ вызова нативного кода из JavaScript с помощью Java Applet
Ожидает приглашения
Хочу поделиться опытом написания апплетов, взаимодействующих с нативным кодом. Мне это было нужно для того, чтобы вызывать из JavaScript криптографические функции, описанные в нативных библиотеках. Предполагаю, что это может быть полезно кому-то ещё.
Апплеты всегда были слабым местом в Java. Но с появлением новой версии Deploy Plugin в Java 1.6.10 всё изменилось. Теперь апплет представляет собой полноценное JNLP приложение, запускаемое в браузере, доступ к которому осуществляется так же как и классическим апплетам. Подробней об этом вы можете почитать например тут, но я коснусь лишь тех аспектов которые относятся к нашей теме.
Итак, нам нужен апплет, который умеет загружать нативную библиотеку и вызывать её метод.
Код библиотеки для Windows:
И Linux:
Ну и заголовочный файл:
Процесс сборки нативных библиотек я опускаю, это выходит за рамки данной статьи.
Для вызова нативных методов мы будем использовать JNA, как самый гуманный из известных мне фреймворков для этих целей.
Код интерфейса к библиотекам:
Код апплета выглядит следующим образом:
Страничка с апплетом:
Для запуска апплета мы будем использовать Java Deployment Toolkit с API на JavaScript, которое описано тут (http://download.oracle.com/javase/6/docs/technotes/guides/jweb/deployment_advice.html).
Сборка
Программисты ленивы. Поэтому они придумали Maven, который будет делать за них всё. Мы не исключение, и для своих целей так же будем его использовать. Нам понадобится Webstart Maven Plugin который умеет делать следующее:
Апплеты всегда были слабым местом в Java. Но с появлением новой версии Deploy Plugin в Java 1.6.10 всё изменилось. Теперь апплет представляет собой полноценное JNLP приложение, запускаемое в браузере, доступ к которому осуществляется так же как и классическим апплетам. Подробней об этом вы можете почитать например тут, но я коснусь лишь тех аспектов которые относятся к нашей теме.
Итак, нам нужен апплет, который умеет загружать нативную библиотеку и вызывать её метод.
Код библиотеки для Windows:
#include <stdio.h>
#include <process.h>
#include "getpid.h"
int myGetPid () {
printf("\nHello World from C\n");
return getpid();
}
И Linux:
#include <unistd.h>
#include <stdio.h>
#include "getpid.h"
int myGetPid(){
return getpid();
}
Ну и заголовочный файл:
int myGetPid();
Процесс сборки нативных библиотек я опускаю, это выходит за рамки данной статьи.
Для вызова нативных методов мы будем использовать JNA, как самый гуманный из известных мне фреймворков для этих целей.
Код интерфейса к библиотекам:
package ru.habr.applet;
import com.sun.jna.Library;
public interface GetPidLib extends Library{
int myGetPid();
}
Код апплета выглядит следующим образом:
package ru.habr.applet;
import com.sun.jna.Native;
import javax.swing.JApplet;
public class AppletWithLibTest extends JApplet {
private static GetPidLib LIB;
static {
LIB = (GetPidLib) Native.loadLibrary("getpid", GetPidLib.class);
}
public int getPid() {
return LIB.myGetPid();
}
}
Страничка с апплетом:
<?xml version="1.0" encoding="Windows-1251"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Applet sample</title>
</head>
<body>
<script type="text/javascript" src="http://www.java.com/js/deployJava.js"></script>
<script type="text/javascript">
var attributes = {
width: 1,
height: 1,
id: "applet"
};
var parameters = {jnlp_href:"launch.jnlp"};
deployJava.runApplet(attributes, parameters, "1.6");ы
function showPid() {
var pid = document.getElementById("applet").getPid();
alert("PID: " + pid);
}
</script>
<input type="button" onclick="showPid()" value="Get PID"/>
</body>
</html>
Для запуска апплета мы будем использовать Java Deployment Toolkit с API на JavaScript, которое описано тут (http://download.oracle.com/javase/6/docs/technotes/guides/jweb/deployment_advice.html).
Сборка
Программисты ленивы. Поэтому они придумали Maven, который будет делать за них всё. Мы не исключение, и для своих целей так же будем его использовать. Нам понадобится Webstart Maven Plugin который умеет делать следующее:
- Создавать ключи для ЭЦП
- Удалять подписи из уже подписанных Jar
- Подписывать все зависимости
- Формировать JNLP файл
Для генерации JNLP файла он использует Velocity шаблон, который мы переопределим, т.к. стандартный ничего не знает о новой спецификации JNLP.
<?xml version="1.0" encoding="utf-8"?> <jnlp spec="$jnlpspec" codebase="" href="$outputFile"> <information> <title>$project.name</title> <vendor>$project.organization.name</vendor> <homepage href="$project.url"/> #if($offlineAllowed) <offline-allowed/> #end </information> <security> <all-permissions/> </security> <update check="always" policy="always"/> <resources> <j2se version="$j2seVersion"/> <jar href="${project.artifactId}-${project.version}.jar" download="eager"/> #foreach( $dependency in $project.dependencies ) #if ( !$dependency.classifier ) <jar href="${dependency.artifactId}-${dependency.version}.jar" download="lazy"/> #end #end </resources> <resources os="Windows"> #foreach( $dependency in $project.dependencies ) #if ( $dependency.classifier == 'natives-win' ) <nativelib href="${dependency.artifactId}-${dependency.version}-${dependency.classifier}.jar" download="eager"/> #end #end </resources> <resources os="Linux"> #foreach( $dependency in $project.dependencies ) #if ( $dependency.classifier == 'natives-linux' ) <nativelib href="${dependency.artifactId}-${dependency.version}-${dependency.classifier}.jar" download="eager"/> #end #end </resources> <applet-desc name="Habrahabr sample applet" main-class="$mainClass" width="1" height="1"> </applet-desc> </jnlp>
Обратите внимание, что размер апплета мы указываем в 1 пиксель — иначе браузеры на WebKit «оптимизируют» страницу и не загрузят его.
Пишем pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>habr</groupId> <artifactId>habr</artifactId> <version>1.0</version> <name>Sample Applet for Habr</name> <organization> <name>Habr</name> <url>http://habrahabr.ru</url> </organization> <build> <plugins> <plugin> <groupId>org.codehaus.mojo.webstart</groupId> <artifactId>webstart-maven-plugin</artifactId> <version>1.0-beta-1</version> <executions> <execution> <phase>package</phase> <goals> <goal>jnlp-single</goal> </goals> </execution> </executions> <configuration> <resourcesDirectory>src/main/jnlp</resourcesDirectory> <excludeTransitive>true</excludeTransitive> <verbose>false</verbose> <jnlp> <mainClass>ru.habr.applet.AppletWithLibTest</mainClass> <inputTemplate>src/main/jnlp/jnlptemplate.vm</inputTemplate> </jnlp> <jnlpFiles> <jnlpFile> <outputFile>applet.jnlp</outputFile> </jnlpFile> </jnlpFiles> <sign> <keystore>${basedir}/keystore.jks</keystore> <keypass>habrahabr</keypass> <storepass>habrahabr</storepass> <alias>habrahabr</alias> <dnameCn>Habrahabr</dnameCn> <dnameOu>Habrahabr</dnameOu> <dnameO>Habrahabr</dnameO> <dnameL>Moscow</dnameL> <dnameSt>Moscow</dnameSt> <dnameC>RU</dnameC> <keystoreConfig> <delete>true</delete> <gen>true</gen> </keystoreConfig> </sign> <unsign>true</unsign> <unsignAlreadySignedJars>true</unsignAlreadySignedJars> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>com.sun.jna</groupId> <artifactId>jna</artifactId> <version>3.0.9</version> </dependency> <dependency> <groupId>ru.habr</groupId> <artifactId>mygetpid</artifactId> <version>1.0</version> <classifier>natives-win</classifier> </dependency> <dependency> <groupId>ru.habr</groupId> <artifactId>mygetpid</artifactId> <version>1.0</version> <classifier>natives-linux</classifier> </dependency> </dependencies> </project>
Предварительно нужно положить в локальный репозиторий jar-файлы с нативными библиотеками.
Сделать это можно с помощью maven install plugin:
$ mvn install:install-file -DgroupId=ru.habr -DartifactId=mygetpid -Dversion=1.0 -Dclassifier=natives-linux -Dpackaging=jar -Dfile=libgetpid.so.jar $ mvn install:install-file -DgroupId=ru.habr -DartifactId=mygetpid -Dversion=1.0 -Dclassifier=natives-win -Dpackaging=jar -Dfile=getpid.dll.jar
Обратите внимание, что для разных платформ разные classifier, по которым потом происходит фильтрация ресурсов в JNLP шаблоне.
Осталось запустить сборку:
$ mvn package
Результатом будет файл target/habr-1.0.zip, который будет содержать дистрибутив нашего апплета. Так же в target/jnlp он будет в распакованном виде, откуда его можно запустить:
$ firefox target/jnlp/index.html
Исходники можно найти тут:
git://github.com/nimizgit/applet-example.git