一、引言
1.实验目的
将c++语言进行编程实现的数据压缩和解压缩功能在java和python语言的环境下进行调用/合并,并能正确的返回结果。
2.环境配置
- 操作系统:Windows 10 X64
- 编程工具:Intellij IDEA, Visual Studio 2017, Jupyter Notebook(Anaconda)
二、Java环境下调用C++功能具体过程
1.准备阶段
(1)使用IDEA,新建Java项目:File->New->Project
(2)点击Next
(3)点击Next
(4)设置项目名称以及项目存储位置,点击Finish
2.编译java类
(1)创建java类并声明native方法
这里我们编写一个Java类,命名为Compress,定义两个native方法,一个命名为compressTest,一个命名为uncompressTest。
具体代码如下:
Compress.java:
public class Compress {
//定义两个native方法,分别对应c++里压缩文件和解压缩文件的两个函数,后面方便调用
public native void compressTest(String filename);
public native void uncompressTest(String filename);
public static void main(String[] args) {
Compress compress = new Compress();
//这里是加载从c++源文件生成的动态链接库(DLL),引号的地址换成自己的路径
Runtime.getRuntime().load("E:/eclipse-workspace2/file_compress/src/FileCompress.dll");
//这里是定义需要压缩的文件的绝对路径
String filename = "E:\\\\eclipse-workspace2\\\\file_compress\\\\src\\\\Input.BIG";
//调用函数方法
compress.compressTest(filename);
compress.uncompressTest(filename);
}
}
(2)使用javah 命令生成包含native方法声明的C/C++头文件(.h文件)
进入所创建的java源文件的目录,并在该路径下打开cmd窗口,输入指令javac Compress,生成头文件Compress.h。
生成的Compress.h和java源文件在同一个目录下:
这个文件中的生成的方法会在之后c++源文件的编写中使用到 (注意不要修改这个头文件)
3.按照生成的C/C++头文件开始编写C/C++源文件
(1)使用Visual Studio 2017,新建项目:文件->新建->项目
(2)选择Visual C++目录下的Windows 桌面,点击动态链接库(DLL),定义文件名为FileCompress,自定义文件位置,最后点击确定。
(3)用c++编写解压缩算法
这里采用的解压缩文件的算法为Huffman编码,参考博客
具体的代码如下:
FileCompress.cpp:
#include"stdafx.h"
#include<iostream>
#include<string>
#include <assert.h>
using namespace std;
#include "FileCompress.h"
#include "jni.h"
#include "Compress.h"
//定义jstring转string的方法类
std::string jstring2str(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
std::string stemp(rtn);
free(rtn);
return stemp;
}
//实现Compress.h文件中声明的方法
JNIEXPORT void JNICALL Java_Compress_compressTest
(JNIEnv *env, jobject, jstring filename) {
string file = jstring2str(env,filename);
CompressTest(file);
}
JNIEXPORT void JNICALL Java_Compress_uncompressTest
(JNIEnv *env, jobject, jstring filename) {
string file = jstring2str(env, filename);
UncompressTest(file);
}
将Heap.h,HuffmanTree.h,FileCompress.h三个头文件导入:
头文件具体实现如下:
Heap.h:
#pragma once
#include <vector>
//仿函数
template<class T>
struct Lesser
{
bool operator()(const T& l, const T& r)
{
return l < r;
}
};
template<class T>
struct Greater
{
bool operator()(const T& l, const T& r)
{
return l > r;
}
};
template<class T, class Compare = Lesser<T>>
class Heap
{
public:
Heap()
{}
Heap(const T* a, size_t size)
{
for (size_t i = 0; i < size; ++i)
{
_a.push_back(a[i]);
}
for (int i = (_a.size() - 2) / 2; i >= 0; --i)
{
_AdjustDown(i);
}
}
void Push(const T& x)
{
_a.push_back(x);
_AdjustUp(_a.size() - 1);
}
void Pop()
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);
_a.pop_back();
_AdjustDown(0);
}
T& Top()
{
assert(!_a.empty());
return _a[0];
}
bool Empty()
{
return _a.empty();
}
size_t Size()
{
return _a.size();
}
protected:
void _AdjustUp(int child)
{
Compare cmp;
int parent = (child - 1) / 2;
while (child > 0)//parent>=0 ?
{
if (cmp(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void _AdjustDown(int parent)
{
Compare cmp;
int child = parent * 2 + 1;
while (child < _a.size())
{
if (child + 1 < _a.size() && cmp(_a[child + 1], _a[child]))
{
++child;
}
if (cmp(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
protected:
vector<T> _a;
};
HuffmanTree.h:
#pragma once
#include "Heap.h"
template<class T>
struct HuffmanTreeNode
{
HuffmanTreeNode<T>* _left;
HuffmanTreeNode<T>* _right;
HuffmanTreeNode<T>* _parent;
T _weight;
HuffmanTreeNode(const T& weight)
:_left(NULL)
, _right(NULL)
, _parent(NULL)
, _weight(weight)
{}
};
template<class T>
class HuffmanTree
{
typedef HuffmanTreeNode<T> Node;
public:
HuffmanTree()
:_root(NULL)
{}
~HuffmanTree()
{
_Destory(_root);
}
HuffmanTree(const T* a, size_t size, const T& invalid)
{
_root = _CreateTree(a, size, invalid);
}
Node* GetRoot()
{
return _root;
}
protected:
Node* _CreateTree(const T* a, size_t size, const T& invalid)
{
assert(a);
struct Compare
{
bool operator()(const Node* l, const Node* r)
{
return l->_weight < r->_weight;
}
};
Heap<Node*, Compare> minHeap;
for (size_t i = 0; i < size; ++i)
{
if (a[i] != invalid)
{
minHeap.Push(new Node(a[i]));
}
}
while (minHeap.Size() > 1)
{
Node* left = minHeap.Top();
minHeap.Pop();
Node* right = minHeap.Top();
minHeap.Pop();
Node* parent = new Node(left->_weight + right->_weight);
parent->_left = left;
parent->_right = right;
left->_parent = parent;
right->_parent = parent;
minHeap.Push(parent);
}
return minHeap.Top();
}
void _Destory(Node* root)
{
if (root == NULL)
return;
_Destory(root->_left);
_Destory(root->_right);
}
protected:
Node* _root;
};
void HuffmanTreeTest()
{
int a[] = { 0,1,2,3,4,5,6,7,8,9 };
HuffmanTree<int> ht(a, 10, -1);
}
FileCompress.h:
#pragma once
#pragma warning(disable:4996)
#include "HuffmanTree.h"
#include <Windows.h>
struct CharInfo
{
char _ch;
int _count;
string _code;
CharInfo(unsigned char ch = 0)
:_ch(ch)
, _count(0)
{}
CharInfo operator+(const CharInfo& x)
{
CharInfo tmp;
tmp._count = _count + x._count;
return tmp;
}
bool operator!=(const CharInfo& x) const
{
return _count != x._count;
}
bool operator<(const CharInfo& x) const
{
return _count < x._count;
}
};
template<class T>
class FileCompress
{
public:
FileCompress()
{
for (size_t i = 0; i < 256; ++i)
{
_infos[i] = i;
}
}
void Compress(const char* filename)
{
assert(filename);
FILE* fOutFile = fopen(filename, "rb");
assert(fOutFile);
char ch = fgetc(fOutFile);
int charCount = 0;//统计字符总数
while (!feof(fOutFile))
{
++charCount;
_infos[(unsigned char)ch]._count++;
ch = fgetc(fOutFile);
}
//创建Huffman树
CharInfo invalid(0);
HuffmanTree<CharInfo> t(_infos, 256, invalid);
//由Huffman树生成Huffman编码
_GenerateHuffmanCode(t.GetRoot());
//压缩
string compressFilename = filename;
compressFilename += ".compress";
FILE* fInCompress = fopen(compressFilename.c_str(), "wb");
assert(fInCompress);
fseek(fOutFile, 0, SEEK_SET);
ch = fgetc(fOutFile);
char value = 0;
int size = 0;
while (!feof(fOutFile))
{
string& code = _infos[(unsigned char)ch]._code;
for (size_t i = 0; i < code.size(); ++i)
{
value <<= 1;
if (code[i] == '1')
{
value |= 1;
}
++size;
if (size == 8)
{
fputc(value, fInCompress);
size = 0;
value = 0;
}
}
ch = fgetc(fOutFile);
}
if (size > 0)//补位
{
value <<= (8 - size);
fputc(value, fInCompress);
}
//写配置文件,方便解压缩时读取
string configFilename = filename;
configFilename += ".config";
FILE* fInConfig = fopen(configFilename.c_str(), "wb");
assert(fInConfig);
string line;
char buffer[128];
//将字符总数写入配置文件第一行
line += itoa(charCount, buffer, 10);
line += "\n";
fputs(line.c_str(), fInConfig);
line.clear();
for (size_t i = 0; i < 256; ++i)
{
if (_infos[i]._count)
{
line += _infos[i]._ch;
line += ',';
line += itoa(_infos[i]._count, buffer, 10);
line += '\n';
fputs(line.c_str(), fInConfig);
}
line.clear();
}
fclose(fOutFile);
fclose(fInCompress);
fclose(fInConfig);
}
void UnCompress(const char* filename)
{
//读取配置文件
string configFilename = filename;
configFilename += ".config";
FILE* fOutConfig = fopen(configFilename.c_str(), "rb");
assert(fOutConfig);
string line;
//读取字符总数
_ReadLine(fOutConfig, line);
int charCount = atoi(line.c_str());
line.clear();
while (_ReadLine(fOutConfig, line))
{
if (!line.empty())
{
unsigned char ch = line[0];
_infos[ch]._count = atoi(line.substr(2).c_str());
line.clear();
}
else
{
line.clear();
_ReadLine(fOutConfig, line);
unsigned char ch = '\n';
_infos[ch]._count = atoi(line.substr(1).c_str());
line.clear();
}
}
//重建Huffman树
CharInfo invalid(0);
HuffmanTree<CharInfo> t(_infos, 256, invalid);
//读.compress文件,写.uncompress文件
string compressFilename = filename;
compressFilename += ".compress";
FILE* fOutCompress = fopen(compressFilename.c_str(), "rb");
assert(fOutCompress);
string uncompressFilename = filename;
uncompressFilename += ".uncompress";
FILE* fInUncompress = fopen(uncompressFilename.c_str(), "wb");
assert(fInUncompress);
HuffmanTreeNode<CharInfo>* root = t.GetRoot();
HuffmanTreeNode<CharInfo>* cur = root;
int pos = 7;
char ch = fgetc(fOutCompress);
while (1)
{
if (ch & (1 << pos))
cur = cur->_right;
else
cur = cur->_left;
if (cur->_left == NULL && cur->_right == NULL)
{
fputc(cur->_weight._ch, fInUncompress);
cur = root;
if (--charCount == 0)//字符已读取完,遇到补位的0不再读取
{
break;
}
}
--pos;
if (pos == -1)
{
ch = fgetc(fOutCompress);
pos = 7;
}
}
fclose(fOutCompress);
fclose(fInUncompress);
}
protected:
void _GenerateHuffmanCode(HuffmanTreeNode<CharInfo>* root)
{
if (root == NULL)
return;
_GenerateHuffmanCode(root->_left);
_GenerateHuffmanCode(root->_right);
if (root->_left == NULL && root->_right == NULL)
{
HuffmanTreeNode<CharInfo>* cur = root;
HuffmanTreeNode<CharInfo>* parent = root->_parent;
string& code = _infos[(unsigned char)cur->_weight._ch]._code;
while (parent)
{
if (parent->_left == cur)
code += '0';
if (parent->_right == cur)
code += '1';
cur = parent;
parent = cur->_parent;
}
reverse(code.begin(), code.end());
}
}
bool _ReadLine(FILE* filename, string& line)
{
char ch = fgetc(filename);
if (ch == EOF)
return false;
while (ch != EOF && ch != '\n')
{
line += ch;
ch = fgetc(filename);
}
return true;
}
protected:
CharInfo _infos[256];
};
void CompressTest(string filename)
{
const char* file = filename.c_str();
//压缩
FileCompress<CharInfo> compress;
int CompressBegin = GetTickCount();
compress.Compress(file);
int CompressEnd = GetTickCount();
cout << "Compress time:" << CompressEnd - CompressBegin << " ms" << endl;
}
void UncompressTest(string filename)
{
const char* file = filename.c_str();
//解压缩
FileCompress<CharInfo> uncompress;
int UncompressBegin = GetTickCount();
uncompress.UnCompress(file);
int UncompressEnd = GetTickCount();
cout << "Uncompress time:" << UncompressEnd - UncompressBegin << " ms" << endl;
}
(4)添加配置文件(头文件)
添加Compress.h(之前java生成的头文件),添加jni.h,添加jni_md.h
jni.h头文件是在C:\Program Files\Java\jdk1.8.0_201\include目录下,复制到你的cpp源文件所在文件夹下
jni_md.h头文件是在C:\Program Files\Java\jdk1.8.0_201\include\win32目录下,复制到你的cpp源文件所在文件夹下
这三个头文件添加完之后是这样的:
(5)将C/C++源文件编译为动态链接库(DLL)
先修改要生成的项目配置,将平台由Win32改为x64(不然之后java调用时会出错)
①点击生成->配置管理器
②点击平台,修改为x64,点击关闭
③点击生成->生成解决方案
④编译完成后,打开你的c++项目目录下的x64/Debug,可以看到编译好的.dll文件
4.在JAVA类中调用加载DLL,然后调用声明的native方法
(1)把之前编译好的FileCompress.dll文件复制到java项目src目录下
(2)将准备压缩的文件也放入java项目的src目录下
(3)运行java程序,调用声明的native方法,查看结果
运行成功,可以看到压缩时间和解压缩时间:
查看java项目src目录,可以看到压缩后的文件Input.BIG.compress,压缩过程中产生的配置文件Input.BIG.config,以及解压缩之后的文件Input.BIG.uncompress
可以看到压缩前的文件大小为34KB,压缩完之后文件大小为24KB,解压缩之后文件和压缩前大小相同
三、Python环境下调用C++功能具体过程
方法与在java里调用c++类似,先在c++里生成动态链接库.dll文件,在python里调用即可。这两者的不同点主要在于c++的函数的针对两种不同调用的不同写法。还有python调用c++不需要导入jni.h,jni_md.h和Compress.h头文件(如果之前做java的时候导过了也不影响)。具体过程如下:
1. 在上文FileCompress.cpp的文件基础上做修改
修改成如下内容:
#include"stdafx.h"
#include<iostream>
#include<string>
using namespace std;
#include <assert.h>
#include "FileCompress.h"
//首先在这里新添加定义DLLEXPORT,windows下需要使用__declspec(dllexport)的声明来说明这个函数是动态库导出的,extern “C”声明避免编译器对函数名称进行name mangling,这对于使用C++来编写DLL/SO是必须的
#define DLLEXPORT extern "C" __declspec(dllexport)
//这里是将wchar_t*转化为string类型的函数
std::string Wchar_tToString(wchar_t*wchar)
{
std::string szDst;
wchar_t * wText = wchar;
DWORD dwNum = WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, NULL, 0, NULL, FALSE);//WideCharToMultiByte的运用
char *psText; // psText为char*的临时数组,作为赋值给std::string的中间变量
psText = new char[dwNum];
WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, psText, dwNum, NULL, FALSE);//WideCharToMultiByte的再次运用
szDst = psText;// std::string赋值
delete[]psText;// psText的清除
return szDst;
}
//压缩,这里与提供给java的函数定义方式不同
DLLEXPORT wchar_t* compress(wchar_t *str) {
string file = Wchar_tToString(str);
string s = "Compress success!";
CompressTest(file);
wchar_t * wc = new wchar_t[s.size()];
swprintf(wc, 100, L"%S", s.c_str());
return wc;
}
//解压缩,这里与提供给java的函数定义方式不同
DLLEXPORT wchar_t* uncompress(wchar_t *str) {
string file = Wchar_tToString(str);
string s = "Uncompress success!";
UncompressTest(file);
wchar_t * wc = new wchar_t[s.size()];
swprintf(wc, 100, L"%S", s.c_str());
return wc;
}
修改完之后点击生成->重新生成解决方案即可
2. 编写python文件
将上一步重新编译生成的FileCompress.dll文件复制粘贴进python文件目录下。我使用的python编译器是Anaconda里的Jupyter Notebook,很好用,推荐大家使用。具体的下载途径和环境配置可以自行去网上搜索,这里就不再花费篇幅介绍了。
python文件代码如下:
from ctypes import *
dll = CDLL('FileCompress.dll')
//argtype是定义方法的传入参数类型
//restype是定义方法的返回值类型
dll.compress.argtypes = [c_wchar_p]
dll.compress.restype = c_wchar_p
dll.uncompress.argtypes = [c_wchar_p]
dll.uncompress.restype = c_wchar_p
//定义需要压缩的文件的绝对路径(压缩和解压缩的文件都在这个路径下)
filename = "E:\\py_Notebook\\中间件技术试验\\Test\\Input.BIG";
pchar = dll.compress(filename)
pchar2 = dll.uncompress(filename)
//输出压缩和解压缩的成功信息
print(pchar)
print(pchar2)
运行结果:
四、总结
在本次实验中遇到了一些问题,现在总结一下:
(1)在c++的压缩和解压缩的函数中,都需要传入文件的名称,也就是字符串格式,我在探索了几种不同的类型之后选择了传入string类型,这个也是c++标准库里的基本数据类型,它和char*相比的优势在于不必担心内存是否足够、字符串长度等等。而且在和java以及python的字符串类型转换中也比较方便,如java里调用c++传入的jstring类型,python中调用c++传入的wchar*类型。
(2)在用c++导出dll动态链接库时出现了Can’t load IA 32-bit .dll on a AMD 64-bit platform的错误,这里需要配置生成管理器,将平台从Win32换到x64,这样java和python在调用dll文件时才不会报错。
(3)在java中和在python中调用c++所需要的头文件和配置不同,java调用的c++中需要按照javah生成的头文件定义,python调用的c++需要定义DLLEXPORT extern “C” __declspec(dllexport)。这里有一个小坑是,python调用的c++定义的 DLLEXPORT extern “C” __declspec(dllexport)需要放在#include"stdafx.h"的下面,不然会编译不通过。