在idea2018和vs2017平台下JNI編程調用C++算法(1)-環境搭建


##JNI簡介 JNI是Java Native Interface的簡稱,通過JNI,可以調用C++或C的程序(稱為本地程序)。 ##應用場景 我認為應用場景可以用三個字概括“不得不”,即只有遇到必須跨語言的時候,才會選擇JNI。從Java的場景出發,使用JNI意味着失去了跨平台的優勢;從C++的角度想,可能需要將程序發布到安卓端等,而不得不采用JNI進行跨語言。 通過調研JNI,目前JNI的應用場景大部分是需要在安卓平台引入C/C++代碼。也有人和我一樣,正在開發的java系統需要調用一段C++的核心代碼。於是,讓我們愉快地入坑吧~ ##本文特色 本文主要講解在idea2018和vs2017平台下搭建一套Java項目和C++解決方案,其中Java項目實現一個簡單的HelloWorld功能,該功能正是通過JNI調用C++實現的。 作為一名JNI剛入坑小將,以及多年沒寫過(只在書本和考試中與其交手)C++的渣渣,寫一個HelloWorld程序也是反復磨煉的過程。在反復磨煉過程中,我發現網上很多教程有些繁瑣,需要將各種文件復制來粘貼去,稍有錯誤就要重新復制粘貼,實在讓人惱火。所以本文介紹一種**借助idea和vs平台盡量減少步驟的搭建方法**。 ##環境介紹 - 操作系統:Win7專業版64bit - JDK:1.8 - idea:2018 - vs:2017 > Tips: > windows系統注定了生成的動態鏈接庫是dll文件 > JDK10將javah工具取消,需要使用javac -h替代,這是與jdk8不同的地方

主要步驟

  1. 創建一個java項目,在其中編寫一個帶有native方法的類
  2. 利用idea生成.h頭文件
  3. 在vs中創建一個動態鏈接庫應用程序的解決方案
  4. 在解決方案中創建C++文件,實現頭文件中的方法
  5. 生成動態鏈接庫
  6. 回到idea,運行java項目,排錯重復以上步驟直到運行成功

1.在idea創建java項目

首先本次項目主要想實現一個簡單的HelloWorld,java程序聲明sayHello函數,並將name當做參數傳入。在C++中實現sayHello,將sayHello的文本傳回給java程序。步驟如下

  1. 在idea創建java項目,在src目錄下新建一個package,本文包為com.study,jni.demo.simple。

  2. 在包下創建一個類,用來編寫native方法和main函數。

     package com.study.jni.demo.simple;
    
     import com.study.jni.demo.common.Constants;
     
     public class SimpleHello {
     
         public static native String sayHello(String name);
     
         public static void main(String[] args) {
             String name = "lucyChen";
             String text = sayHello(name);
             System.out.println("after native, java shows:" + text);
         }
     
         static {
     //        System.loadLibrary("JNICPPDEMO");
             System.load(Constants.DLLPATH + "JNICPPDEMO.dll");
         }
     
     }
    

其中sayHello是一個靜態方法,在前面標注為native代表了這是一個本地函數。
main函數中,調用sayHello函數。
下面的static代碼塊暫且不談。
代碼寫好后,在生成頭文件前,我們需要build一下項目,生成class文件,build后,可在左側目錄看到out/production目錄下生成了對應class文件。

2.生成頭文件

頭文件可以使用命令行生成(見參考文獻),或者熟悉格式后自己手寫。但是正如前文的介紹,本文希望用一種簡便的方式。所以我希望能夠隨便點一下就生成頭文件(真的有點懶得)。於是,我找到了一種用idea工具生成頭文件的方法,那就是External Tools。
External Tools其實就是將手動輸入的命令存下來,本質也是運行javah,后面跟着配置參數,這些參數存在External Tools,避免每次手動輸入。

  • 添加External Tools.File->Settings->Tools->ExternalTools,點擊添加

  • 編輯Tools

      Name:Generate Header File   
      Program:$JDKPath$/bin/javah 
      Arguments:-jni -classpath $OutputPath$ -d ./jni $FileClass$
      Working directory: $ProjectFileDir$
    
    • Name:External Tools的名稱,喜歡什么起什么,只要自己明白
    • Program是javah工具所在地址,即jdk所在路徑下的bin,該參數是指tool采用的運行工具是javah
    • Arguments設置的是javah的參數,具體可在命令行中查看javah的幫助,查看每個函數含義
    • Working directory:項目名稱
  • 生成頭文件

    • 保存工具后,右擊需要生成頭文件的類,即我們的SimpleHello,選擇External Tool,點擊我們剛剛創建的tool。
    • 然后你就會發現我們的目錄中多了一個jni文件夾,jni文件夾里面有一個名字長長的.h文件,成功!

Tips:
該方法適用於jdk8,jdk10中取消了javah,適用javac -h。但是jdk10在使用External Tools時會報錯。但是我的工作環境不可能用jdk10,所以我也沒有鑽進去研究了~

3.在vs中創建解決方案

長時間沒接觸過C++了,想當年(10年前)上學那會,我還只會用VC6.0刷刷題,而現在都要vs2017了,而我的C++知識早就忘得差不多了。雖然我作為一個小白,但是仍然阻擋不了我吐槽VS的中文翻譯——解決方案,解決方,解決,解,角……emm,真變扭。廢話不多說了,我們一起創建一個"解決方案"吧!

  • 文件->新建->項目->Windows桌面->Windows桌面向導,輸入名稱。

  • 選擇應用程序類型,注意此處不要勾選預編譯標頭【參考

  • 設置項目包含目錄
    本來我是按照這篇文章復制jni.h等文件的,但是一直報錯“找不到 源 文件 jni.h”。搞來搞去總是不成,后來才發現,我在vs2017直接復制,jni.h並沒有到C++項目目錄下,而是仍然在原來的目錄里,這與java的ide很不同啊。雖然被這個問題搞到差點摔桌子,但我轉念一想,在原來的目錄下就還不錯啊,省得我復制來復制去。於是刷刷刷設置了包含路徑

  • 點擊項目,我的項目叫jniCppDemo,在菜單欄選擇項目->屬性->配置屬性->VC++目錄->包含目錄

  • 設置包含路徑

    • 設置jni.h所在路徑 C:\Program Files\Java\jdk1.8.0_181\include
    • 設置jni_md.h所在路徑 C:\Program Files\Java\jdk1.8.0_181\include\win32
    • 設置剛剛生成頭文件所在路徑 D:\javaWorkspace\jniJavaDemo\jni

4.編寫cpp文件

創建一個cpp文件,其中include jni.h,剛剛生成的頭文件。如果上一步設置路徑成功,這里不會報錯。
在這個cpp,參考了這篇文章,實現sayHello,即獲取參數name,並返回hello name給java程序。

#include "jni.h"
#include "stdio.h"
#include "string.h"
#include "com_study_jni_demo_simple_SimpleHello.h"  
JNIEXPORT jstring JNICALL Java_com_study_jni_demo_simple_SimpleHello_sayHello(
	JNIEnv *env, jclass cls, jstring j_str)
{
	const char *c_str = NULL;
	char buff[128] = { 0 };
	jboolean isCopy;
	c_str = env->GetStringUTFChars(j_str, &isCopy);
	if (c_str == NULL)
	{
		printf("out of memory.\n");
		return NULL;
	}
	printf("Java Str:%x %s %d %d\n", c_str, c_str, strlen(c_str), isCopy);
	sprintf_s(buff, "hello %s", c_str);
	env->ReleaseStringUTFChars(j_str, c_str);
	return env->NewStringUTF(buff);
}

Tips
C++的調用方式和C的調用jni的方式不同,在jni.h中可以看出來,網上很多教程都是基於C的,我這里將其改成了C++的調用方式

5.生成dll文件

寫好了cpp,讓我們勇敢地生成dll文件吧。
參考文獻里指出需要將解決方案平台改成64bit,那就改一下吧,畢竟我們需要運行在64位操作系統上。

然后右擊項目生成/重新生成,就生成了dll文件。從控制台輸出可看到dll的地址

6.運行java

生成dll文件后,讓我們重新回到java項目,我們繼續來討論剛剛遺留的一段代碼

static {
	//      System.loadLibrary("JNICPPDEMO");
	        System.load(Constants.DLLPATH + "JNICPPDEMO.dll");
	    }

含義很好理解,就是在java里面加載dll庫。注釋的方法是如果把dll庫拷貝到java項目路徑下,可以采用這種方式加載,不用寫路徑,不需要寫后綴。
還有一種采用Systerm.load方式,這種需要指定庫的位置並加上后綴。由於我在學習過程中,可能會遇到各種問題,來回修改和拷貝很麻煩,於是我采用了第二種方式,為了代碼規范及好看,我新建了一個類存放dll的地址。

總結

至此,一個簡單的HelloWorld的JNI項目搭建就打通了,本文采用idea2018和vs2017ide環境,實現了一種簡便的搭建方法,這種方法可方便地應用在JNI學習過程中。

代碼位置

后面會更新到github中,敬請期待

參考文獻

https://blog.csdn.net/wsxzhbzl/article/details/82727034
http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/workflow.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM