前言
在公司裡維護一個網路後台服務端程式,用C++寫的,為了一些靈活性,就想整合個指令碼語言,因為lua似乎沒有什麼庫(或者是我對它比較無知),於是就選了老牌的python。確實噢,這傢伙基本什麼c/c++的庫,都有它的封裝。於是在服務端程式裡,嵌了一個python的解譯器。
可惜事情沒有這麼美好,公司裡會python本來就不多,並且多數僅在入門。
讓學Java的人去學習python? 有木有發現一個現象:學java的程式員們相對都比較自我封閉,基本都不愛再學門語言什麼的,愛穩定,不愛折騰(凡客體?)
好吧,我知道java不是指令碼語言,但反正它編譯成class之後,也是交給c/c++的程式去執行的,這麼一講,它和php,python也差不多,可以當成需要偽編譯一下指令碼語言來看了——呀,我知道python也可以交給net平台或jvm上啟動並執行,s可是這兩個傢伙不是C寫的程式嗎? (最近我寫一句話:世界上只有兩種語言,第一種是C語言,第二種是由C語言寫成的語言,包括C語言)。
一、要做成什麼效果?
1.1 表面作用就這樣——
先讓java程式員,在Eclipse或什麼IDE裡,寫一段java代碼。比如輸出一句 hello world。然後編譯成一個class檔案。
然後在運行我們寫的C++程式,這個程式就執行那一段java代碼,於是在螢幕上輸出一句hello world。
1.2 應用到工作中?
將些功能,交給Java程式員實現,然後通過這套機制,實現一個由C++寫的,已經編譯好的程式,可以將後面業務上需要的一些新功能或變化功能,交給Java實現。這個有實際意義至少: a因為現在的一般公司組成,確實C++程式員比較少。b另外C++要把事情做好,確實比它要把事情做糟的門檻,要高得多。c最後,相比java這樣有很多高階特徵的語言,c++這樣太靜態語言所缺乏一些動態性,有時挺煩人的,比如就沒法通過wsdl來動態產生一個ws的調用代碼(因為沒有反射功能)。
二、準備工作
1.1 C++方面
還是我們常用的 Code::Blocks,也還是 gcc;別的好像也沒有了,當然,用微軟的VC也可以用,只是一些設定的介面不一樣。
1.2 Java方面
一是要裝JDK
JDK和其它c,c++檔案也沒有什麼兩樣,裡面有提供給C,C++的源檔案,有include目錄,有lib目錄好像。
我裝在E盤,路徑是:
E:\Program Files\Java\jdk1.6.0_26 。所以你也就知道了我裝的jdk是什麼版本的。
那些完全沒玩過java的同學注意,可能你的windows裡已經有裝過jvm,比如安裝oracle時,不過jvm只是java虛擬機器,JDK才是你要在jvm上開發的SDK。因為JVM是c寫的,所以它的SDK裡的東東,基本就是C和C++的標頭檔、以及c編譯出來的庫檔案。
二是裝了一個Eclipse
當然,用不用或用什麼IDE都沒關係。
三是寫一段java代碼:
public class Greeting {public void SayHello() {System.out.print("--------Hello C++, I'm Java.----------\n");}}
這樣小的一段代碼,強大的Eclipse在我存檔時,就把它編譯好了。於是我立即在Java工程目錄的bin子目錄下,找到了 Greeting.class檔案。
我沒有寫java的main函數及類,反正那隻是約定好的入口,我們也不需要。我們準備讓自己寫的c++程式來決定要從java的哪個類的哪個方法執行起——說得好像很有得選擇似的,其實本例中,只有一個類,叫Greeting,一個方法,叫SayHello。不是靜態方法,所以調用之前,我們要在c++代碼裡,先建立一個對象,這是後話。
三、C++項目配置
1.1 建立C++項目
步驟一:用code::blocks的嚮導,產生一個空的 c++ 控制台(console)項目,項目名稱命名為 callJava。
1.2 配置JDK標頭檔與庫檔案的搜尋路徑
步驟二:因為要用到jdk的標頭檔和庫檔案,我們又不想把它們都給複製一份到我們callJava的項目目錄下,所以需要設定一下路徑。
這個方法有很多,最簡單的是直接寫絕對路徑,但我不喜歡這個工程換台器就編譯不了。另外我也不想這樣一個臨時實驗性的工作,專門為java的庫在c::b裡設定一個全域路徑變數。所以我們單獨為這個項目(calljava)設定一個變數好了。
開啟項目的“build options/構建條件”對話方塊,然後:
然後,就用這個變數名,在項目配置裡,加上 jdk 標頭檔的包含路徑:
最後是 庫檔案的連結路徑,操作類似,只是換了一頁:
1.3為項目添加連結庫
以上配置,等於是讓編譯器知道上哪些個檔案夾裡,去找它所需要 jdk的標頭檔和庫檔案,不過最重要的是,它需要的是檔案?標頭檔當然是在代碼裡寫上,庫檔案呢,我們還需要配置一下。這個例子很簡單,我們只需要一個叫 jvm.lib的檔案:
這個檔案,你可以在一開始說的那個jdk安裝子目錄lib下找到。
四、C++ 代碼
終於到寫C++代碼了。就直接在嚮導自動產生的main.cpp裡寫吧。
#include <iostream>#include <cassert> //assert需要#include <jni.h> //jdk 的標頭檔using namespace std;struct JVMInfo{ JavaVM* jvm; JNIEnv* env; JavaVMInitArgs vm_args; #define VM_OPT_COUNT 3 JavaVMOption options[VM_OPT_COUNT]; JVMInfo() : jvm(0), env(0) { options[0].optionString = const_cast<char *>("-Djava.compiler=NONE"); //classpath有多個時,用";"分隔,UNIX下以":"分割。 options[1].optionString = const_cast<char *>("-Djava.class.path=./demo/bin"); //這裡,至少要包含前面java代碼編譯出來的Greeting.class檔案所在路徑 //根據我設定的相對路徑,可以推出我的callJava 的C++工程和demo的Java工程所在位置的相對關係。 //用於跟蹤運行時的資訊 options[2].optionString = const_cast<char *>("-verbose:none"); //"-verbose:jni" 換成這個,則jvm啟動時,不會在螢幕上輸出一堆資訊 //JNI版本號碼 vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = VM_OPT_COUNT; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_TRUE; } //建立VM bool Create() { assert(NULL == jvm && NULL == env); //初始化虛擬機器 return 0 == JNI_CreateJavaVM(&jvm, (void**)(&env), &vm_args); } //釋放掉VM void Destroy() { if (jvm) { jvm->DestroyJavaVM(); jvm = 0; env = 0; } } //一個示範功能 void Demo() { assert(NULL != jvm); do { jclass cls = env->FindClass("Greeting"); #define BREAK_ON_FAIL(fail_condition, fail_msg) if (fail_condition) \ { cerr << fail_msg << endl; break; } BREAK_ON_FAIL(!cls, "Can't found class 'Greeting'!"); //找Greeting類的建構函式 jmethodID ctor = env->GetMethodID(cls, "<init>", "()V"); BREAK_ON_FAIL(!ctor, "Can't found class' ctor !"); //構建一個Greeting的對象 jobject obj = env->NewObject(cls, ctor); BREAK_ON_FAIL(!obj, "Can't create a object of 'Greeting'!"); //找SayHello函數 jmethodID midOfSayHello = env->GetMethodID(cls, "SayHello", "()V"); BREAK_ON_FAIL(!midOfSayHello, "Can't found class' method 'SayHello()'!"); //調用obj 的 SayHello 函數 env->CallObjectMethod(obj, midOfSayHello); }while(false); } };int main(){ JVMInfo jvmInfo; if (!jvmInfo.Create()) { cerr << "Create JVM fail!" << endl; return -1; } cout << "C++++++++++" << endl; jvmInfo.Demo(); jvmInfo.Destroy(); cout << "C++++++++++" << endl; return 0;}
解釋就看注釋吧,或者在網上搜尋一下同類檔案。
四、運行效果
C++代碼編譯通過,按F9運行……
這隻是一個實驗用的例子,根據在當初擁Python入懷的經驗教訓下,知道要用這種方法來運行大的,包括多線程的java的代碼,還要調用JDK中和並發有關的許多事。
三行輸出都來自同一個程式,只是其中上下兩行 C++++++++++++++++是來自C++的代碼,中間一句來自Java代碼。現在,我們不再管C++程式,邀請一位Java程式員,讓他協助改改那段 Greet的SayHello函數,就可以直接從運行效果中體現出來修改效果。