Locked History Actions

swt

SWT

SWTはなぜDLLが要らない(ように見える)のか

大昔のSWTはjarファイルとDLLファイル(Windowsの場合)とに分離しており、これをアプリケーションで使うにはアプリ自体にはjarを付属させ、dllファイルは「パス」に置くしかなかった(起動時にjava.library.pathを指定してもよいが)。しかし、現在のswtはswt.jarのみをアプリに付属させれば良いようになっている。

しかし当然のことながら、相変わらずDLLは必要なのであり、しかもこれはOS側がロードするものなので、それぞれのDLLはそれぞれ一つのファイルとなっていなければならない。一体どうやっているのか?

以下はswt3.7での検証結果である。

jarの中にすべてのdllが含まれている

例えば、win32-win32-x86用のswt.jarを展開してみると、以下のようなファイルが現れる。

2011/09/05  10:43    <DIR>          .
2011/09/05  10:43    <DIR>          ..
2011/06/13  20:51               187 external.xpt
2011/09/05  10:43    <DIR>          META-INF
2011/09/05  10:43    <DIR>          org
2011/05/26  19:13            53,248 swt-awt-win32-3735.dll
2011/05/26  19:13           118,784 swt-gdip-win32-3735.dll
2011/05/26  19:13            77,824 swt-webkit-win32-3735.dll
2011/05/26  19:13            61,440 swt-wgl-win32-3735.dll
2011/05/26  19:13           430,080 swt-win32-3735.dll
2011/05/26  19:13            77,824 swt-xulrunner-win32-3735.dll
2011/06/13  20:51                13 version.txt
               8 個のファイル             819,400 バイト

このように、実はswt.jarのトップに必要なdllが格納された状態になっている。 これをどうやって実行時に「Windowsにロードさせる」だろうか?

DLLロードまでの経緯

ここで、実際にDLLがロードされるまでの経緯を追ってみる。これはswtのソースコードを見ればすぐにわかる。 例えば、org.eclipse.swt.awt.SWT_AWT.javaを見てみる。これは、SWTからSwingの機能を呼び出すためのクラスである。 冒頭に、以下の部分がある。

static synchronized void loadLibrary () {
        if (loaded) return;
        loaded = true;
        Toolkit.getDefaultToolkit();
        /*
        * Note that the jawt library is loaded explicitly
        * because it cannot be found by the library loader.
        * All exceptions are caught because the library may
        * have been loaded already.
        */
        try {
                System.loadLibrary("jawt");
        } catch (Throwable e) {}
        Library.loadLibrary("swt-awt");
}

とりあえず、「System.loadLibrary("jawt")」は無視し、「Library.loadLibrary」("swt-awt")に注目する。 このLibraryとは、org.eclipse.swt.internal.Libraryであるが、このloadLIbraryを見ると、

public static void loadLibrary (String name) {
        loadLibrary (name, true);
}
....
public static void loadLibrary (String name, boolean mapName) {
        String prop = System.getProperty ("sun.arch.data.model"); //$NON-NLS-1$
        if (prop == null) prop = System.getProperty ("com.ibm.vm.bitmode"); //$NON-NLS-1$
        if (prop != null) {
                if ("32".equals (prop) && IS_64) { //$NON-NLS-1$
                        throw new UnsatisfiedLinkError ("Cannot load 64-bit SWT libraries on 32-bit JVM"); //$NON-NLS-1$
                }
                if ("64".equals (prop) && !IS_64) { //$NON-NLS-1$
                        throw new UnsatisfiedLinkError ("Cannot load 32-bit SWT libraries on 64-bit JVM"); //$NON-NLS-1$
                }
        }
        
        /* Compute the library name and mapped name */
        String libName1, libName2, mappedName1, mappedName2;
        if (mapName) {
                String version = System.getProperty ("swt.version"); //$NON-NLS-1$
                if (version == null) {
                        version = "" + MAJOR_VERSION; //$NON-NLS-1$
                        /* Force 3 digits in minor version number */
                        if (MINOR_VERSION < 10) {
                                version += "00"; //$NON-NLS-1$
                        } else {
                                if (MINOR_VERSION < 100) version += "0"; //$NON-NLS-1$
                        }
                        version += MINOR_VERSION;               
                        /* No "r" until first revision */
                        if (REVISION > 0) version += "r" + REVISION; //$NON-NLS-1$
                }
                libName1 = name + "-" + Platform.PLATFORM + "-" + version;  //$NON-NLS-1$ //$NON-NLS-2$
                libName2 = name + "-" + Platform.PLATFORM;  //$NON-NLS-1$
                mappedName1 = mapLibraryName (libName1);
                mappedName2 = mapLibraryName (libName2);
        } else {
                libName1 = libName2 = mappedName1 = mappedName2 = name;
        }

        StringBuffer message = new StringBuffer();
        
        /* Try loading library from swt library path */
        String path = System.getProperty ("swt.library.path"); //$NON-NLS-1$
        if (path != null) {
                path = new File (path).getAbsolutePath ();
                if (load (path + SEPARATOR + mappedName1, message)) return;
                if (mapName && load (path + SEPARATOR + mappedName2, message)) return;
        }

        /* Try loading library from java library path */
        if (load (libName1, message)) return;
        if (mapName && load (libName2, message)) return;

        /* Try loading library from the tmp directory if swt library path is not specified */
        String fileName1 = mappedName1;
        String fileName2 = mappedName2;
        if (path == null) {
                path = System.getProperty ("user.home"); //$NON-NLS-1$
                File dir = new File (path, SWT_LIB_DIR);
                if ((dir.exists () && dir.isDirectory ()) || dir.mkdirs ()) {
                        path = dir.getAbsolutePath ();
                } else {
                        /* fall back to using the home dir directory */
                        if (IS_64) {
                                fileName1 = mapLibraryName (libName1 + SUFFIX_64);
                                fileName2 = mapLibraryName (libName2 + SUFFIX_64);
                        }
                }
                if (load (path + SEPARATOR + fileName1, message)) return;
                if (mapName && load (path + SEPARATOR + fileName2, message)) return;
        }
                
        /* Try extracting and loading library from jar */
        if (path != null) {
                if (extract (path + SEPARATOR + fileName1, mappedName1, message)) return;
                if (mapName && extract (path + SEPARATOR + fileName2, mappedName2, message)) return;
        }
        
        /* Failed to find the library */
        throw new UnsatisfiedLinkError ("Could not load SWT library. Reasons: " + message.toString()); //$NON-NLS-1$
}
}

前半は、与えられた名称をプラットフォームに合わせて適切な名称に変換している。この場合には「swt-awt」を「swt-awt-win32-3735」に変換しているものと思われる。後半はそれをロードするわけであるが、まずは、swt.library.pathや通常のjavaライブラリパスを優先してロードしてみる。

それが失敗すると、「user.home」のSWT_LIB_DIRからロードしようとする。このSWT_LIB_DIRはファイルの先頭で「SWT_LIB_DIR = ".swt" + SEPARATOR + "lib" + SEPARATOR + os() + SEPARATOR + arch();」などと定義されている。 例えば、Windows 7(32ビット)でユーザ名がfooの場合には、「c:\users\foo\.swt\lib\win32\x86」となる。

それにも失敗すると、

        /* Try extracting and loading library from jar */
        if (path != null) {
                if (extract (path + SEPARATOR + fileName1, mappedName1, message)) return;
                if (mapName && extract (path + SEPARATOR + fileName2, mappedName2, message)) return;
        }

として、jarファイルからdllを抜き出してそれをロードするが、これはextract,loadメソッドを見ればわかるが単純な処理である。 単にjar中のリソースとしてdllファイルをオープンし、先のパスのファイルとしてコピーし、それをSystem.loadLibraryでロードしている。

Java Web Startの場合

以上は、swtを使用するスタンドアロンアプリの場合だが、Java Web Startの場合でも全く同じになる。 通常、Java Web Startにネイティブライブラリを含めるには、nativelibというタグを使わなくてはならないが、swtの場合にはあたかもネイティブライブラリ無しのjarファイルであるかのように扱ってよい。

<resources os="Windows" arch="x86">
  <jar href="lib/win32-win32-x86/swt.jar"/>
</resources>

もちろん、swt.jarが署名されていることが前提である。署名しなければ、、「c:\users\foo\.swt\lib\win32\x86」のようなディレクトリに書きこむことはできず、そもそもネイティブライブラリの呼び出し自体ができない。