Go编译DLL与SO

1. 简介

将Go编译成DLL/SO供其他语言调用。

  • .DLL:文件是 Windows 操作系统的动态链接库文件。
  • .SO 文件是 Unix、Linux 和其他类 Unix 系统的共享库文件。

2. Go编译DLL/SO

注意

  1. export后面导出的方法名一定要大写。
package main

/*
#include <stdlib.h>
*/
import "C"
import "strings"

//export Sum
func Sum(a, b C.int) C.int {
	return a + b
}

//export ToLower
func ToLower(c *C.char) *C.char {
	s := C.GoString(c)
	return C.CString(strings.ToLower(s))
}

func main() {
}

编译成SO

go build -buildmode=c-shared -o example.so

编译成DLL

go build -buildmode=c-shared -o example.dll

3. Python调用DLL/SO

调用SO

import ctypes
from ctypes import cdll

example = cdll.LoadLibrary('./example.so')
print(example.Sum(1, 2))  # 3

to_lower = example.ToLower
to_lower.argtypes = [ctypes.c_char_p]
to_lower.restype = ctypes.c_char_p
print(to_lower(b'HELLO WORLD'))  # b'hello world'

调用DLL(未测试)

import ctypes

example = ctypes.CDLL("./example.so")

print(example.Sum(1, 2))  # 3

to_lower = example.ToLower
to_lower.argtypes = [ctypes.c_char_p]
to_lower.restype = ctypes.c_char_p
print(to_lower(b'HELLO WORLD'))  # b'hello world'
Go 编译的 `.so`(Linux)、`.dll`(Windows)或 `.dylib`(macOS)打包进 Java 的 JAR 文件,并在运行时自动提取和加载,是一种常见的“无缝集成”需求。这种做法可以实现 **跨平台 native 库的自包含部署**,避免用户手动安装依赖。 下面详细介绍如何使用 **JNA + 自动资源提取机制** 实现这一目标。 --- ## ✅ 目标 - 将 Go 编写的动态库(如 `libmath.so`, `math.dll`)嵌入到 JAR 中 - 在 Java 程序启动时自动提取到临时目录 - 使用 `Native.load(...)` 加载该库 - 支持多平台(Windows/Linux/macOS) --- ## 🧱 步骤概览 1. 用 Go 编写函数并编译为 `.dll` / `.so` / `.dylib` 2. 将生成的 native 库放入 `resources/native/` 目录 3. 在 Java 中通过 `ClassLoader.getResourceAsStream()` 提取文件 4. 保存到系统临时目录(带唯一名称) 5. 使用 JNA 加载本地路径的库 --- ## 🔧 第一步:Go 编译为 C 共享库 ### 示例:`math.go` ```go package main import "C" //export Add func Add(a, b C.int) C.int { return a + b } //export Multiply func Multiply(a, b C.int) C.int { return a * b } func main() {} ``` ### 编译命令(生成各平台库) #### Windows (64-bit) ```bash GOOS=windows GOARCH=amd64 go build -buildmode=c-shared -o resources/native/windows-x86-64/math.dll math.go ``` #### Linux (64-bit) ```bash GOOS=linux GOARCH=amd64 go build -buildmode=c-shared -o resources/native/linux-x86-64/libmath.so math.go ``` #### macOS (Intel) ```bash GOOS=darwin GOARCH=amd64 go build -buildmode=c-shared -o resources/native/darwin-x86-64/libmath.dylib math.go ``` #### macOS (Apple Silicon M1+) ```bash GOOS=darwin GOARCH=arm64 go build -buildmode=c-shared -o resources/native/darwin-aarch64/libmath.dylib math.go ``` > ⚠️ 注意命名规范: > - Linux: `libxxx.so` > - Windows: `xxx.dll` > - macOS: `libxxx.dylib` --- ## 📁 第二步:项目结构示例 ``` src/ ├── main/ │ ├── java/ │ │ └── com/example/NativeMath.java │ └── resources/ │ └── native/ │ ├── windows-x86-64/math.dll │ ├── linux-x86-64/libmath.so │ ├── darwin-x86-64/libmath.dylib │ └── darwin-aarch64/libmath.dylib pom.xml (or build.gradle) ``` --- ## 💡 第三步:Java 代码 —— 自动提取并加载 native 库 ```java package com.example; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public interface NativeMath extends Library { // 单例实例(自动加载对应平台库) NativeMath INSTANCE = loadLibrary(); int Add(int a, int b); int Multiply(int a, b); /** * 自动检测平台、提取 native 库、返回 JNA 接口实例 */ static NativeMath loadLibrary() { try { // 1. 确定平台和库名 String platform = getPlatformSubPath(); String libName = getLibraryName(); // 如 math.dll 或 libmath.so // 2. 资源路径 String resourcePath = "/native/" + platform + "/" + libName; // 3. 提取到临时文件 Path tempFile = extractResourceToTemp(resourcePath, libName); // 4. 加载 JNA 库 return (NativeMath) Native.load(tempFile.toAbsolutePath().toString(), NativeMath.class); } catch (Exception e) { throw new RuntimeException("Failed to load native library", e); } } /** * 根据当前 OS 和架构确定子目录名 */ static String getPlatformSubPath() { String os = System.getProperty("os.name").toLowerCase(); String arch = System.getProperty("os.arch").toLowerCase(); if (os.contains("win")) { return "windows-x86-64"; } else if (os.contains("linux")) { return "linux-x86-64"; } else if (os.contains("mac") || os.contains("darwin")) { if ("aarch64".equals(arch) || "arm64".equals(arch)) { return "darwin-aarch64"; } else { return "darwin-x86-64"; } } else { throw new UnsupportedOperationException("Unsupported OS: " + os); } } /** * 获取平台特定的库文件名 */ static String getLibraryName() { if (Platform.isWindows()) { return "math.dll"; } else if (Platform.isLinux()) { return "libmath.so"; } else if (Platform.isMac()) { return "libmath.dylib"; } else { throw new UnsupportedOperationException("Unsupported platform"); } } /** * 从 classpath 提取资源到临时文件 */ static Path extractResourceToTemp(String resourcePath, String fileName) throws IOException { InputStream is = NativeMath.class.getResourceAsStream(resourcePath); if (is == null) { throw new FileNotFoundException("Native library not found in JAR: " + resourcePath); } // 创建临时文件(注意:deleteOnExit 不保证执行) Path temp = Files.createTempFile("jna-", "-" + fileName); temp.toFile().deleteOnExit(); try (is; FileOutputStream fos = new FileOutputStream(temp.toFile())) { byte[] buffer = new byte[8192]; int read; while ((read = is.read(buffer)) != -1) { fos.write(buffer, 0, read); } } // 设置可执行权限(Linux/macOS) if (!Platform.isWindows()) { temp.toFile().setExecutable(true, false); } System.out.println("Extracted native library to: " + temp); return temp; } } ``` --- ## 🧪 第四步:测试调用 ```java public class TestNativeMath { public static void main(String[] args) { System.out.println("Result of Add(3, 4): " + NativeMath.INSTANCE.Add(3, 4)); System.out.println("Result of Multiply(3, 4): " + NativeMath.INSTANCE.Multiply(3, 4)); } } ``` 输出: ``` Extracted native library to: /tmp/jna-12345-math.dll Result of Add(3, 4): 7 Result of Multiply(3, 4): 12 ``` --- ## ✅ 优点总结 | 特性 | 描述 | |------|------| | ✅ 自包含 | 所有 native 库都打包在 JAR 内 | | ✅ 跨平台支持 | 可根据运行环境选择正确的库 | | ✅ 部署简单 | 用户无需额外安装 DLL/SO | | ✅ 安全可控 | 来源明确,可做哈希校验 | --- ## ⚠️ 注意事项优化建议 ### 1. **临时文件清理问题** JVM 退出时不一定能删除临时文件(尤其崩溃时)。建议: - 使用 `deleteOnExit()`(基本可用) - 或定期清理旧版本缓存 ### 2. **重复提取问题** 每次启动都会重新提取。可加入版本哈希判断是否已存在有效副本。 ```java String expectedHash = "a1b2c3d4..."; // 存储资源的 SHA-256 Path cached = Paths.get(System.getProperty("java.io.tmpdir"), "native", "v1", libName); if (!Files.exists(cached) || !checkHash(cached, expectedHash)) { extractAndSave(resourcePath, cached); } return cached; ``` ### 3. **权限问题(Linux/macOS)** 确保 `.so` 和 `.dylib` 有执行权限: ```java temp.toFile().setExecutable(true, false); ``` ### 4. **防篡改校验(生产级推荐)** 在提取前验证资源完整性: ```java private boolean verifyChecksum(InputStream original, String expectedSha256) { ... } ``` --- ## 🛠️ 构建工具集成(Maven 示例) 确保资源被正确打包进 JAR: ```xml <build> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> </build> ``` 使用 `mvn package` 后检查 JAR 内容: ```bash jar -tf target/app.jar | grep math # 应看到: # native/windows-x86-64/math.dll # native/linux-x86-64/libmath.so ``` --- ## 🔄 替代方案:使用 JNI + GraalVM Native Image(高级) 如果你希望完全消除 JNA 开销,还可以考虑: - 使用 GraalVM 将 Go + Java 合并为一个 native 可执行文件 - 或使用 [jfrpc](https://github.com/glensc/jfrpc) 等框架进行进程间通信 但复杂度更高,适合高性能场景。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yimtcode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值