主要步驟
- 創建一個java項目,在其中編寫一個帶有native方法的類
- 利用idea生成.h頭文件
- 在vs中創建一個動態鏈接庫應用程序的解決方案
- 在解決方案中創建C++文件,實現頭文件中的方法
- 生成動態鏈接庫
- 回到idea,運行java項目,排錯重復以上步驟直到運行成功
1.在idea創建java項目
首先本次項目主要想實現一個簡單的HelloWorld,java程序聲明sayHello函數,並將name當做參數傳入。在C++中實現sayHello,將sayHello的文本傳回給java程序。步驟如下
-
在idea創建java項目,在src目錄下新建一個package,本文包為com.study,jni.demo.simple。
-
在包下創建一個類,用來編寫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