目前主流的跨语言异构模块通信方案有很多种,比如:
1、跨语言的RPC调用(Apache Thrift):它是Facebook贡献给Apache基金会的开源项目,旨在构建跨语言平台的通信方案。目前它支持非常多种语言,其中当然包括C/C++和Java。Thrift内置一个语言编译器,可以根据Thrift的语法规范,编译生成指定语言的RPC调用模块,功能也是非常的强大。Thrift的语法规范里面定义了数据类型、数据模块结构,有点类似WebService里面的WSDL文件。通过Thrift,我们就可以实现跨语言的异构模块间的通信。
2、Google Protobuf:功能上支持C/C++和Java的语言绑定,基于对象消息的序列化、反序列化技术。性能好,效率高。proto文件生成目标语言代码,就可以实现跨语言的模块通信。当然,还有一个和Google Protobuf类似的Apache开源项目Avro,也能达到这个目标。
3、FCGI服务方式:全称为FastCGI,CGI的升级版本。支持HTTP协议对其进行访问,而CGI( Common Gateway Interface)通用网关接口,本身也只是一种接口协议,它目前有支持C/C++语言的版本库。值得注意的是:FCGI、CGI本身要通过Web服务器进行消息转发方式才能被调用。常见的Web服务器有:Apache、Nginx等等。具体的调用过程是:Web服务器接收到客户端的请求之后,通过标准输入流的方式把数据送给FCGI、CGI服务,FCGI、CGI服务计算、运行之后,把结果通过标准输出流的方式返回给客户端,因此,理论上只要能操作标准输入流、标准输出流的语言都支持FCGI、CGI服务方式实现。通过上面的分析,跨语言的调用也简单了。即通过C/C++把主要的业务功能模块,封装成FCGI、CGI服务。然后Java利用HTTP协议,通过Web服务器转发到FCGI、CGI服务上,就能满足跨语言模块通信的要求。
当然,还有其它很多种跨语言的通信方式,在此,我就不一一举例了。
由于本人工作的需要,要将目前在用的一些关键业务模块,移植到Java平台下面。因为原来的大多数业务模块是用C/C++语言实现的,而且涉及的逻辑比较复杂,直接迁移到Java平台下面,开发、迁移难度不小,而且还要涉及全量的功能性覆盖测试。对于开发、测试工作能力的要求比较高。那么,除了上述归纳的跨语言通信方案之外,还有没有一种便捷、灵活的方案,能无缝的衔接Java和C/C++两种业务功能模块呢?虽然,本人还未达到上述成熟开源跨语言通信框架设计者,高屋建瓴般的能力。但是,可以基于一些成熟开源框架的设计思路、以及功能模块,完全可以打造一个,全新的跨语言异构模块通信解决方案。下面,我就重点跟大家介绍一种,基于JNA(Java Native Access)技术和C/C++异构模块通信的解决方案。
本文的灵感,主要来自于Thrift、BGCC(百度开源的Java/C++跨语言通信框架,网址为:http://bgcc.baidu.com/)中,对于跨语言生成的模块,可以通过特定的语法规范进行定制编写(当然,WebService里面也有类似的做法,比如WSDL、IDL等等)。以及,通过一些总结归纳发现:利用JNA技术对于某些特定场景的C/C++函数模块访问形式,可以抽象提取出,某种固定的代码编写策略,于是乎,诞生了本篇文章。
说到JNA,就不得不提起JNI(Java Native Interface)。JNI作为Java平台的组成部分,它允许Java代码,和其它语言编写的代码模块进行交互。JNI一开始就设计成为本地语言,尤其是为C/C++模块通信所设计的。利用JNI,Java可以和本地环境中,已经编译的代码模块进行交互。首先,Java可以通过JNI调用本地的C/C++模块组件,另外一方面,本地的C/C++代码模块也可以调用Java代码。
说完了JNI,继续说一下JNA。JNA(Java Native Access)是一个开源的Java框架,它的官方网址是:https://jna.java.net/,最早是由SUN公司主导开发的,建立在JNI基础之上的一个开源框架。它简化了JNI开发中很多繁琐的步骤,可以认为:JNA是JNI抽象层次更高的封装。利用JNA,我们只要在Java的接口中,描述目标C/C++的函数模块和数据结构,JNA就会自动实现Java接口到C/C++函数模块和数据结构的映射关系,简化了开发的步骤,降低了JNI开发的难度。本文中涉及的jna.jar可以去上述官网下载使用,下载地址:https://java.net/projects/jna/downloads。
JNA技术访问C/C++异构模块的方式
JNA是直接通过和C语言方式的API进行通信访问的。所以,如果你要利用JNA和C++通信访问的话,没有问题,但是要把C++面向对象的模块,再封装出C风格的函数即可。综上所述,利用JNA可以直接和C/C++模块进行交互通信。
JNA访问C/C++异构模块策略
最常见的情况可以看下面的图例说明。

1、情况A是最常见的情况:C语言的函数模块是常见数据类型集合。这里所谓的C语言常见数据类型(原生数据类型Primitive)是指:int、float、double、char*、int*等等原生数据类型、原生数据指针类型所构成的函数模块。
2、情况B是另外一种情况:C的函数模块涉及到一些数据模型结构,也就是C里面的结构体类型struct。结构体是由纯粹的C原生数据类型、原生数据指针类型构成的。本文中,由于要和C++模块进行通信,所以JNA/C++访问模块生成器,优先考虑一种,比较通用的生成策略:它的C函数的伪代码,表示为:void function(struct* pInArray,struct* pOutArray);其中pInArray表示待处理的结构体数组,而pOutArray表示处理后的结构体数组。
3、情况C是指:结构体类型struct里面嵌套其它的结构体,组成复合数据类型的情况。
4、情况D是指:除了情况A、B、C之外的其它种情况。不过很多种情况,都可以间接通过情况B,进行适配转换。所以,本文的例子,也是基于情况B,进行开发设计的。
JNA/C++访问模块生成器设计思路
先来看下,JNA/C++访问模块生成器设计思路的脑图:

1、支持C/C++数据对象配置可定制化
由于是基于上述JNA访问C/C++异构模块策略B的情况。因此要对访问的模块结构类型进行描述。对应的描述文件名为JNA.xml,该文件内容描述如下:
<jna>
<package>Java访问模块的包名</package>
<module>模块名称</module>
<struct>
<attr>
<name>模块属性名称</name>
<type>模块属性类型</type>
</attr>
<attr>
<name>模块属性名称</name>
<type>模块属性类型</type>
</attr>
</struct>
</jna>
其中,package节点表示对应生成Java访问模块的包名;module表示要访问的模块名称;struct节点下面,包含若干attr节点。attr节点的name表示模块某个属性的名称;attr节点的type表示模块某个属性的类型(目前只支持整型int/字符型string),上述JNA.xml对应的类图结构为:

其中,JNAModule结构体(JNA模块属性)是和JNA.xml配置文件的内容一一对应。另外一个JNAConfig模块(属性配置管理)是负责解析JNA.xml模块信息配置文件,生成对应的JNA访问模块代码和C++访问模块代码。
2、JNA访问C/C++异构模块策略可配置
目前设计的JNA访问C/C++异构模块策略只支持情况B,因此在JNA.xml配置文件中,没有体现模块策略规则属性,后续可以扩展完善。
3、自动生成JNA访问模块代码
对应的类模块为:void JNAConfig::genJNAModule(JNAModule& module)中的JNA c++ 文件头模块定义、JNA c++ 文件实现模块定义部分。
4、自动生成C/C++访问模块代码
对应的类模块为:void JNAConfig::genJNAModule(JNAModule& module)中的JNA java 模块定义模块定义部分。
现在给出完整的代码实现,首先是JNA/C++访问模块生成器的头文件(C++实现)JNA.h
/**
* @filename:JNA.h
*
* Newland Co. Ltd. All rights reserved.
*
* @Description:JNA接口生成模块定义
* @author tangjie
* @version 1.0
*
*/
#ifndef __JNA_H__
#define __JNA_H__
#ifdef WIN32
#include <Windows.h>
#include <direct.h>
#endif
#include <string>
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/typeof/std/utility.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/filesystem/fstream.hpp>
using namespace boost;
//JNA 字段名称以及字段类型,目前类型暂支持int/string
struct JNAAttr
{
string attrName;
int attrType;
enum AttrType
{
INT,
STRING
};
int getAttrType() const
{
return attrType;
}
};
//JNA 模块属性包含模块名称/包名。聚合若干JNAAttr。
struct JNAModule
{
string packageName;
string moduleName;
deque<JNAAttr> lstAttr;
};
//JNA 属性配置管理
class JNAConfig
{
public:
JNAConfig();
bool load(JNAModule& module);
void genJNAModule(JNAModule& module);
private:
string getConfigFilePath();
string getConfigFile();
};
#endif // (!__JNA_H__)
其次是JNA/C++访问模块生成器的源文件(C++实现):JNA.cpp
/**
* @filename:JNA.cpp
*
* Newland Co. Ltd. All rights reserved.
*
* @Description:JNA接口生成模块实现
* @author tangjie
* @version 1.0
*
*/
#include "stdafx.h"
#include "JNA.h"
//JNA模块实现部分
JNAConfig::JNAConfig(){}
void JNAConfig::genJNAModule(JNAModule& module)
{
string moduleName = module.moduleName;
if(moduleName.empty()) return;
//JNA c++ 文件头模块定义
{
string macro = to_upper_copy(module.moduleName);
boost::filesystem::path p(".\\"+moduleName+"Processor.h");
boost::filesystem::ofstream ofs(p.string().c_str());
ofs<<"#ifndef __"<<macro<<"PROCESSOR_H__"<<endl;
ofs<<"#define __"<<macro<<"PROCESSOR_H__"<<endl<<endl;
ofs<<"#include <deque>\n#include <algorithm>\n#include <iterator>\nusing namespace std;"<<endl<<endl;
ofs<<"typedef struct "<<moduleName<<" {"<<endl;
BOOST_FOREACH(JNAAttr attr,module.lstAttr)
{
ofs<<((attr.attrType == JNAAttr::INT) ? " int ":" char* ")<<attr.attrName<<";"<<endl;
}
ofs<<"} "<<moduleName<<";"<<endl<<endl<<endl<<"#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n";
ofs<<" class "<<moduleName<<"Processor {\n public:\n "<<moduleName<<"Processor();\n void calcRule(deque<"<<moduleName<<"> input);\n static int getResultSize(){return output.size();}\n static deque<"<<moduleName<<"> getResult(){return output;}\n\n protected:\n static deque<"<<moduleName<<"> output;\n };\n\n //C Stype API for JNA invoke\n void "<<to_lower_copy(moduleName.substr(0,1))<<moduleName.substr(1)<<"Processor("<<moduleName<<"* p"<<moduleName<<","<<moduleName<<"** pp"<<moduleName<<",int num"<<moduleName<<");\n"<<endl;
ofs<<" //because use malloc/free c-api otherwise lead to memory leaks\n void freeMemory("<<moduleName<<"* p"<<moduleName<<");\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // (!__"<<macro<<"PROCESSOR_H__)\n\n\n"<<endl;
ofs.close();
}
//JNA c++ 文件实现模块定义
{
bool existTypeString = false;
deque<JNAAttr>::iterator iter = find_if(module.lstAttr.begin(),module.lstAttr.end(),boost::bind<bool>(std::equal_to<int>(),JNAAttr::STRING,boost::bind(&JNAAttr::getAttrType, _1)));
if(iter != module.lstAttr.end())
{
existTypeString = true;
}
boost::filesystem::path p(".\\"+moduleName+"Processor.cpp");
boost::filesystem::ofstream ofs(p.string().c_str());
ofs<<"#include \""<<moduleName<<"Processor.h\"\n"<<endl;
ofs<<"deque<"<<moduleName<<"> "<<moduleName<<"Processor::output;\n"<<endl;
ofs<<moduleName<<"Processor::"<<moduleName<<"Processor()\n{\n "<<moduleName<<"Processor::output.clear();\n}\n"<<endl;
ofs<<"void "<<moduleName<<"Processor::calcRule(deque<"<<moduleName<<"> input)\n{\n if(input.empty()) return;\n\n copy(input.begin(),input.end(),std::back_inserter(output));\n}\n"<<endl;
ofs<<"void "<<to_lower_copy(moduleName.substr(0,1))<<moduleName.substr(1)<<"Processor("<<moduleName<<"* p"<<moduleName<<","<<moduleName<<"** pp"<<moduleName<<",int num"<<moduleName<<")\n{\n deque<"<<moduleName<<"> list(p"<<moduleName<<",p"<<moduleName<<"+num"<<moduleName<<");\n\n "<<moduleName<<"Processor processor;\n\n processor.calcRule(list);\n\n deque<"<<moduleName<<"> result = "<<moduleName<<"Processor::getResult();\n int number = result.size();\n\n *pp"<<moduleName<<" = ("<<moduleName<<"*)malloc(sizeof("<<moduleName<<") * number);\n\n memset(*pp"<<moduleName<<", 0, sizeof("<<moduleName<<") * number);\n\n for(int i = 0;i<number;i++)\n {"<<endl;
BOOST_FOREACH(JNAAttr attr,module.lstAttr)
{
ofs<<((attr.attrType == JNAAttr::INT) ? (" (*pp"+moduleName+")[i]."+attr.attrName+" = result[i]."+attr.attrName+";\n"+" (*pp"+moduleName+")[i]."+attr.attrName+" = result[i]."+attr.attrName+";\n"):(" int "+attr.attrName+"Len = strlen(result[i]."+attr.attrName+") + 1;\n (*pp"+moduleName+")[i]."+attr.attrName+" = (char*)malloc(sizeof(char) * "+attr.attrName+"Len);\n strcpy((*pp"+moduleName+")[i]."+attr.attrName+", result[i]."+attr.attrName+");\n"))<<endl;
}
ofs<<" }\n\n return;\n}\n"<<endl;
if(existTypeString)
{
ofs<<"void freeMemory("<<moduleName<<"* p"<<moduleName<<")\n{\n deque<"<<moduleName<<"> result = "<<moduleName<<"Processor::getResult();\n"<<endl;
ofs<<" for(int i = 0;i<result.size();i++)\n {"<<endl;
BOOST_FOREACH(JNAAttr attr,module.lstAttr)
{
if(attr.attrType == JNAAttr::STRING)
{
ofs<<" free(result[i]."<<attr.attrName<<");"<<endl;
}
}
ofs<<" }\n"<<endl;
}
else
{
ofs<<"void freeMemory("<<moduleName<<"* p"<<moduleName<<")\n{"<<endl;
}
ofs<<" free(p"<<moduleName<<");\n}\n\n"<<endl;
ofs.close();
}
//JNA java 模块定义
{
boost::filesystem::path p(".\\"+moduleName+"Processor.java");
boost::filesystem::ofstream ofs(p.string().c_str());
ofs<<"package "<<module.packageName<<";\n"<<endl;
ofs<<"import com.sun.jna.Library;\nimport com.sun.jna.Native;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.Structure;\nimport com.sun.jna.Structure.ByReference;\nimport com.sun.jna.ptr.PointerByReference;\n\nimport java.util.ArrayList;\n\n"<<endl;
ofs<<"public class "<<moduleName<<"Processor {\n public interface JNALibrary extends Library {\n public static class "<<moduleName<<"Struct extends Structure {\n public static class ByReference extends "<<moduleName<<"Struct implements\n Structure.ByReference {\n }\n"<<endl;
BOOST_FOREACH(JNAAttr attr,module.lstAttr)
{
ofs<<" public "<<(attr.attrType == JNAAttr::INT ? "int " :"String ")<<attr.attrName<<";"<<endl;
}
ofs<<" public "<<moduleName<<"Struct() {\n }\n\n public "<<moduleName<<"Struct(Pointer p) {\n super(p);\n }\n }\n\n public void "<<to_lower_copy(moduleName.substr(0,1))<<moduleName.substr(1)<<"Processor("<<moduleName<<"Struct.ByReference vals,\n PointerByReference valsRef, int numVals);\n\n public void freeMemory(Pointer p);\n }\n\n public static void invoke(ArrayList<JNALibrary."<<moduleName<<"Struct> list,\n ArrayList<JNALibrary."<<moduleName<<"Struct> listResult) {\n JNALibrary library;\n JNALibrary."<<moduleName<<"Struct.ByReference ref"<<moduleName<<" = null;\n\n PointerByReference refPtrVal;\n\n try {\n library = (JNALibrary) Native.loadLibrary(\n \"../src/lib"<<moduleName<<"Processor.so\", JNALibrary.class);\n ref"<<moduleName<<" = new JNALibrary."<<moduleName<<"Struct.ByReference();\n refPtrVal = new PointerByReference();\n } catch (UnsatisfiedLinkError e) {\n System.out.println(\"JNA invoke error\");\n library = (JNALibrary) Native.loadLibrary(\n \"./lib"<<moduleName<<"Processor.so\", JNALibrary.class);\n ref"<<moduleName<<" = new JNALibrary."<<moduleName<<"Struct.ByReference();\n refPtrVal = new PointerByReference();\n }\n\n JNALibrary."<<moduleName<<"Struct[] array = (JNALibrary."<<moduleName<<"Struct[]) ref"<<moduleName<<"\n .toArray(list.size());\n"<<endl;
ofs<<" for (int i = 0; i < list.size(); i++) {"<<endl;
BOOST_FOREACH(JNAAttr attr,module.lstAttr)
{
ofs<<" array[i]."<<attr.attrName<<" = list.get(i)."<<attr.attrName<<";"<<endl;
}
ofs<<" }\n"<<endl;
ofs<<" library."<<to_lower_copy(moduleName.substr(0,1))<<moduleName.substr(1)<<"Processor(ref"<<moduleName<<", refPtrVal, array.length);\n\n Pointer pValues = refPtrVal.getValue();\n\n JNALibrary."<<moduleName<<"Struct refValues = new JNALibrary."<<moduleName<<"Struct(\n pValues);\n\n refValues.read();\n\n JNALibrary."<<moduleName<<"Struct[] arrayResult = (JNALibrary."<<moduleName<<"Struct[]) refValues\n .toArray(list.size());\n\n for (JNALibrary."<<moduleName<<"Struct element : arrayResult) {\n listResult.add(element);\n }\n\n library.freeMemory(pValues);\n }\n}\n"<<endl;
ofs.close();
}
}
bool JNAConfig::load(JNAModule& module)
{
boost::property_tree::ptree pt;
boost::property_tree::read_xml(getConfigFile().c_str(), pt);
try
{
module.packageName = pt.get<string>("jna.package");
module.moduleName = pt.get<string>("jna.module");
//加载解析JNA模块字段定义
{
BOOST_AUTO(child, pt.get_child("jna.struct"));
for (BOOST_AUTO(pos, child.begin()); pos != child.end(); ++pos)
{
BOOST_AUTO(child_paths, pos->second.get_child(""));
JNAAttr attr;
int i = 0;
for (BOOST_AUTO(pos_paths, child_paths.begin()); pos_paths != child_paths.end(); ++pos_paths,++i)
{
if(i%2 == 0)
{
attr.attrName = pos_paths->second.data();
}
else
{
attr.attrType = to_lower_copy(string(pos_paths->second.data())).compare("string") ? JNAAttr::INT : JNAAttr::STRING;
}
}
module.lstAttr.push_back(attr);
}
}
return true;
}
catch(const std::exception& e)
{
fprintf(stderr, "%s\n", e.what());
return false;
}
catch(...)
{
fprintf(stderr, "Unexpected exception.\n");
return false;
}
}
//获取配置文件路径信息
string JNAConfig::getConfigFile()
{
return getConfigFilePath().append(".xml");
}
string JNAConfig::getConfigFilePath()
{
string result;
char path_buffer[_MAX_PATH + 1] = {0};
char drive[_MAX_DRIVE + 1] = {0};
char dir[_MAX_DIR + 1] = {0};
char fname[_MAX_FNAME + 1] = {0};
char ext[_MAX_EXT + 1] = {0};
::GetModuleFileNameA(NULL, path_buffer, sizeof(path_buffer) - 1);
_splitpath(path_buffer, drive, dir, fname, ext);
result += drive;
result += dir;
result += fname;
return result;
}
通过VS2010,编译生成目标应用程序JNA.exe,然后把定义好的JNA.xml模块描述文件,放在JNA.exe的同一个目录下面。比如,我要通过JNA访问C++模块名称为MobileUser(手机用户),对应的Java访问模块的包名:newlandframework.jna.businessrule。模块属性为:手机用户归属地市homeCity、手机用户网龄(即手机号码使用年限)netAge,类型为整型int;手机号码msisdn、用户姓名name,类型为字符型string。综上得到如下的模块描述:
<jna>
<package>newlandframework.jna.businessrule</package>
<module>MobileUser</module>
<struct>
<attr>
<name>homeCity</name>
<type>int</type>
</attr>
<attr>
<name>msisdn</name>
<type>string</type>
</attr>
<attr>
<name>name</name>
<type>string</type>
</attr>
<attr>
<name>netAge</name>
<type>int</type>
</attr>
</struct>
</jna>
双击运行,自动生成对应的C/C++访问模块代码(MobileUserProcessor.h/cpp)、JNA访问模块代码(MobileUserProcessor.java),截图如下:

生成代码内容如下所列举:
MobileUserProcessor.h文件内容如下:
#ifndef __MOBILEUSERPROCESSOR_H__
#define __MOBILEUSERPROCESSOR_H__
#include <deque>
#include <algorithm>
#include <iterator>
using namespace std;
typedef struct MobileUser {
int homeCity;
char* msisdn;
char* name;
int netAge;
} MobileUser;
#ifdef __cplusplus
extern "C" {
#endif
class MobileUserProcessor {
public:
MobileUserProcessor();
void calcRule(deque<MobileUser> input);
static int getResultSize(){return output.size();}
static deque<MobileUser> getResult(){return output;}
protected:
static deque<MobileUser> output;
};
//C Stype API for JNA invoke
void mobileUserProcessor(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser);
//because use malloc/free c-api otherwise lead to memory leaks
void freeMemory(MobileUser* pMobileUser);
#ifdef __cplusplus
}
#endif
#endif // (!__MOBILEUSERPROCESSOR_H__)
MobileUserProcessor.cpp文件内容如下:
#include "MobileUserProcessor.h"
deque<MobileUser> MobileUserProcessor::output;
MobileUserProcessor::MobileUserProcessor()
{
MobileUserProcessor::output.clear();
}
void MobileUserProcessor::calcRule(deque<MobileUser> input)
{
if(input.empty()) return;
copy(input.begin(),input.end(),std::back_inserter(output));
}
void mobileUserProcessor(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser)
{
deque<MobileUser> list(pMobileUser,pMobileUser+numMobileUser);
MobileUserProcessor processor;
processor.calcRule(list);
deque<MobileUser> result = MobileUserProcessor::getResult();
int number = result.size();
*ppMobileUser = (MobileUser*)malloc(sizeof(MobileUser) * number);
memset(*ppMobileUser, 0, sizeof(MobileUser) * number);
for(int i = 0;i<number;i++)
{
(*ppMobileUser)[i].homeCity = result[i].homeCity;
(*ppMobileUser)[i].homeCity = result[i].homeCity;
int msisdnLen = strlen(result[i].msisdn) + 1;
(*ppMobileUser)[i].msisdn = (char*)malloc(sizeof(char) * msisdnLen);
strcpy((*ppMobileUser)[i].msisdn, result[i].msisdn);
int nameLen = strlen(result[i].name) + 1;
(*ppMobileUser)[i].name = (char*)malloc(sizeof(char) * nameLen);
strcpy((*ppMobileUser)[i].name, result[i].name);
(*ppMobileUser)[i].netAge = result[i].netAge;
(*ppMobileUser)[i].netAge = result[i].netAge;
}
return;
}
void freeMemory(MobileUser* pMobileUser)
{
deque<MobileUser> result = MobileUserProcessor::getResult();
for(int i = 0;i<result.size();i++)
{
free(result[i].msisdn);
free(result[i].name);
}
free(pMobileUser);
}
MobileUserProcessor.java文件内容如下:
package newlandframework.jna.businessrule;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Structure.ByReference;
import com.sun.jna.ptr.PointerByReference;
import java.util.ArrayList;
public class MobileUserProcessor {
public interface JNALibrary extends Library {
public static class MobileUserStruct extends Structure {
public static class ByReference extends MobileUserStruct implements
Structure.ByReference {
}
public int homeCity;
public String msisdn;
public String name;
public int netAge;
public MobileUserStruct() {
}
public MobileUserStruct(Pointer p) {
super(p);
}
}
public void mobileUserProcessor(MobileUserStruct.ByReference vals,
PointerByReference valsRef, int numVals);
public void freeMemory(Pointer p);
}
public static void invoke(ArrayList<JNALibrary.MobileUserStruct> list,
ArrayList<JNALibrary.MobileUserStruct> listResult) {
JNALibrary library;
JNALibrary.MobileUserStruct.ByReference refMobileUser = null;
PointerByReference refPtrVal;
try {
library = (JNALibrary) Native.loadLibrary(
"../src/libMobileUserProcessor.so", JNALibrary.class);
refMobileUser = new JNALibrary.MobileUserStruct.ByReference();
refPtrVal = new PointerByReference();
} catch (UnsatisfiedLinkError e) {
System.out.println("JNA invoke error");
library = (JNALibrary) Native.loadLibrary(
"./libMobileUserProcessor.so", JNALibrary.class);
refMobileUser = new JNALibrary.MobileUserStruct.ByReference();
refPtrVal = new PointerByReference();
}
JNALibrary.MobileUserStruct[] array = (JNALibrary.MobileUserStruct[]) refMobileUser
.toArray(list.size());
for (int i = 0; i < list.size(); i++) {
array[i].homeCity = list.get(i).homeCity;
array[i].msisdn = list.get(i).msisdn;
array[i].name = list.get(i).name;
array[i].netAge = list.get(i).netAge;
}
library.mobileUserProcessor(refMobileUser, refPtrVal, array.length);
Pointer pValues = refPtrVal.getValue();
JNALibrary.MobileUserStruct refValues = new JNALibrary.MobileUserStruct(
pValues);
refValues.read();
JNALibrary.MobileUserStruct[] arrayResult = (JNALibrary.MobileUserStruct[]) refValues
.toArray(list.size());
for (JNALibrary.MobileUserStruct element : arrayResult) {
listResult.add(element);
}
library.freeMemory(pValues);
}
}
其中C函数void mobileUserProcessor(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser);中的参数描述如下:
MobileUser* pMobileUser表示待处理的结构模块数组(一维指针);MobileUser** ppMobileUser表示处理后的结构模块数组(二维指针);int numMobileUser表示待处理的结构模块数组的大小。
利用JNA.exe(JNA/C++访问模块生成器)生成代码实现Java/C++通信访问
要实现Java和C++的跨语言通信访问,我们先来看下操作步骤:

现在我们根据上面的步骤一步一步来说明:
首先,我们完成一个简单的功能:通过Java访问模块MobileUser,Java传入待处理的MobileUser数组集合,然后C++直接简单地,把待处理数据直接拷贝返回(当然,你可以在生成代码模板的基础上,进行二次开发定制。),最后Java的客户端打印出C++返回的集合内容。
1、编辑JNA.xml定义通信模块信息
配置JNA.xml模块信息,MobileUser结构属性内容如下:
<jna>
<package>newlandframework.jna.businessrule</package>
<module>MobileUser</module>
<struct>
<attr>
<name>homeCity</name>
<type>int</type>
</attr>
<attr>
<name>msisdn</name>
<type>string</type>
</attr>
<attr>
<name>name</name>
<type>string</type>
</attr>
<attr>
<name>netAge</name>
<type>int</type>
</attr>
</struct>
</jna>
2、运行JNA.exe (JNA/C++访问模块生成器)
把JNA.exe和JNA.xml放在同一目录下面,双击JNA.exe生成对应的JNA访问模块、C/C++访问模块。
3、根据策略生成JNA访问模块
即JNA.exe生成的MobileUserProcessor.java代码模块。
4、根据策略生成C/C++访问模块
即JNA.exe生成的MobileUserProcessor.h/cpp代码模块。
5、编译C/C++访问模块并生成对应动态库
编译MobileUserProcessor.h/cpp代码模块得到动态库libMobileUserProcessor.so,Makefile参考如下:
GEN_SRC = $(shell ls .*.cpp)
rule:MobileUserProcessor.cpp
g++ -I../inc -Wall -Wextra -pedantic -g -O2 -std=c++98 -MD -MP -c -o MobileUserProcessor.o ./MobileUserProcessor.cpp;
g++ -shared -o libMobileUserProcessor.so MobileUserProcessor.o;
6、编译JNA模块生成.class文件
编译MobileUserProcessor.java代码模块,得到.class字节码文件,参考命令如下:javac -classpath jna.jar -d . MobileUserProcessor.java ProcessMain.java。
7、打包JNA模块.class文件以及动态库
I、先到https://java.net/projects/jna/downloads 的官网下载jna.jar,然后把它解压出来。解压命令参考:jar xvf jna.jar com/
II、编写调用客户端ProcessMain.java,传入待处理的数据集合一共5个,手机用户名称分别为Jim、Tom、John、Dave、William,给JNA里面的C++模块MobileUserProcessor.invoke。并打印C++的处理结果。现在给出调用客户端ProcessMain.java的实现:
package newlandframework.jna.businessrule;
import java.util.ArrayList;
import newlandframework.jna.businessrule.MobileUserProcessor.JNALibrary;
public class ProcessMain {
public ProcessMain() {
}
public static void main(String[] args) {
ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct> list = new ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct>();
ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct> listResult = new ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct>();
MobileUserProcessor.JNALibrary.MobileUserStruct userA = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userB = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userC = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userD = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userE = new MobileUserProcessor.JNALibrary.MobileUserStruct();
userA.homeCity = 591;
userA.msisdn = "5911000";
userA.name = "Jim";
userA.netAge = 2;
list.add(userA);
userB.homeCity = 592;
userB.msisdn = "5921000";
userB.name = "Tom";
userB.netAge = 4;
list.add(userB);
userC.homeCity = 593;
userC.msisdn = "5931000";
userC.name = "John";
userC.netAge = 5;
list.add(userC);
userD.homeCity = 594;
userD.msisdn = "5941000";
userD.name = "Dave";
userD.netAge = 5;
list.add(userD);
userE.homeCity = 595;
userE.msisdn = "5951000";
userE.name = "William";
userE.netAge = 3;
list.add(userE);
MobileUserProcessor.invoke(list, listResult);
for(int i = 0; i < listResult.size(); i++) {
System.out.println(listResult.get(i).homeCity);
System.out.println(listResult.get(i).msisdn);
System.out.println(listResult.get(i).name);
System.out.println(listResult.get(i).netAge);
System.out.println("----------------------------------------------");
}
}
}
III、编写MANIFEST.MF文件,文件内容参考如下:
Manifest-Version: 1.0 Class-Path: . Main-Class: newlandframework.jna.businessrule.ProcessMain Name: com/sun/jna/ Specification-Title: Java Native Access (JNA) Implementation-Title: com.sun.jna Implementation-Version: 3.3.0 Specification-Version: 3 Implementation-Vendor: JNA Development Team Specification-Vendor: JNA Development Team
IV、然后把C++模块的动态库libMobileUserProcessor.so、MobileUserProcessor.java、ProcessMain.java代码模块编译出的字节码文件、jna.jar的字节码文件、MANIFEST.MF打包生成MobileUserProcessor.jar。命令参考如下:jar cvfm MobileUserProcessor.jar ./META-INF/MANIFEST.MF ./newlandframework/jna/businessrule/MobileUserProcessor.class ./newlandframework/jna/businessrule/ProcessMain.class com ../src/libMobileUserProcessor.so。
8、运行jar包完成跨语言通信访问
在终端输入:java -jar MobileUserProcessor.jar,然后运行截图如下:

Java程序中,送入待处理的手机用户名称:Jim、Tom、John、Dave、William,通过JNA访问C++的处理模块。正如我们预计的一样,正确打印出了处理结果。
基于JNA.exe(JNA/C++访问模块生成器)生成代码的二次开发
现在我们更进一步,在原来生成的JNA/C++代码上面进行二次开发。现在业务算法要求:通过JNA调用C++的处理模块,传入MobileUser数组集合,计算出集合中网龄最大和最小的前N个排名的记录,然后,把排名结果返回,Java打印出排序结果。
首先,还是通过JNA.exe生成对应的JNA/C++代码模块,然后进行二次开发,根据上述的业务要求,加入处理逻辑,封装出C风格的处理模块:
找出手机用户MobileUser网龄最小的前topN个数据:
void minTopNByNetAge(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser,int topN);
找出手机用户MobileUser网龄最大的前topN个数据:
void maxTopNByNetAge(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser,int topN);
然后对应的代码内容如下:
MobileUserProcessor.h(C++)代码内容如下:
/**
* @filename:MobileUserProcessor.h
*
* Newland Co. Ltd. All rights reserved.
*
* @Description:手机用户网龄TOPN排名管理类定义
* @author tangjie
* @version 1.0
*
*/
#ifndef __MOBILEUSERPROCESSOR_H__
#define __MOBILEUSERPROCESSOR_H__
#include <string>
#include <iostream>
#include <deque>
#include <algorithm>
#include <functional>
#include <iterator>
#include <memory.h>
using namespace std;
//JNA异构模块定义
typedef struct MobileUser {
int homeCity;//归属地市
char* msisdn;//手机号码
char* name;//用户名称
int netAge;//网龄
} MobileUser;
//比较网龄
namespace std
{
template<>
struct less<MobileUser> :
public binary_function<MobileUser,MobileUser,bool>
{
bool operator()(const MobileUser& rhs1,const MobileUser& rhs2) const
{
return rhs1.netAge < rhs2.netAge;
}
};
}
#ifdef __cplusplus
extern "C" {
#endif
//计算规则处理基类
class BusinessRuleProcessor {
public:
BusinessRuleProcessor();
virtual void calcRule(deque<MobileUser> input) = 0;
static int getResultSize(){return output.size();}
static deque<MobileUser> getResult(){return output;}
protected:
static deque<MobileUser> output;
};
//取网龄最小的topN
class MinTopNNetAgeProcessor : public BusinessRuleProcessor {
public:
MinTopNNetAgeProcessor(int topN);
void calcRule(deque<MobileUser> input);
private:
int topN;
};
//取网龄最大的topN
class MaxTopNNetAgeProcessor : public BusinessRuleProcessor {
public:
MaxTopNNetAgeProcessor(int topN);
void calcRule(deque<MobileUser> input);
private:
int topN;
};
//C Stype API for JNA invoke
void minTopNByNetAge(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser,int topN);
void maxTopNByNetAge(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser,int topN);
void findTopNByNetAge(BusinessRuleProcessor* pRuleProcessor,MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser);
//because use malloc/free c-api otherwise lead to memory leaks
void freeMemory(MobileUser* pMobileUser);
#ifdef __cplusplus
}
#endif
#endif // (!__MOBILEUSERPROCESSOR_H__)
MobileUserProcessor.cpp(C++)代码内容如下:
/**
* @filename:MobileUserProcessor.cpp
*
* Newland Co. Ltd. All rights reserved.
*
* @Description:手机用户网龄TOPN排名管理类实现
* @author tangjie
* @version 1.0
*
*/
#include "MobileUserProcessor.h"
//计算规则处理基类
deque<MobileUser> BusinessRuleProcessor::output;
BusinessRuleProcessor::BusinessRuleProcessor()
{
BusinessRuleProcessor::output.clear();
}
//取网龄最小的topN
MinTopNNetAgeProcessor::MinTopNNetAgeProcessor(int topN):BusinessRuleProcessor(){this->topN = topN;}
void MinTopNNetAgeProcessor::calcRule(deque<MobileUser> input)
{
if(input.empty()) return;
sort(input.begin(),input.end(),less<MobileUser>());
int interval = (this->topN >= input.size()) ? input.size() : this->topN;
copy(input.begin(),input.begin()+interval,std::back_inserter(output));
}
//取网龄最大的topN
MaxTopNNetAgeProcessor::MaxTopNNetAgeProcessor(int topN):BusinessRuleProcessor(){this->topN = topN;}
void MaxTopNNetAgeProcessor::calcRule(deque<MobileUser> input)
{
if(input.empty()) return;
sort(input.begin(),input.end(),not2(less<MobileUser>()));
int interval = (this->topN >= input.size()) ? input.size() : this->topN;
copy(input.begin(),input.begin()+interval,std::back_inserter(output));
}
//C Stype API for JNA invoke
void findTopNByNetAge(BusinessRuleProcessor* pRuleProcessor,MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser)
{
deque<MobileUser> list(pMobileUser,pMobileUser+numMobileUser);
pRuleProcessor->calcRule(list);
deque<MobileUser> result = BusinessRuleProcessor::getResult();
int number = result.size();
*ppMobileUser = (MobileUser*)malloc(sizeof(MobileUser) * number);
memset(*ppMobileUser, 0, sizeof(MobileUser) * number);
for(int i = 0;i<number;i++)
{
(*ppMobileUser)[i].homeCity = result[i].homeCity;
(*ppMobileUser)[i].netAge = result[i].netAge;
int msisdnLen = strlen(result[i].msisdn)+1;
(*ppMobileUser)[i].msisdn = (char*)malloc(sizeof(char) * msisdnLen);
memset((*ppMobileUser)[i].msisdn, 0, sizeof(char) * msisdnLen);
strcpy((*ppMobileUser)[i].msisdn, result[i].msisdn);
int nameLen = strlen(result[i].name)+1;
(*ppMobileUser)[i].name = (char*)malloc(sizeof(char) * nameLen);
memset((*ppMobileUser)[i].name, 0, sizeof(char) * nameLen);
strcpy((*ppMobileUser)[i].name, result[i].name);
}
return;
}
void minTopNByNetAge(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser,int topN)
{
auto_ptr<BusinessRuleProcessor> pRuleProcessor(new MinTopNNetAgeProcessor(topN));
findTopNByNetAge(pRuleProcessor.get(),pMobileUser,ppMobileUser,numMobileUser);
}
void maxTopNByNetAge(MobileUser* pMobileUser,MobileUser** ppMobileUser,int numMobileUser,int topN)
{
auto_ptr<BusinessRuleProcessor> pRuleProcessor(new MaxTopNNetAgeProcessor(topN));
findTopNByNetAge(pRuleProcessor.get(),pMobileUser,ppMobileUser,numMobileUser);
}
void freeMemory(MobileUser* pMobileUser)
{
deque<MobileUser> result = BusinessRuleProcessor::getResult();
for(int i = 0;i<result.size();i++)
{
free(result[i].msisdn);
free(result[i].name);
}
free(pMobileUser);
}
MobileUserProcessor.java(Java)代码内容如下:
/**
* @filename:MobileUserProcessor.java
*
* Newland Co. Ltd. All rights reserved.
*
* @Description:手机用户网龄TOPN排名管理JNA类实现
* @author tangjie
* @version 1.0
*
*/
package newlandframework.jna.businessrule;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.Structure.ByReference;
import com.sun.jna.ptr.PointerByReference;
import java.util.ArrayList;
public class MobileUserProcessor {
public interface JNALibrary extends Library {
public static class MobileUserStruct extends Structure {
public static class ByReference extends MobileUserStruct implements
Structure.ByReference {
}
public int homeCity;
public String msisdn;
public String name;
public int netAge;
public MobileUserStruct() {
}
public MobileUserStruct(Pointer p) {
super(p);
}
}
//网龄最大topN
public void maxTopNByNetAge(MobileUserStruct.ByReference vals,
PointerByReference valsRef, int numVals, int topN);
//网龄最小topN
public void minTopNByNetAge(MobileUserStruct.ByReference vals,
PointerByReference valsRef, int numVals, int topN);
//释放JNA内存模块
public void freeMemory(Pointer p);
}
//通过JNA访问调用C++的实现
public static void invoke(ArrayList<JNALibrary.MobileUserStruct> list,
ArrayList<JNALibrary.MobileUserStruct> listResult,int topN) {
JNALibrary library;
JNALibrary.MobileUserStruct.ByReference refMobileUser = null;
//和C++的指针的指针绑定映射,其内容为保存处理结果的首地址
PointerByReference refPtrVal;
try {
library = (JNALibrary) Native.loadLibrary(
"../src/libMobileUserProcessor.so", JNALibrary.class);
refMobileUser = new JNALibrary.MobileUserStruct.ByReference();
refPtrVal = new PointerByReference();
} catch (UnsatisfiedLinkError e) {
System.out.println("JNA invoke error");
library = (JNALibrary) Native.loadLibrary(
"./libMobileUserProcessor.so", JNALibrary.class);
refMobileUser = new JNALibrary.MobileUserStruct.ByReference();
refPtrVal = new PointerByReference();
}
//为了兼容JNA,用C的方式封装了C++的逻辑模块。传入给C函数的数据,在Java里面映射成数组Array
JNALibrary.MobileUserStruct[] array = (JNALibrary.MobileUserStruct[]) refMobileUser
.toArray(list.size());
for (int i = 0; i < list.size(); i++) {
array[i].homeCity = list.get(i).homeCity;
array[i].msisdn = list.get(i).msisdn;
array[i].name = list.get(i).name;
array[i].netAge = list.get(i).netAge;
}
//取网龄最大的前topN移动手机用户
library.maxTopNByNetAge(refMobileUser, refPtrVal, array.length, topN);
//取网龄最小的前topN移动手机用户
//library.minTopNByNetAge(refMobileUser, refPtrVal, array.length, topN);
//其内容为处理结果数组的首地址
Pointer pValues = refPtrVal.getValue();
JNALibrary.MobileUserStruct refValues = new JNALibrary.MobileUserStruct(
pValues);
refValues.read();
JNALibrary.MobileUserStruct[] arrayResult = (JNALibrary.MobileUserStruct[]) refValues
.toArray(topN);
for (JNALibrary.MobileUserStruct element : arrayResult) {
listResult.add(element);
}
//JNA释放C++的堆内存,否则会内存泄露
library.freeMemory(pValues);
}
}
然后,编写一个客户端ProcessMain.java传入要处理的数据,具体参考代码如下:
/**
* @filename:ProcessMain.java
*
* Newland Co. Ltd. All rights reserved.
*
* @Description:手机网龄排名JNA调用主函数
* @author tangjie
* @version 1.0
*
*/
package newlandframework.jna.businessrule;
import java.util.ArrayList;
import newlandframework.jna.businessrule.MobileUserProcessor.JNALibrary;
public class ProcessMain {
public ProcessMain() {
}
public static void main(String[] args) {
ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct> list = new ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct>();
ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct> listResult = new ArrayList<MobileUserProcessor.JNALibrary.MobileUserStruct>();
//构造一些测试用户
MobileUserProcessor.JNALibrary.MobileUserStruct userA = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userB = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userC = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userD = new MobileUserProcessor.JNALibrary.MobileUserStruct();
MobileUserProcessor.JNALibrary.MobileUserStruct userE = new MobileUserProcessor.JNALibrary.MobileUserStruct();
userA.homeCity = 591;
userA.msisdn = "5911000";
userA.name = "Jim";
userA.netAge = 2;
list.add(userA);
userB.homeCity = 592;
userB.msisdn = "5921000";
userB.name = "Tom";
userB.netAge = 4;
list.add(userB);
userC.homeCity = 593;
userC.msisdn = "5931000";
userC.name = "John";
userC.netAge = 5;
list.add(userC);
userD.homeCity = 594;
userD.msisdn = "5941000";
userD.name = "Dave";
userD.netAge = 5;
list.add(userD);
userE.homeCity = 595;
userE.msisdn = "5951000";
userE.name = "William";
userE.netAge = 3;
list.add(userE);
//取网龄最大的前3的移动手机用户
int topN = 3;
//通过JNA调用C++的逻辑处理模块
MobileUserProcessor.invoke(list, listResult, topN);
//打印处理结果
for (int i = 0; i < listResult.size(); i++) {
System.out.println(listResult.get(i).homeCity);
System.out.println(listResult.get(i).msisdn);
System.out.println(listResult.get(i).name);
System.out.println(listResult.get(i).netAge);
System.out.println("----------------------------------------------");
}
}
}
Jim网龄2年、Tom网龄4年、John网龄5年、Dave网龄5年、William网龄3年。分别找到网龄最大的前3名用户信息和网龄最小的前3名用户信息。编译运行过程参考:利用JNA.exe(JNA/C++访问模块生成器)生成代码实现Java/C++通信访问 章节。
然后最终的运行结果如下:
处理访问网龄最大的前3名用户资料:

处理访问网龄最小的前3名用户资料:

正如我们所预计的结果一样,完全正确!利用JNA我们很快的把C++模块和Java模块,有机的结合在一起了。
写在最后
本文通过JNA技术,实现了Java和C/C++两种语言模块的通信调用,但是,目前仅仅实现了特定访问策略生成模块的部分代码。后续有时间,我还会继续归纳总结出,其它JNA访问策略的通用共性代码,使得上述框架变得更加灵活、通用。算是为Java/C++的跨语言通信调用,提供另外一种可行的解决方案。当然,要想继续完善上述的通信框架,对于JNA的进一步深入理解是必不可少的,希望有Geek精神的各位园友们,可以在此基础上加以改良,不断完善,让JNA为我所用。更多的JNA的技术细节大家可以参考:https://jna.java.net/ 上面的API文档。当然,如果你具备较好的C++功底的话,理解JNA里面的一些实现方式和功能模块,会更加地得心应手。洋洋洒洒写了这么多,如果面前的你,觉得本文写得还不错,对您的学习、工作,有借鉴意义的话,可以点击“推荐”一下,举手之劳。算是对我辛苦写博客的一种肯定和鼓励吧!
最后,谢谢大家耐心读完整篇文章!希望我的一点儿实践和总结,对您有帮助!

浙公网安备 33010602011771号