一、介紹
在Java中,sun.misc.Unsafe可以認爲是用於JDK內部使用的工具類,它將一些需要使用native語言實現的功能通過java方法暴露出來,這些方法比較“危險”,因爲它們可以直接修改內存中的值。
通常情況下,我們並不能直接在程序中使用Unsafe,Unsafe的構造方法被私有化,語法層面上只能通過其提供的公共靜態方法getUnsafe獲取Unsafe實例:theUnsafe,theUnsafe在靜態代碼塊被初始化,所以其是一個單例,如下所示:
public final class Unsafe {
//靜態的、私有的實例字段
private static final Unsafe theUnsafe;
......
//私有化構造器
private Unsafe() {
}
......
//對外提供靜態方法獲取實例
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
//檢查是否允許訪問Unsafe
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
......
static {
......
//初始化Unsafe實例
theUnsafe = new Unsafe();
......
}
}
從Unsafe的代碼中可以看出,通過getUnsafe方法獲取Unsafe單例是唯一的“正規”途徑,而該方法中對調用者classLoader做了檢查,我們看一下檢查代碼:VM.isSystemDomainLoader:
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
邏輯比較簡單,主要是看classLoader是否爲null,也就是判斷該類是不是由BootStrap加載,如果不是的話,則不具有訪問Unsafe的權限,隨即拋出SecurityException異常。
import sun.misc.Unsafe;
public class Test{
public static void main(String[] args){
Unsafe unsafe = Unsafe.getUnsafe();
}
}
上述代碼嘗試獲取一個Unsafe實例,會拋出異常異常:
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
二、使用Unsafe
雖然我們沒有權限調用Unsafe#getUnsafe方法,但是我們還是有辦法能夠獲取到Unsafe實例。既然theUnsafe字段在靜態代碼塊中被初始化了,並且它是一個靜態變量,那麼我們可以直接通過反射獲取該字段值。
在下面的代碼中,我們先獲取到了Unsafe實例,並且通過Unsafe#putInt方法將Test實例的value字段設置爲100,然後通過Unsafe#getIntVolatile方法獲取該值,並輸出:
public class Test {
private int value;
public static void main(String[] args) throws Exception {
//獲取theUnsafe字段
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
//私有變量設置訪問權限
unsafeField.setAccessible(true);
//theUnsafe是靜態變量,直接通過Field#get(null)獲取
Unsafe theUnsafe = (Unsafe) unsafeField.get(null);
//創建Test實例
Test test = new Test();
//獲取value字段在Test中的偏移量
long fieldOffset = theUnsafe.objectFieldOffset(Test.class.getDeclaredField("value"));
//直接操作該內存,設置值
theUnsafe.putInt(test, fieldOffset, 100);
//獲取該偏移的值
System.out.println(theUnsafe.getIntVolatile(test, fieldOffset));
}
}
控制檯輸出爲:100
三、源碼實現
Unsafe中的所有基礎方法都屬於native方法,這裏我們就不一一解析了,就看一下JUC中經常使用的CAS源碼實現即可,至於其它方法,讀者朋友可以根據自身情況酌情研究。
在openjdk的源碼中,Unsafe主要的實現代碼在unsafe.cpp中,其中包含了native方法操作內存的實現細節。這裏我們主要看看以下兩個native方法的實現:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
2.1 compareAndSwapInt
我們首先看看compareAndSwapInt方法在openjdk中的實現:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
首先是JNIHandles::resolve(obj)方法。該方法定義在jniHandles.hpp中:
inline oop JNIHandles::resolve(jobject handle) {
oop result = (handle == NULL ? (oop)NULL : *(oop*)handle);
assert(result != NULL || (handle == NULL || !CheckJNICalls || is_weak_global_handle(handle)), "Invalid value read from jni handle");
assert(result != badJNIHandle, "Pointing to zapped jni handle area");
return result;
};
可以看到,該方法定義爲一個內聯函數,沒有做什麼複雜的事兒,就是把jobject參數轉成了oop。
然後調用index_oop_from_field_offset_long(p, offset);方法,其中offset爲修改的字段在對象所佔內存中的偏移位置(相關信息可參考Java對象內存佈局),最終得到的addr就是該字段在內存中的位置,這裏可以簡單理解爲對象地址加上offset。
得到字段地址addr之後就會調用核心的Atomic::cmpxchg方法,該方法定義在atomic.hpp中,除了cmpxchg,還有xchg、cmpxchg_ptr等等,我們簡單看一下:
class Atomic : AllStatic {
public:
// Atomic operations on jlong types are not available on all 32-bit
// platforms. If atomic ops on jlongs are defined here they must only
// be used from code that verifies they are available at runtime and
// can provide an alternative action if not - see supports_cx8() for
// a means to test availability.
// Atomically store to a location
inline static void store (jbyte store_value, jbyte* dest);
inline static void store (jshort store_value, jshort* dest);
inline static void store (jint store_value, jint* dest);
// See comment above about using jlong atomics on 32-bit platforms
inline static void store (jlong store_value, jlong* dest);
inline static void store_ptr(intptr_t store_value, intptr_t* dest);
inline static void store_ptr(void* store_value, void* dest);
inline static void store (jbyte store_value, volatile jbyte* dest);
inline static void store (jshort store_value, volatile jshort* dest);
inline static void store (jint store_value, volatile jint* dest);
// See comment above about using jlong atomics on 32-bit platforms
inline static void store (jlong store_value, volatile jlong* dest);
inline static void store_ptr(intptr_t store_value, volatile intptr_t* dest);
inline static void store_ptr(void* store_value, volatile void* dest);
// See comment above about using jlong atomics on 32-bit platforms
inline static jlong load(volatile jlong* src);
// Atomically add to a location, return updated value
inline static jint add (jint add_value, volatile jint* dest);
inline static intptr_t add_ptr(intptr_t add_value, volatile intptr_t* dest);
inline static void* add_ptr(intptr_t add_value, volatile void* dest);
// See comment above about using jlong atomics on 32-bit platforms
static jlong add (jlong add_value, volatile jlong* dest);
// Atomically increment location
inline static void inc (volatile jint* dest);
static void inc (volatile jshort* dest);
inline static void inc_ptr(volatile intptr_t* dest);
inline static void inc_ptr(volatile void* dest);
// Atomically decrement a location
inline static void dec (volatile jint* dest);
static void dec (volatile jshort* dest);
inline static void dec_ptr(volatile intptr_t* dest);
inline static void dec_ptr(volatile void* dest);
// Performs atomic exchange of *dest with exchange_value. Returns old prior value of *dest.
inline static jint xchg(jint exchange_value, volatile jint* dest);
static unsigned int xchg(unsigned int exchange_value, volatile unsigned int* dest);
inline static intptr_t xchg_ptr(intptr_t exchange_value, volatile intptr_t* dest);
inline static void* xchg_ptr(void* exchange_value, volatile void* dest);
// Performs atomic compare of *dest and compare_value, and exchanges *dest with exchange_value
// if the comparison succeeded. Returns prior value of *dest. Guarantees a two-way memory
// barrier across the cmpxchg. I.e., it's really a 'fence_cmpxchg_acquire'.
static jbyte cmpxchg (jbyte exchange_value, volatile jbyte* dest, jbyte compare_value);
inline static jint cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value);
// See comment above about using jlong atomics on 32-bit platforms
inline static jlong cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value);
static unsigned int cmpxchg(unsigned int exchange_value,
volatile unsigned int* dest,
unsigned int compare_value);
inline static intptr_t cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value);
inline static void* cmpxchg_ptr(void* exchange_value, volatile void* dest, void* compare_value);
};
cmpxchg實現在atomic.cpp中,最終會根據具體的宿主環境內聯具體的實現,具體我們參照include的atomic.inline.hpp頭文件:
// Linux
#ifdef TARGET_OS_ARCH_linux_x86
# include "atomic_linux_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "atomic_linux_sparc.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "atomic_linux_zero.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "atomic_linux_arm.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "atomic_linux_ppc.inline.hpp"
#endif
// Solaris
#ifdef TARGET_OS_ARCH_solaris_x86
# include "atomic_solaris_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "atomic_solaris_sparc.inline.hpp"
#endif
// Windows
#ifdef TARGET_OS_ARCH_windows_x86
# include "atomic_windows_x86.inline.hpp"
#endif
// BSD
#ifdef TARGET_OS_ARCH_bsd_x86
# include "atomic_bsd_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "atomic_bsd_zero.inline.hpp"
#endif
#endif // SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
以x86的linux環境爲例,其引入的是atomic_linux_x86.inline.hpp頭文件,其中實現的內聯函數較多,我們先只看看jint的cmpxchg函數實現:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
第一步調用了os::is_MP()函數,該函數定義在os.hpp中,主要是判斷當前環境是否爲多處理器環境:
// Interface for detecting multiprocessor system
static inline bool is_MP() {
assert(_processor_count > 0, "invalid processor count");
return _processor_count > 1 || AssumeMP;
}
使用__asm__ volatile嵌入彙編代碼片段,一個內聯彙編表達式分爲四個部分,以:進行分隔。使用__asm__來聲明表達式,volatile則保證指令不會被gcc優化影響,關於gcc內聯彙編的更多內容不是本文的主題(博主也忘差不多了o(╯□╰)o),感興趣的朋友可以自行了解。cmpxchgl指令則是x86的比較並交換指令,如果是多處理器會使用lock前綴,關於lock前綴,我們在總結volatile的博文中介紹過,其可以達到一個內存屏障的效果,也可以參照intel手冊。
從上面也看到了,Atomic::cmpxchg的方法的返回值是內存中的
2.2 compareAndSwapLong
同樣先在unsafe.cpp中找到compareAndSwapLong的實現:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
UnsafeWrapper("Unsafe_CompareAndSwapLong");
Handle p (THREAD, JNIHandles::resolve(obj));
jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
if (VM_Version::supports_cx8())
return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
else {
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) { *addr = x; success = true; }
return success;
}
UNSAFE_END
和compareAndSwapInt不一樣的是,jlong類型的CAS操作需要判斷宿主平臺是否支持8字節的CAS操作,也就是通過VM_Version::supports_cx8方法進行判斷。該方法定義在vm_version.hpp中:
// does HW support an 8-byte compare-exchange operation?
static bool supports_cx8() {
#ifdef SUPPORTS_NATIVE_CX8
return true;
#else
return _supports_cx8;
#endif
}
其中_supports_cx8默認爲false,在相應的平臺下會有對應的方法進行賦值,比如在x86下,會在vm_version_x86.cpp中調用supports_cmpxchg8()方法進行賦值,該方法定義在vm_version_x86.hpp中:
static bool supports_cmpxchg8() { return (_cpuFeatures & CPU_CX8) != 0; }
這個_cpuFeatures可以理解爲cpu的一些屬性,其在vm_version_x86.cpp中處理:如果支持CPUID,則會通過調用feature_flags()方法獲取:
if (cpu_family() > 4) { // it supports CPUID
_cpuFeatures = feature_flags();
// Logical processors are only available on P4s and above,
// and only if hyperthreading is available.
_logical_processors_per_package = logical_processor_count();
}
我們來看一下feature_flags方法:
static uint32_t feature_flags() {
uint32_t result = 0;
if (_cpuid_info.std_cpuid1_edx.bits.cmpxchg8 != 0)
result |= CPU_CX8;
if (_cpuid_info.std_cpuid1_edx.bits.cmov != 0)
result |= CPU_CMOV;
if (_cpuid_info.std_cpuid1_edx.bits.fxsr != 0 || (is_amd() &&
_cpuid_info.ext_cpuid1_edx.bits.fxsr != 0))
result |= CPU_FXSR;
// HT flag is set for multi-core processors also.
if (threads_per_core() > 1)
result |= CPU_HT;
if (_cpuid_info.std_cpuid1_edx.bits.mmx != 0 || (is_amd() &&
_cpuid_info.ext_cpuid1_edx.bits.mmx != 0))
result |= CPU_MMX;
if (_cpuid_info.std_cpuid1_edx.bits.sse != 0)
result |= CPU_SSE;
if (_cpuid_info.std_cpuid1_edx.bits.sse2 != 0)
result |= CPU_SSE2;
if (_cpuid_info.std_cpuid1_ecx.bits.sse3 != 0)
result |= CPU_SSE3;
if (_cpuid_info.std_cpuid1_ecx.bits.ssse3 != 0)
result |= CPU_SSSE3;
if (_cpuid_info.std_cpuid1_ecx.bits.sse4_1 != 0)
result |= CPU_SSE4_1;
if (_cpuid_info.std_cpuid1_ecx.bits.sse4_2 != 0)
result |= CPU_SSE4_2;
if (_cpuid_info.std_cpuid1_ecx.bits.popcnt != 0)
result |= CPU_POPCNT;
if (_cpuid_info.std_cpuid1_ecx.bits.avx != 0 &&
_cpuid_info.std_cpuid1_ecx.bits.osxsave != 0 &&
_cpuid_info.xem_xcr0_eax.bits.sse != 0 &&
_cpuid_info.xem_xcr0_eax.bits.ymm != 0) {
result |= CPU_AVX;
if (_cpuid_info.sef_cpuid7_ebx.bits.avx2 != 0)
result |= CPU_AVX2;
}
if (_cpuid_info.std_cpuid1_edx.bits.tsc != 0)
result |= CPU_TSC;
if (_cpuid_info.ext_cpuid7_edx.bits.tsc_invariance != 0)
result |= CPU_TSCINV;
if (_cpuid_info.std_cpuid1_ecx.bits.aes != 0)
result |= CPU_AES;
if (_cpuid_info.sef_cpuid7_ebx.bits.erms != 0)
result |= CPU_ERMS;
if (_cpuid_info.std_cpuid1_ecx.bits.clmul != 0)
result |= CPU_CLMUL;
// AMD features.
if (is_amd()) {
if ((_cpuid_info.ext_cpuid1_edx.bits.tdnow != 0) ||
(_cpuid_info.ext_cpuid1_ecx.bits.prefetchw != 0))
result |= CPU_3DNOW_PREFETCH;
if (_cpuid_info.ext_cpuid1_ecx.bits.lzcnt != 0)
result |= CPU_LZCNT;
if (_cpuid_info.ext_cpuid1_ecx.bits.sse4a != 0)
result |= CPU_SSE4A;
}
return result;
}
從方法體中可以看到,如果從CPUID中判斷到支持cmpxchg8,那麼會將result和CPU_CX8進行按位或,CPU_CX8定義在枚舉中,表示哪一位是cmpxchg8是否支持的標識:
enum {
CPU_CX8 = (1 << 0), // next bits are from cpuid 1 (EDX)
CPU_CMOV = (1 << 1),
CPU_FXSR = (1 << 2),
CPU_HT = (1 << 3),
CPU_MMX = (1 << 4),
CPU_3DNOW_PREFETCH = (1 << 5), // Processor supports 3dnow prefetch and prefetchw instructions
// may not necessarily support other 3dnow instructions
CPU_SSE = (1 << 6),
CPU_SSE2 = (1 << 7),
CPU_SSE3 = (1 << 8), // SSE3 comes from cpuid 1 (ECX)
CPU_SSSE3 = (1 << 9),
CPU_SSE4A = (1 << 10),
CPU_SSE4_1 = (1 << 11),
CPU_SSE4_2 = (1 << 12),
CPU_POPCNT = (1 << 13),
CPU_LZCNT = (1 << 14),
CPU_TSC = (1 << 15),
CPU_TSCINV = (1 << 16),
CPU_AVX = (1 << 17),
CPU_AVX2 = (1 << 18),
CPU_AES = (1 << 19),
CPU_ERMS = (1 << 20), // enhanced 'rep movsb/stosb' instructions
CPU_CLMUL = (1 << 21) // carryless multiply for CRC
} cpuFeatureFlags;
這裏可以看到,用_cpuFeatures的最低位標識cmpxchg8,如果支持,那麼將該位通過 | 操作設置爲1,判斷的時候像這樣簡單判斷即可:
_cpuFeatures & CPU_CX8 != 0
我們回到Unsafe_CompareAndSwapLong處,如果支持cmpxchg8的話,那麼和jint類似,會調用Atomic::cmpxchg方法實現CAS操作,一下是linux_x86下的內聯實現:
inline jlong Atomic::cmpxchg (jlong exchange_value, volatile jlong* dest, jlong compare_value) {
bool mp = os::is_MP();
__asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
注意前面jint的CAS使用的是cmpxchgl指令,而此處使用的是cmpxchgq指令。當然,如果不支持cmpxchg8的話,就只能自己處理,而且需要保證原子性,所以使用了ObjectLocker,上鎖之後實現比較並交換邏輯即可:
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) { *addr = x; success = true; }
return success;
最後,CAS操作返回的是布爾類型的結果,使用ObjectLocker時,從代碼上就很好理解,先比較地址上的值,如果相等則交換,返回true,否則返回false。而使用Atomic::cmpxchg時,結果是cmpxchg返回值和期望值比較的結果:
(Atomic::cmpxchg(x, addr, e)) == e
回到上面cmpxchg的內聯實現,其返回的是exchange_value。關於cmpxchgl和cmpxchgq指令,如果期望值和當前值相等的話,那麼會將新值寫到內存地址,覆蓋當前值,並且返回被覆蓋的值(舊值,其實也是期望值);如果期望值和當前值不相等,那麼會返回最新的當前值。所以這個操作叫做比較並交換,不論比較的結果如何,都會“換出”內存地址的當前值。
所以,只有當返回的exchange_value和我們的期望值相等時,才表示指令執行成功了,否則表示該內存上的值被其它線程更新了,指令失敗。
四、JNI實現
現在我們再回過頭來看JNI(Java Native Interface),即Java本地接口,它能將Java與c/c++等本地代碼進行集成,使得Java代碼能夠與其它編程語言進行相互操作。JNI是一個接口,類似於虛擬機規範,其並沒有限制實現細節。
某一些功能我們可能無法用Java語言直接實現(或者說高效實現),那麼就需要藉助其他本地語言實現,我們只需要在Java中定義好接口(native),JVM在調用方法時會根據指定的方法名從本地庫中尋找具體的實現,這個實現可能由c++編寫。
我們這裏實現一個JNI調用的流程。
首先,我們編寫Java文件,TestJNI.java:
class TestJNI{
public native void say();
static{
System.loadLibrary("TestLib");
}
public static void main(String[] args){
new TestJNI().say();
}
}
在TestJNI.java中,我們定義了一個native方法say(),該方法我們會以c++實現。在static靜態代碼塊中加載動態庫,該庫中需要包含say方法的本地實現,所以接下來我們需要組建TestLib動態庫(TestLib.dll)。dll即Dynamic Link Library,可以想象爲我們Java中的jar包,裏面包含一系列可供他人調用的公用函數。
注:這裏爲了簡單,我直接把TestJNI.java放到桌面上操作。
我們的native方法是定義在TestJNI.java中的,現在要用c++實現該方法,需要定義該方法的頭文件,並且JNI對native方法的方法名有規範,並不能隨便寫,因爲JVM尋找本地實現的時候有自己的規則。這樣看來編寫頭文件還比較麻煩,好在jdk爲我們提供了指令:javah,以快速創建該頭文件。
C:\Users\Administrator\Desktop>javah TestJNI
C:\Users\Administrator\Desktop>
調用javah TestJNI之後,會生成一個TestJNI.h文件,我們來看一下文件內容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */
#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJNI
* Method: say
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_TestJNI_say
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
我們看到TestJNI.h中引入了jni.h,該文件位於%JAVA_HOME%\include目錄中:
文件中定義了一個Java_TestJNI_say方法,JNIEnv*表示的是JNI本身,通過它可以調用jni.h中定義好的函數,jobject表示當前對象(實例對象或者class)。我們不用太過關注,知道這裏定義了一個接口就行了。
有了頭文件,接下來我們要開始編寫實現、生成dll了,由於博主環境爲64位,所以需要生成64位環境的dll,使用的IDE是Visual Studio 2010。
首先,選擇文件-新建-項目,選擇模板Visual C++、Win32控制檯應用程序,項目名稱爲TestJNI.java中加載的庫名TestLib,同時我們要清楚項目位置,這裏使用的是默認配置:
然後點擊確定:
這裏點擊“下一步”,然後再應用程序設置中,選擇應用程序類型爲DLL,附加選項爲空項目,點擊完成:
這樣項目就創建好,接下來我們設置64位環境。首先進入配置管理器:
平臺在win32處下拉,選擇新建:
在新平臺處選擇64,從Win32處複製,勾選創建新的項目平臺:
然後點擊確定:
然後關閉配置管理器。
接下來我們創建頭文件,在項目上右鍵-添加-類,選擇C++類:
選擇之後,點擊添加。然後在嚮導中輸入類名,這裏我們使用前面定義的TestJNI就行了,點擊完成。
這裏就創建了TestJNI.h和TestJNI.cpp文件:
然後我們直接將前面使用javah命令生成的TestJNI.h文件內容拷貝到當前項目的TestJNI.h中,由於需要引用jni.h,所以我們需要將jni.h從%JAVA_HOME%\include目錄中拷貝一份。爲了簡單,這裏直接將jni.h放到TestJNI項目中,和TestJNI.h處於同一目錄,所以我們需要修改TestJNI.h中引用jni.h的方法,改爲相對路徑引入,即<>改爲"":
這裏我們打開jni.h簡單看一下,發現其中需要引入jni_md.h:
所以我們需要將jni.h文件和jni_md.h都拷貝到項目中,和TestJNI.h同目錄(jni_md.h在%JAVA_HOME%\include\win32目錄中):
接下來編寫TestJNI.cpp文件,在該文件中實現Java_TestJNI_say方法:簡單輸出hello world
#include "TestJNI.h"
JNIEXPORT void JNICALL Java_TestJNI_say (JNIEnv *env , jobject obj){
printf("hello world");
}
到這裏我們代碼就編寫完畢了,接下來進入頂部導航欄:生成-重新生成解決方案
下方輸出區域會顯示執行結果:
這裏可能會被360等軟件攔截,允許即可。生成成功之後,我們進入項目目錄的x64\Debug目錄中,可以看到已經生成了dll文件了:
到這裏我們TestLib.dll動態鏈接庫已經創建好了,接下來將其複製到TestJNI.java目錄中,當然我們這裏是在桌面上:
然後我們打開命令窗口,先javac TestJNI.java編譯,然後java TestJNI運行代碼:
我們看到,輸出了hello world,native方法調用成功。