Java 無法直接存取硬體裝置,需透過 JNI (Java Native Interface) 呼叫平台原生程式碼。本方案採用:Java UI 層 (Swing) → JTwain 介面類 → jtwain.dll (C/C++ 實作 TWAIN 協定) → 掃描儀驅動程式。
package edu.ctgu.twain;
public class JTwain {
private static final JTwain INSTANCE = new JTwain();
private static final String DLL_NAME = "jtwain"; // jtwain.dll / libjtwain.so
private JTwain() {
loadLibrary();
}
public static JTwain getInstance() {
return INSTANCE;
}
// 原生方法宣告
public native boolean isTwainAvailable();
public native String[] getAvailableSources();
public native String acquire(); // 使用預設來源
public native String acquire(String sourceName); // 指定來源
private void loadLibrary() {
try {
System.loadLibrary(DLL_NAME);
} catch (UnsatisfiedLinkError e) {
System.err.println("載入 " + DLL_NAME + " 失敗: " + e.getMessage());
// 可嘗試 System.load("完絕對路徑") 作為備援
}
}
}
public class JTwacker extends JFrame {
private JTwain twain = JTwain.getInstance();
private JComboBox sourceCombo;
private JPEGPanel imagePanel; // 自訂 JPanel 顯示 BufferedImage
protected void acquireImage() {
if (!twain.isTwainAvailable()) return;
String source = (String) sourceCombo.getSelectedItem();
String filename = twain.acquire(source); // 阻塞直到掃描完成
if (filename != null && !filename.isEmpty()) {
showImage(new File(filename)); // 非同步載入顯示
}
}
protected void showImage(File file) {
new Thread(() -> {
try {
BufferedImage img = ImageIO.read(file); // 現代 API 替代 JPEGCodec
SwingUtilities.invokeLater(() -> imagePanel.setImage(img));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
// jtwain.cpp 關鍵片段
#include
#include // TWAIN 標頭檔
// 全域 TWAIN 狀態管理
static DSMENTRYPROC dsmEntry = NULL;
static TW_IDENTITY appId = {0};
static TW_IDENTITY srcId = {0};
static HWND hwnd = NULL; // 需要視窗控制代碼作為訊息泵
JNIEXPORT jboolean JNICALL Java_edu_ctgu_twain_JTwain_isTwainAvailable
(JNIEnv *env, jobject obj) {
return DSM_Entry(&appId, NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &hwnd) == TWRC_SUCCESS;
}
JNIEXPORT jobjectArray JNICALL Java_edu_ctgu_twain_JTwain_getAvailableSources
(JNIEnv *env, jobject obj) {
// 列舉來源:DG_CONTROL / DAT_IDENTITY / MSG_GETFIRST / MSG_GETNEXT
// 將來源名稱轉為 jobjectArray (String[]) 回傳
}
JNIEXPORT jstring JNICALL Java_edu_ctgu_twain_JTwain_acquire
(JNIEnv *env, jobject obj, jstring sourceName) {
// 1. 開啟來源
// 2. 協商能力 (ICAP_PIXELTYPE, ICAP_BITDEPTH, ICAP_XRESOLUTION 等)
// 3. 啟用掃描 (DG_CONTROL / DAT_USERINTERFACE / MSG_ENABLEDS)
// 4. 訊息迴圈處理 XFERDONE, CLOSEDSREQ 等事件
// 5. 接收影像資料 (DG_IMAGE / DAT_IMAGENATIVEXFER / MSG_GET)
// 6. 儲存為暫存檔 (JPEG/TIFF/PDF),回傳檔案路徑字串
}
| 挑戰 | 解決方案 |
|---|---|
| 視窗控制代碼 (HWND) 取得 | AWT/Swing 無直接 API,需用 JNA WindowUtils.getHWND() 或 JNI 透過 JAWT_GetAWT 取得 |
| 訊息迴圈阻塞 EDT | 掃描流程在原生執行緒跑訊息泵,Java 端用 Future/CompletableFuture 非同步等待結果 |
| 大影像記憶體管理 | 原生層分塊寫入暫存檔,避免一次性分配巨大記憶體;Java 端用 ImageIO 串流讀取 |
| 跨平台部署 | Windows: jtwain.dll;Linux: libjtwain.so (需 SANE backend);Mac: libjtwain.jnilib (需 TWAIN-SANE bridge) |
| 例外處理傳遞 | 原生層用 ThrowNew 拋出 Java 例外,保留堆疊追蹤 |
sane-backends + JNA 直接呼叫,免寫 JNIjtwain.dll 置於 java.library.path 路徑(或 -Djava.library.path 指定)