Music Studio项目心得--JNI实现C++调用JAVA

这个项目是我參加内蒙古挑战杯的比赛项目,因为时间关系,我没时间实现OpenOMR开源项目由JAVA全然向C++的转换,经过我半个多月的尝试,我将OpenOMR中的1/3的代码改写成C++,只是非常快我就发现,假设依照这个进度,我是不管怎样也无法按时完毕工作了,更重要的是Joone人工智能库的算法要是全然移植不是我一个大二学生可以在这么短的时间做到的,于是我放弃JAVA转C++的解决方。

取而代之的是,我使用JAVA做算法的核心,这样就能够用最小的代价高速完毕项目,而用C++去调用JAVA的方法,并封装成dll,最后使用C# + IrisSkin2 + 自绘空间的方式制作界面并实现业务逻辑(不要觉得C++的步骤多此一举,实际上我的母语是C++,所以做起来很顺手,反而使我的效率大幅度提升)。

以下说一下我在实现C++调用JAVA的过程中遇到的一些问题。

开发环境: Visual Studio 2010 Ultimate (英文版) + eclipse 3.6.1 + JDK 1.6.0_10

众所周知JAVA依赖jvm,并且运行起来很繁琐,我这个项目的命令行例如以下(批处理)

java -cp %cd%/joone-engine-2.0.0RC1/joone-engine.jar;%cd%/jfreechart-1.0.1/lib/jcommon-1.0.0.jar;.;%cd%/jfreechart-1.0.1/lib/jfreechart-1.0.1.jar -Xmx256m openomr.openomr.SheetMusic

不要说用户看这东西会不知所措,就是我看着这东西也是非常头疼(尽管我非常熟悉dos)

以下讲一下技术难点:

一、不依赖用户机器上的JAVA环境

解决方式:使用本地应用程序集成jre环境,脱离对客户机器jre的依赖

遇见的问题:最開始的时候我觉得仅仅须要附带jvm.dll就可以实现我的目的,代码例如以下

#include <iostream>
#include <Windows.h>
#include "jdk1.6.0_10/include/jni.h"

#ifdef _WIN32
#define PATH_SEPARATOR ‘;‘
#else
#define PATH_SEPARATOR ‘:‘
#endif

#pragma comment(lib, "jvm")

....................

int main()
{
HMODULE JVM_DLL;

// 获取程序自带JVM路径
CHAR JvmFileName[MAX_PATH] = {0};

GetModuleFileNameA(NULL, JvmFileName, MAX_PATH);
(*strrchr(JvmFileName, ‘//‘)) = ‘/0‘;
::strcat(JvmFileName, "//jvm.dll");

//获得JVM.DLL启动实体
//JVM_DLL = LoadLibraryA(JvmFileName);
if (JVM_DLL == NULL)
{
::MessageBoxA(NULL, "载入不了jvm.dll", "", MB_OK);
}

//JVM内部函数JNI_CreateJavaVM读取
JNICreateJavaVM createJavaVM = (JNICreateJavaVM)GetProcAddress(JVM_DLL, "JNI_CreateJavaVM");

if (createJavaVM == NULL)
{
::MessageBoxA(NULL, "JNI_CreateJavaVM函数读取失败!", "", MB_OK);
}
.......................
}

我将jvm.lib和jvm.dll都放在Debug目录中,我发现这时的程序能正确载入jvm.dll,可是运行

JNICreateJavaVM createJavaVM = (JNICreateJavaVM)GetProcAddress(JVM_DLL, "JNI_CreateJavaVM");

就提示失败了,为了解决问题我開始阅读jni的文档,可是收效甚微,于是開始在各大技术论坛開始寻找解决方式,可是没有找到解决方式,于是我将系统的jre中的jvm.dll载入到程序中,这次程序最终正常跑了起来。

小结:jvm.dll的执行依赖jre中很多dll,而我在项目初期仅仅外挂一个jvm.dll导致了jvm无法正常创建,顺便说一下,解决问题从下午5点一直到晚上接近十点才彻底解决,有时候人的惯性思维真的会限制住灵感。

技术难点二:带有文件夹结构的java类调用

这个纠结了小半天,直接贴代码了

#include <iostream>
#include <Windows.h>
#include "jdk1.6.0_10/include/jni.h"

#ifdef _WIN32
#define PATH_SEPARATOR ‘;‘
#else
#define PATH_SEPARATOR ‘:‘
#endif

#pragma comment(lib, "jvm")

using namespace std;

typedef jint (JNICALL *JNICREATEPROC)(JavaVM **, void **, void *);

//主函数名,也可改为其它名称,JVM以此查询启动接口。
const char MainName[] ="main";

//虚拟机启动參数总数。
const int JVMOptionCount = 5;

//JVM编译器设定,none为使用默认编译器。
static char Compiler[] = "-Djava.compiler=NONE";

//最小内存
static char MinMB[] = "-Xms256M";

//最大内存
static char MaxMB[] = "-Xmx512M";

//jar包中主函数class所在路径。
static char AppClass[] = "openomr/openomr/SheetMusic";

//须要运行的jar包所在路径,‘./‘为当前路径简写,多jar包以‘;‘切割。
static char ClassPath[] = "-Djava.class.path=./;E:/joone-engine-2.0.0RC1/joone-engine.jar;E:/jfreechart-1.0.1/lib/jcommon-1.0.0.jar;E:/jfreechart-1.0.1/lib/jfreechart-1.0.1.jar";

static char LibraryPath[] = "-Djava.library.path=./";

typedef jint (WINAPI* JNICreateJavaVM)(JavaVM**, JNIEnv**, void *);
/**
* Win主函数
**/
int main()
{
HMODULE JVM_DLL;

// 获取程序自带JVM路径
CHAR JvmFileName[MAX_PATH] = {0};

GetModuleFileNameA(NULL, JvmFileName, MAX_PATH);
(*strrchr(JvmFileName, ‘//‘)) = ‘/0‘;
::strcat(JvmFileName, "//jvm.dll");

//获得JVM.DLL启动实体
//JVM_DLL = LoadLibraryA(JvmFileName);
JVM_DLL = LoadLibraryA("E://project//Cpp调用Java//Debug//jdk1.6.0_10//jre//bin//client//jvm.dll");
if (JVM_DLL == NULL)
{
::MessageBoxA(NULL, "载入不了jvm.dll", "", MB_OK);
}

//JVM内部函数JNI_CreateJavaVM读取
JNICreateJavaVM createJavaVM = (JNICreateJavaVM)GetProcAddress(JVM_DLL, "JNI_CreateJavaVM");

if (createJavaVM == NULL)
{
::MessageBoxA(NULL, "JNI_CreateJavaVM函数读取失败!", "", MB_OK);
}

//指向本地方法调用接口
JNIEnv* env;
//表示Java虚拟机
JavaVM* jvm;

//设定JVM启动參数
JavaVMInitArgs vm_args;
JavaVMOption options[JVMOptionCount];

/**
*jtl(Java Tools Language)设定:JVM的缺省行为是用“即时”编译器(或JIT[字节代码编译器])运行字节码。
*当载入类时,JIT将类字节码转换成本机代码。使用JIT会导致在每一个类载入后有短暂延迟,
*但可提高程序的整体性能。在某些情况下,运行时间可缩短十分之中的一个。
*可用Compiler指定jtl,如将Compiler=foo后,该例中虚拟机将查找名为foo.dll的JIT编译器。
*搜索其他编译器是在jre/bin文件夹中和系统的PATH上进行的。
*若找不到这种编译器,虚拟机将缺省使用解释器。
*/
options[0].optionString = Compiler;
//类地址
options[1].optionString = ClassPath;
//lib地址
options[2].optionString = LibraryPath;
//最小内存
options[3].optionString = MinMB;
//最大内存
options[4].optionString = MaxMB;
//PS:此參数用于设定跟踪执行时的信息,暂不须要。
//options[3].optionString = "-verbose:jni";

//使用的jni版本号,眼下最高为JNI_VERSION_1_6。
vm_args.version = JNI_VERSION_1_6;
vm_args.options = options;
vm_args.nOptions = JVMOptionCount;
vm_args.ignoreUnrecognized = JNI_TRUE;

//启动JVM,并返回结果
int res = createJavaVM(&jvm, &env, &vm_args);

if (res < 0)
{
::MessageBoxA(NULL, "JVM启动失败!", "", MB_OK);
}

//查找目的类
jclass clazz = env->FindClass(AppClass);

if (clazz == 0)
{
::MessageBoxA(NULL, "JNI_CreateJavaVM函数读取失败!", "", MB_OK);

return cin.get();
}

//取得入口主函数序列号
jmethodID mid = env->GetStaticMethodID(clazz, MainName, "([Ljava/lang/String;)V");

if (0 == mid)
{
::MessageBoxA(NULL, "没有找到主函数!", "", MB_OK);

return cin.get();
}

cout << "env->CallStaticVoidMethod(clazz, mid, NULL);開始运行" << endl;
//main启动
env->CallStaticVoidMethod(clazz, mid, NULL);
cout << "env->CallStaticVoidMethod(clazz, mid, NULL);运行结束" << endl;

//JVM释放
jvm->DestroyJavaVM();

return cin.get();
}

期间遇到的主要问题就是在VS2010上调试程序,始终FindClass查找类失败,为此试了各种办法,先是调用jar,后来又把jar解压。。。都没有成功,于是乎。。。各种纠结。。。下午赶上腾讯的CF有活动,因此申请了个新QQ,去领了把M4A1-A + 防弹衣嘿嘿,小小的YD一下。。。晚上回来突然想法一个问题,就是VS2010的解决方式的文件夹结构问题

先看一下我的測试项目的文件夹结构-

大家注意,E:/project/Cpp调用Java/Debug是可运行文件生成的目录,我的库也是直接放在了这个目录内,可是这就导致了一个问题,我的代码和exe不再同一个目录,于是

static char ClassPath[] = "-Djava.class.path=./;E:/joone-engine-2.0.0RC1/joone-engine.jar;E:/jfreechart-1.0.1/lib/jcommon-1.0.0.jar;E:/jfreechart-1.0.1/lib/jfreechart-1.0.1.jar";

static char LibraryPath[] = "-Djava.library.path=./";

这里面的”./“在源代码中代表的路径就是E:/project/Cpp调用Java/Cpp调用Java

而在生成的exe中是E:/project/Cpp调用Java/

但因为VS2010的特性,它的工作文件夹不是exe所在文件夹,而是源代码所在文件夹,这就导致了

static char AppClass[] = "openomr/openomr/SheetMusic";

在加上工作文件夹组成的绝对路径错误,也就是真正导致FindClass失败的原因

小结:这个问题在非常诡异,须要在今后的开发中注意,不能再犯这样的低级错误了

哈哈,调用java方法成功了,我能够放松一下了。。。我去CF了。。。

时间: 2024-10-14 00:35:04

Music Studio项目心得--JNI实现C++调用JAVA的相关文章

Android JNI c/c++调用java 无需新建虚拟机

近期通过研究SDL源码 得出android JNI  c/c++调用java 无需新建虚拟机: 具体步骤如下 第一步获得:两个参数 JNIEnv和jclass void Java_com_Test_Audio_Init( JNIEnv* env,jclass cls, jobject thiz ) { InitJNI(env,cls); } bool InitJNI(JNIEnv* env,jclass cls) { m_Env=env; m_cls=cls; } 第二步 获得java那边定义的

JNI中C调用Java方法

背景需求 我们需要在JNI的C代码调用Java代码.实现原理:使用JNI提供的反射借口来反射得到Java方法,进行调用. JNI关键方法讲解. 1. 在同一个类中,调用其他方法 JNIEXPORT void JNICALL Java_cn_itcast_ndkcallback_DataProvider_callmethod1 (JNIEnv * env, jobject obj){ //在c代码里面调用java代码里面的方法 // java 反射 //1 . 找到java代码的 class文件

JNI由浅入深_7_c调用Java方法一

1.在Java中声明方法 <span style="font-size:14px;">/** * javah -encoding utf-8 -jni com.example.jniandroid.service.CFunction * 当java中有中文时,会报编码GBK不可映射字符 方法的调用,加上-encoding utf-8 * * @author libin * */ public class CFunction { /** * 实现在c里面回调callback方

cocos2d-x笔记5: 通过jni实现C++调用Java

Cocos2d-x的跨平台性很强大,但是偶尔也需要平台的原生API结合. C++在Win32平台下简单的很,C++可以直接用MFC或者调用Win32API. Ios在XCode下直接就能C++和OC混编. 而Android又一次悲剧了,C++既不是Android的原生语言,也没有IDE可以混编... 我们只好通过jni来搞. Cocos2d-x 给我们提供了JniHelper类(良心!).头文件 #include "platform/android/jni/JniHelper.h".通

Android studio 项目支持JNI方法

步骤: 1. build.gradle 配置如下,主要两项 ndk 和 sourceSets apply plugin: 'com.android.application' android { compileSdkVersion 28 buildToolsVersion "29.0.1" defaultConfig { applicationId "com.shuguo.myapplication" minSdkVersion 21 targetSdkVersion

jni中调用java方法获取当前apk的签名文件md5值

相应的java方法: void getsign(Context context) throws Exception { PackageInfo localPackageInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), 64); StringBuilder localStringBuilder = new StringBuilder(); Signature[] arrayOfSignature

python调用java

这么个标题多少有点蛋疼的感觉,两个都是互联网时代的语言,学习成本和执行效率也差不多,之所以会产生这种需求,多半是想在python中引用java的类,例如安卓和hadoop的生态圈,基本是java代码的天下,虽然python大数据有不错的接口,但直接调用java的需求总是有的.这个目前已经有解决方案,例如jython,采用了java编写的python的解释器,在java虚拟机上面运行了python虚拟机,想想都悲催,两个慢解释型语言的结合jython速度慢来自于基因. 还有一种方案就是使用c做桥接

Android Studio NDK开发-JNI调用Java方法

相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在JNI直接Java中的方法和变量,比如callback,系统信息等.... 如何在JNI中调用Java方法呢?就需要先了解FindClass和GetMethodID了. FindClass和GetMethodID 在JNI中可以通过FindClass可以找到Java类,得到jclass,例如: jclass clz=(*env)->FindClass(env,"com/jjz/JniHandle"

【jni编程】—— 从Java调用C/C++

                                                  从Java调用C/C++ 当无法用 Java 语言编写整个应用程序时,JNI 允许您调用C/C++本机代码.在下列典型情况下,您可能决定使用本机代码: 希望用更低级.更快的编程语言C/C++去实现对时间有严格要求的代码. 希望从 Java 程序访问旧代码或代码库. 需要标准 Java 类库中不支持的依赖于平台的特性. 我需要它的代码背景 我在安卓项目中,需要用到C++的soundtouch库函数.