如何通过JNI调用无JNI导出函数的编译DLL?能否单独运行含导出函数的CPP文件?
嘿,针对你的三个问题,我来给你详细拆解一下:
因为目标DLL没有提供JNI规范的导出函数,所以我们需要做一个中间包装DLL——它负责暴露符合JNI要求的函数,同时内部去调用原DLL的逻辑。具体步骤如下:
生成JNI头文件
先写一个包含native方法的Java类,比如:public class CppDllCaller { static { System.loadLibrary("JniWrapper"); // 后续要加载的中间DLL } public native void invokeCppLogic(); public static void main(String[] args) { new CppDllCaller().invokeCppLogic(); } }编译这个类后,用
javah工具生成JNI头文件:javah -jni CppDllCaller会得到
CppDllCaller.h,里面定义了JNI函数的签名。编写中间包装DLL的C++代码
这个包装DLL需要加载原C++ DLL,并调用它的函数。如果原DLL的函数是C++类成员,还需要做对象实例化的适配。举个简单的动态加载示例:#include <jni.h> #include "CppDllCaller.h" #include <windows.h> // 定义原DLL中目标函数的指针类型 typedef void (*TargetFunc)(); JNIEXPORT void JNICALL Java_CppDllCaller_invokeCppLogic(JNIEnv*, jobject) { // 动态加载原DLL HINSTANCE hOriginalDll = LoadLibrary("YourOriginalCpp.dll"); if (hOriginalDll != nullptr) { // 获取原函数的地址 TargetFunc targetFunc = (TargetFunc)GetProcAddress(hOriginalDll, "targetFunctionName"); if (targetFunc != nullptr) { targetFunc(); // 调用原DLL的函数 } FreeLibrary(hOriginalDll); } }编译中间包装DLL
编译时要指定JNI的头文件路径($JAVA_HOME/include和$JAVA_HOME/include/win32),并链接必要的系统库(比如kernel32.lib)。以MSVC为例:cl /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" /LD JniWrapper.cpp kernel32.lib运行Java程序
确保中间DLL和原DLL都在Java的库搜索路径中,运行CppDllCaller即可。
JNA的核心优势是无需编写C/C++中间层,它通过Java接口直接映射DLL的函数签名,底层自动完成类型转换和调用逻辑。比如用JNA调用原DLL的代码:
import com.sun.jna.Library; import com.sun.jna.Native; public interface OriginalDll extends Library { OriginalDll INSTANCE = Native.load("YourOriginalCpp", OriginalDll.class); void targetFunctionName(); // 直接声明原DLL的函数 } // 调用示例 public class Main { public static void main(String[] args) { OriginalDll.INSTANCE.targetFunctionName(); } }
对比JNI,JNA省去了写包装层、编译DLL的繁琐步骤,对Java开发者更友好。但JNI也有优势:
- 性能略高(少了JNA的动态映射开销)
- 支持更复杂的内存管理、自定义类型转换
- 适合对性能要求极高或需要深度操控原生代码的场景
不行,.cpp是源代码文件,必须先编译成可执行文件(.exe)或DLL才能运行。不过你可以给带导出函数的.cpp加一个main入口,编译成.exe后就能单独执行,同时也能作为DLL被其他程序调用(虽然这种做法不常见,但技术上可行)。
举个例子:
#include <iostream> // 导出函数 extern "C" __declspec(dllexport) void myExportedFunc() { std::cout << "This is the exported function!" << std::endl; } // 主入口,用于单独运行 int main() { std::cout << "Running as executable..." << std::endl; myExportedFunc(); return 0; }
编译成.exe后,双击就能直接运行;如果把它当成DLL加载,也能调用myExportedFunc。不过更清晰的做法是分开编译:一个版本做exe,一个版本做dll。
内容的提问来源于stack exchange,提问作者hugo411

