跨语言的开发和调用实现数据压缩和解压缩功能

一、引言

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"的下面,不然会编译不通过。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
压缩文件 1. Function MyZip_AddFile(SrcFile,ZipFile:pchar):integer;stdcall; 功能 : 将文件SrcFile添加到压缩文档ZipFile 参数 : SrcFile 待压缩文件(全路径) Zipfile 目标文件(全路径) 返回 : 0 成功 说明 : 同名文件将自动被替换(overwrite) 2. Function MyZip_AddDirectory(SrcPath,ZipFile:pchar):integer;stdcall; 功能 : 将目录SrcPath里的所有文件(子目录)添加到压缩文档ZipFile 参数 : SrcPath 待压缩目录(全路径) Zipfile 目标文件(全路径) 返回 : 0 成功 说明 : 同名文件将自动被替换(overwrite) 解压文件 1. Function MyZip_ExtractFileAll(ZipFile,PathName:pchar):integer;stdcall; 功能 : 将ZipFile中包含的所有文件解包到文件夹PathName 参数 : ZipFile 压缩文件(全路径) PathName 文件输出路径(如果不存在,则自动创建该目录) 返回 : 0 解包的文件数量 说明 : 同名文件将自动被替换(overwrite) 2. Function MyZip_ExtractFile(ZipFile,srcName,DstName:pchar):integer;stdcall; 功能 : 从ZipFile中将由SrcName指定的文件解包到由DstName指定的目标文件 参数 : Zipfile 压缩文件(全路径) SrcName 需要解包的文件(不包含路径) DstName 目标文件(全路径) 返回 : 0 成功 说明 : 同名文件将自动被替换(overwrite) 错误信息 1. Function MyZip_GetLastError(out msg : ShortString):integer;stdcall; 功能 : 在压缩/解压的过程中,如有错误发生,可立即调用该函数获取相关错误信息,并由msg返回 参数 : msg 用于返回相关错误信息 返回 : 0 成功

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值