Как стать автором
Обновить

Легкий и кроссплатформенный способ вызова нативного кода из JavaScript с помощью Java Applet

Хочу поделиться опытом написания апплетов, взаимодействующих с нативным кодом. Мне это было нужно для того, чтобы вызывать из JavaScript криптографические функции, описанные в нативных библиотеках. Предполагаю, что это может быть полезно кому-то ещё.

Апплеты всегда были слабым местом в 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
    

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.