天天看點

JNA加載DLL及在jar中的運用

文章目錄

      • 需求相關
      • DLL相關
      • JNA相關
      • 使用JNA加載DLL
        • 在jar中使用JNA加載DLL

需求相關

  • 需求

    使用JNI方式加載DLL,并封裝為SDK供他人使用。

  • 方法
    • 原生JNI方式
    • 調用JNA架構
  1. 原生JNI方式适合自行定制的dll,也就是在擁有dll源碼的情況下,可操作性比較強;不過如果方法太多以及涉及結構體之類,需要自己手寫許多類型轉換
  2. JNA通用于各種場景,在類型轉換上比較省功夫,懶人必備

此次目标是一個隻有頭檔案的windows32位dll檔案,是以選擇使用JNA。

注意,因為是win32平台,是以全程使用的32位的JDK,這裡用的jdk_1.8.152_x86。

DLL相關

當然為了踩坑有條件的話還是自已建立dll來嘗試。是以我選擇祭出宇宙第一IDE,用牛刀來殺個雞—生成一個簡單的dll檔案。

頭檔案:

#pragma once

#define DLL_API _declspec(dllexport) 

extern "C" {
	DLL_API int getVersion(void);
	DLL_API int add(int a, int b);
}
           

源碼:

#include "pch.h"
#include "soft.h"

int getVersion(void) {
	return 2;
}

int add(int a, int b) {
	return a + b;
}
           

接下來自然是生成DLL。這裡有一些配置要注意:

  1. 使用Release/win32來生成解決方案
  2. 選擇“在靜态庫中使用MFC”

如圖配置即可:

JNA加載DLL及在jar中的運用

如果不這樣配置,将會出現Can’t find dependent libraries問題,大概意思是目前的dll要依賴另外的dll,但在目前環境下找不到。具體依賴可以使用dependencywalker進行檢測查詢,出現下圖類似的情況就說明缺少依賴:

JNA加載DLL及在jar中的運用

配置好了,就可以撿到一個新鮮的dll檔案。

好,現在我們模拟一種情況—源碼丢失了。這樣就變成了最開始提到的需求。

JNA相關

JNA是一個成熟的架構,當然是有組織的。各路好漢都可以在github上得到最新版本,傳送門在此:

jna

目前最新版本是5.5.0。這裡也直接轉述其jar的maven位址:

jna-5.5.0

jna-platform-5.5.0

兩個包,前者相當于核心包,後者類似于插件包。一般情況下下載下傳前者即可。

然後…

你會發現這個jar包真大。

JNA加載DLL及在jar中的運用

目前是1.4M大小,而我們在上一步生成的Dll檔案的大小:

JNA加載DLL及在jar中的運用

牛刀殺雞 X 2。

此事萬不能忍,得削它。

通過打開jar包我們可以看到:

JNA加載DLL及在jar中的運用

這一看就是為各種環境準備的相容包,這裡我們隻需要win32位的,得想辦法得到源碼并删減它,再重新生成一個jna的jar包出來。

然而在maven上提供的jna-5.5.0-sources.jar源碼包中并沒有這些平台相關的東西,是以隻能手動合一下。好在這裡平台相關的相容包中并不是class檔案,而是各類so檔案、dll檔案、jnilib檔案等。是以可以直接合。

具體操作:

  1. 把jna-5.5.0-sources.jar源碼包中的代碼全部複制出來
  2. 把jna-5.5.0.jar目錄下的所需要的環境相容包複制出來
  3. 生成jar包

在IDEA中大概是這樣:

JNA加載DLL及在jar中的運用

這裡隻需要win32-x86的環境支援,是以隻複制了這一個環境的相容包,可以看到,這裡隻需要添加jnidispatch.dll檔案即可。

随後配置一下artifacts,快捷生成jar包:

JNA加載DLL及在jar中的運用

如此就撿到了一個大小288KB的僅支援win32-x86的jna包:

JNA加載DLL及在jar中的運用

雖然還是有點大,但先湊合用吧。

使用JNA加載DLL

先在外部工程中測試一下。首先将之前生成的SimpleDll.dll檔案放到工程根目錄下,然後隻需要寫一個繼承Libary的接口,加載dll以及聲明dll中的方法:

import com.sun.jna.Library;
import com.sun.jna.Native;

public interface ISimpleDll extends Library {
	ISimpleDll INSTANCE = Native.loadLibrary("SimpleDll", ISimpleDll.class);

	int getVersion();

	int add(int a, int b);
}
           

最後調用即可:

public class Main {

    public static void main(String[] args) {
        System.out.println(ISimpleDll.INSTANCE.getVersion());
        System.out.println(ISimpleDll.INSTANCE.add(1,2));
    }
}
           

輸出:

2
3
           

其目錄結構如下:

JNA加載DLL及在jar中的運用

至此說明JNA包和DLL檔案均為正常。

在jar中使用JNA加載DLL

在jar包中使用JNA加載dll,最大的問題其實是路徑問題,也就是怎樣讓jna在jar包仍然可以找到相應的dll檔案。

将SimpleDll檔案放到Libary Module的根目錄,并打出jar包:

JNA加載DLL及在jar中的運用

然後使用的時候提示:

Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'SimpleDll':
找不到指定的子產品。

找不到指定的子產品。

找不到指定的子產品。

Can't obtain InputStream for win32-x86/SimpleDll.dll
	at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:302)
	at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:455)
	at com.sun.jna.Library$Handler.<init>(Library.java:192)
	at com.sun.jna.Native.loadLibrary(Native.java:646)
	at com.sun.jna.Native.loadLibrary(Native.java:630)
	at SomeSDK.<init>(SomeSDK.java:16)
	at SomeSDK.<init>(SomeSDK.java:3)
	at SomeSDK$Holder.<clinit>(SomeSDK.java:6)
	at SomeSDK.getInstance(SomeSDK.java:10)
	at Main.main(Main.java:4)
           

似乎是想win32-x86目錄下去找這個dll檔案,那簡單—

改一下artifacts的配置,手動添加一個目錄就行:

JNA加載DLL及在jar中的運用

果然就可以了。

新版的JNA似乎在對路徑上的查找上作了一些改變。

最後,所有JAVA代碼上傳到github上了。

JnaDemo

最後的最後,再次強調,本次調試全程使用的win32的dll,以及32位的JDK。