欢迎转载,转载请说明。
网上很多界绍使用JAVA调用C/C++的SO库,但从例子中大多都是一个简单的C语言文件,然后进行编译打库。
这些例子只能带给我们了解打库和调用步骤,在实际项目过程中,遇到的并非想象中的哪么简单。
下面我将以一个C++的例子来演示从打库到调用的全过程,希望对初学者有用,大虾,大牛一边过,欢迎围观。
例子:
有一个汽车类接口ICar ,其中声明了几个基本方法,同时有一个phorsche(宝时捷)继承ICar,并实现了相应的接口,其中用到了基本数据类型及结构体的类型,目的在于想通过该例子来实践一下在实际项目中遇到不同类型的参数时的接口,是不是直接不需要转换就可以调用。
interfacecar.h
#ifndef ICAR_H #define ICAR_H #include "string" typedef struct tagEngine{ char name[20]; char date[20]; char address[100]; int number; double width; double height; bool isrepair; }Engine,*pEngine; class ICar { public: ICar(void); ~ICar(void); enum CarState {csStop,csRunning,csStart,csBad}; virtual void setCarName(const std::string& carname)=0; virtual std::string getCarName()=0; virtual CarState getCurrentState()=0; virtual void setState(CarState cs)=0; virtual void getEngine(Engine* engine)=0; virtual Engine getEngin()=0; virtual void stop()=0; virtual void start()=0; virtual void run()=0; virtual void breakit()=0; }; class Phorsche : public ICar { public: Phorsche(); ~Phorsche(); void setCarName(const std::string& carname); std::string getCarName(); CarState getCurrentState(); void setState(CarState cs); void getEngine(Engine* engine); Engine getEngin(); void stop(); void start(); void run(); void breakit(); private: CarState m_state; Engine m_engine; std::string m_name; }; #endif
interfacecar.cpp
#include "interfacecar.h" #include "iostream" #include "string.h" ICar::ICar(void) { } ICar::~ICar(void) { } void Phorsche::setCarName( const std::string& carname ) { m_name = carname; } Phorsche::Phorsche() { strcpy(m_engine.address , "GZ"); strcpy(m_engine.date , "2012-11-2"); m_engine.height = 23.4; m_engine.width = 56.2; strcpy(m_engine.name,"Phorsche"); m_engine.isrepair = false; m_engine.number = 120042912; } Phorsche::~Phorsche() { } std::string Phorsche::getCarName() { return m_name; } ICar::CarState Phorsche::getCurrentState() { return m_state; } void Phorsche::setState( CarState cs ) { m_state = cs; } void Phorsche::getEngine( Engine* engine ) { strcpy(engine->address , m_engine.address); strcpy(engine->date , m_engine.date); engine->height = m_engine.height; engine->width = m_engine.width; strcpy(engine->name,m_engine.name); engine->isrepair = m_engine.isrepair; engine->number = m_engine.number; } Engine Phorsche::getEngin() { return m_engine; } void Phorsche::stop() { std::cout<<"The car is stop."<<std::endl; m_state = csStop; } void Phorsche::start() { std::cout<<"The car is start."<<std::endl; m_state = csStart; } void Phorsche::run() { std::cout<<"The car is running."<<std::endl; m_state = csRunning; } void Phorsche::breakit() { std::cout<<"The car is breakit."<<std::endl; m_state = csBad; }
下面将讲解打SO库和调用过程。
准备工作:
如果是Linux环境:请按装gcc ,g++,JDK,SWIG,[eclipse] ,中括号的可以不装,为了调试用的。当然也可以使用命令行来进行。
如果是WIN平台:请按装cygwin,Swig ,JDK。
我将以LINUX和WINDOW下打SO两种情况都进行介绍:
先对LINUX的进行介绍(小弟是LINUX菜鸟,第一次玩LINUX,也是由于打库过程遇到XX问题后XX解决,才免强对LINUX有点点了解):
LINUX环境:
准备工作:
安装:ubuntu + eclipse + openjdk1.6.3
安装:gcc/g++ 4.6.3 版本
安装:swig 2.0.4 版本
如果没有装,直接打开终端:apt-get install <xx名>来自动按装所需要的软件,(反正我是这么装过来的)。
用户名为:xxxx 不是root 权限,这对后面的COPY文件或删除文件操作上有限制。可以使用sudo su -命令进行将用户提升为root权限。我不知道其它LINUX行不行,反正我的ubuntu系统装好后默认是我设置的用户,对于ROOT我并不知道密码,没有办法我只能使用sudo su -来提升。如果还有其它方法可以得到ROOT密码的请教教小弟。
建立好文件夹:/home/xxxx/demo 其中xxxx是我的LINUX用户名;路径大家可以自行存放了,在这个demo文件中放置前面的interfacecar.h和interfacecar.cpp文件。
准备工作OK。
1、先建立一个.i文件,这个文件相对比较重要,是由它来将C/C++接口变为JAVA调用的C接口。我这里命名为:cplus.i
cplus.i(手写的,不知道有没有工具)
/* File : cplus.i */
%module cplus
%{
#include "interfacecar.h"
%}
%include "interfacecar.h"
2、使用swig 进行将.i文件转为JAVA文件。
使用命令:swig -c++ -java -package com.plus -outdir ./ /home/xxxx/demo/cplus.i
说明:
-c++ :表示需要输换的是C++接口。如果不使用这个参数,默认将以C接口进行输换。
-java :转为JAVA接口。
-package :指定包的名称,当然这个不是必须的。按自己习惯。如果不使用就删除这个参数。
-outdir : 用来指定编译后的文件输出路径。
./ : 表示输出目录为当前路径。
后面的就是.i文件存放的路径了。
执行命令后,生成.java和xxxx_wrap.cxx文件,
其中.java就是显后面调用接口时查找类型时使用的。而xxxx_wrap.cxx就是用来打SO库的。
将产生的JAVA文件放到测试工程demo目录中。
3、使用CD指令进入到DEMO所在目录路径下。
cd /home/xxxx/demo
4、使用locate jni.h来查看jni.h所在的目录,使用locate jni_md.h 来查看jni_md.h所在的目录。
5、编译cpp和cxx生成.o文件(因为后面通过.o来生成.so)
g++ -Wall -c interfacecar.cpp cplus_wrap.cxx -I/usr/lib/jvm/java-6-openjdk-i386/include -I/usr/lib/jvm/java-6-openjdk-i386/include/linux
这里用-I(大写的i)来连接头文件所在的路径。
另外要注意,这里是C++所以要用G++来编,如果用GCC编,就有可能出现找不到声明的错误。
将产生两个.o文件(interfacecar.o,cplus_wrap.o)
6、生成so库
g++ -share -fpic interface.o cplus_wrap.o -o libcplus.so
注意:如果只是使用cplus_wrap.o一个文件来打SO,可能出现数据类型找不到等问题。
如果使用一个.o进行连接后面测试运行时将会报类似错误(我想这应该就是依赖关系未打进来导致)
上面的步骤完成
下面是ECLIPSE调用演示,JAVA高手不要见笑,第一次使用ECLIPSE。
新建一个JAVA 工程
取名为Callcplus
将前面生成的JAVA文件添加到测试工程目录中,如下图。
其次将DEMO中的.Java文件全部COPY到测试工程目录下。
编写测试代码
演示调用代码:
package com.cplus; public class Callcplus { static { System.loadLibrary("cplus"); //System.load("/home/xxxx/demo/cplus/libcplus.so"); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub //System.out.print(System.getProperty("java.library.path")); //int a = example.fact(50); //double bb = example.getMy_variable(); //String bb = example.get_time(); Phorsche pche = new Phorsche(); //pche.setCarName("t m d"); //System.out.print(pche.getCarName()); pche.run(); //SWIGTYPE_p_std__string ok = "phorsche"; //pche.setCarName(ok); ICar.CarState cs = pche.getCurrentState(); Engine eg = pche.getEngin(); System.out.printf("%s\n",eg.getAddress()); System.out.printf("%s\n",eg.getDate()); System.out.printf("%s\n",eg.getName()); System.out.printf("%f\n",eg.getHeight()); System.out.printf("%f\n",eg.getWidth()); System.out.printf("%d\n",eg.getNumber()); System.out.printf("%b\n",eg.getIsrepair()); if (cs == ICar.CarState.csRunning) { System.out.print("OK"); } } }
这里如果使用System.loadlibrary时就把lib和.so去掉,只取中间的名称,如果使用System.load时就使用路径如(System.load("/home/xxxx/libcplus.so"))
运行演示:
出错了,(是因为我的eclipse找的JAVA库路径问题,高手自己设了,我这里只知道将SO库COPY到/usr/lib下,我试过放在/usr/local/lib下但不行)
于是使用cp /home/xxx/demo/libcplus.so /usr/lib
注意需要ROOT权限
最后,再次运行。OK;
其还有一个问题未得到解决,就是我用到了std::string在C++中,但在SWIG转类型时转为SWIGTYPE_p_std__string了,JAVA中用的是String。因此会报String cannot to convert SWIGTYPE_p_std__string 错误,我查了下,好像是要在.i文件中转换类型。这个请大牛指教一下了。
下集,将演示在WIN平台打包SO。
好晚了,睡觉。