`

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失败的原因

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

分享到:
评论

相关推荐

    Android Studio3.0开发JNI流程------C++调用Java以及Java调用C++,C++/Java互相调用

    本文将详细讲解在Android Studio 3.0中如何实现C++调用Java以及Java调用C++的过程。 首先,我们需要在Android Studio项目中配置NDK(Native Development Kit),它是Android用于编写和编译C/C++代码的工具集。在...

    Android-JNI完整工程,实现c++调用java和java调用c++

    这个完整工程展示了如何在Android应用中使用JNI,实现C++与Java之间的双向调用。这种技术在需要高效计算、利用硬件加速或者复用已有的C/C++库时非常有用。 1. **JNI基础知识**: - JNI接口提供了Java虚拟机(JVM)...

    C++库封装JNI接口-实现java调用c++

    总结,C++库封装JNI接口实现Java调用C++涉及的主要步骤包括:声明Java中的本地方法,生成JNI头文件,编写C++实现,编译成库,最后在Java中加载并调用。这个过程需要理解Java和C++之间的数据类型转换,以及如何在两种...

    visual studio 2019下C++通过JNI调用JAVA代码

    在本文中,我们将深入探讨如何在Visual Studio 2019环境下使用C++通过Java Native Interface (JNI)来调用Java代码。JNI是Java平台的一部分,它为Java应用程序提供了与本地代码交互的能力,使得开发者可以将Java应用...

    Android JNI基础-利用JNI实现JAVA调用C++代码

    Android JNI基础-利用JNI实现JAVA调用C++代码

    3.jni_c++调用java中的方法

    在本主题中,我们将深入探讨如何使用C++通过JNI来调用Java中的方法,以及如何实现C++与JavaScript的互调。这在跨平台开发、性能优化或利用现有C/C++库时尤其有用。 首先,我们需要理解JNI的基本结构。JNI接口定义了...

    C++调用java代码的JNI实现

    为了实现C++对Java代码的调用,需要确保项目中正确配置了必要的JNI头文件。具体来说,需要包含以下三个头文件: - **stdafx.h**:这是由Visual Studio 2005自动生成的预编译头文件,用于加快编译速度。在该文件中,...

    利用JNI实现Java调用C++库

    利用JNI技术实现Java中调用C++编写的函数库示例程序源码,并附上参考JNI文档。 详情见本人博客:Java学习之通过JNI调用C/C++编写的dll链接库(图文教程)(http://write.blog.csdn.net/postlist)

    C++调用Java方法

    Android Studio项目,此Demo实现Java调用C++函数,然后C++函数回调Java方法、纯C++直接调用Java方法,此为github地址链接

    ocos2d-x 通过JNI实现c/c++和Android的java层函数互调

    cocos2d-x 通过JNI实现c/c++和Android的java层函数互调, 本文主要实现两个功能: (1)通过Android sdk的API得到应用程序的包名(PackageName),然后传递给c++层函数。 (2)通过c++函数调用Android的java层函数,显示一...

    通过JNI实现C++与JAVA相互调用之TCP编程

    在这个"通过JNI实现C++与JAVA相互调用之TCP编程"的示例中,我们探讨的是如何使用JNI来实现TCP通信,其中C++作为底层通信引擎,而Java则负责上层应用逻辑。 首先,TCP(Transmission Control Protocol)是一种面向...

    JNI 调用实例(java JNI cpp互相调用实例)

    本文旨在介绍 Java 和 C++ 之间的互相调用实例,通过 JNI(Java Native Interface)技术,实现 Java 调用 C++ 和 C++ 调用 Java 的操作。下面将对标题、描述、标签和部分内容进行详细说明。 标题:JNI 调用实例...

    JNI技术手册 c/c++调用java

    ### JNI技术手册:C/C++与Java互调详解 #### 一、JNI技术概览 **JNI(Java Native Interface)** 是Java平台的一部分,它允许Java代码与其他语言写的代码进行交互,尤其是C和C++。JNI是Java与本地代码之间沟通的...

    JAVA项目JNI调用dll实现DEMO源码,C++&JAVA

    完整的实现java跨平台调用C程序源码,包含JAVA源码和C源码以及编译后的demo dll。将dll放到jdk bin目录下,java 项目可以直接运行。若要修改dll可以,修改C源码后重新编译生成dll。该demo处理了多线程调用c,全局...

    cocos2d-x 通过JNI实现c/c++和Android的java层函数互调-源码

    本主题聚焦于如何通过JNI(Java Native Interface)来实现在Cocos2d-x中调用Java层的函数,以及反之在Java层调用C++代码。下面我们将深入探讨这一技术。 JNI是Java平台的一部分,它为开发者提供了一种方式来编写...

    GMSSL的java调用(JNI库和调用实例).zip

    - **JNI动态库**:这是由C或C++编写的库,实现了GMSSL的原生接口,供Java通过JNI调用。库可能有多个版本,针对不同的操作系统和处理器架构。 - **Java调用示例**:这些示例代码展示了如何在Java中加载和使用JNI库,...

    使用C++创建java虚拟机JVM,使用JNI调用java函数.zip

    本文将深入探讨如何使用C++创建Java虚拟机(JVM),并通过JNI来调用Java函数。这是一项技术性很强的任务,需要对C++编程、Java虚拟机的工作原理以及JNI接口有深入的理解。 首先,Java虚拟机(JVM)是Java平台的核心...

    JNI------C语言调用Java

    最近项目中需要使用JNI,所以研究了一下,其中遇到过不少问题,总结一下,让遇到同样问题的人...在C/C++中调用Java的方法一般分为五个步骤:初始化虚拟机、获取类、获取类的方法、创建类对象、调用方法和退出虚拟机。

    Android-ndk-jni AES加解密 C++

    1. **创建JNI接口**:首先,你需要在Java层定义JNI接口,这些接口将被C++代码实现。例如: ```java static { System.loadLibrary("aes"); } public native byte[] encryptAES(byte[] data, byte[] key); ...

    实战-C++调用Java函数

    首先,我们要理解标题"实战-C++调用Java函数"所涉及的核心知识点。C++是一种强大的系统级编程语言,而Java则以其平台无关性和丰富的类库受到广泛应用。有时,我们可能需要利用C++的高效性能和Java的跨平台特性,这就...

Global site tag (gtag.js) - Google Analytics