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

2024-03-05 15:58

本文主要是介绍Music Studio项目心得--JNI实现C++调用JAVA (转),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

http://blog.csdn.net/mdl13412/article/details/6227487


这个项目是我参加内蒙古挑战杯的比赛项目,由于时间关系,我没时间实现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即可实现我的目的,代码如下

view plaincopy to clipboardprint?
#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);
}
.......................
}
#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类调用

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

view plaincopy to clipboardprint?
#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://工程//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();
}
#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://工程//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:/工程/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:/工程/Cpp调用Java/Cpp调用Java

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

但由于VS2010的特性,它的工作目录不是exe所在目录,而是源码所在目录,这就导致了

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

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

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

这篇关于Music Studio项目心得--JNI实现C++调用JAVA (转)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/777031

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Mac系统下卸载JAVA和JDK的步骤

《Mac系统下卸载JAVA和JDK的步骤》JDK是Java语言的软件开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源,:本文主要介绍Mac系统下卸载JAVA和JDK的相关资料,需... 目录1. 卸载系统自带的 Java 版本检查当前 Java 版本通过命令卸载系统 Java2. 卸载自定

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3