对于Windows上的数据压缩和解压缩的实现,最方便的就是直接调用Win32 API函数。Widnows系统的ntdll.dll专门提供了RtlCompressBuffer函数和RtlDecompressBuffer函数来负责对数据压缩和解压缩操作,这两个函数并未公开,需要通过在ntdll.dll中动态调用。
RtlGetCompressionWorkSpaceSize
// 确定缓冲区大小
// 返回STATUS_SUCCESS,则表示成功;否则,失败
NTSTATUS RtlGetCompressionWorkSpaceSize(
_In_ USHORT CompressionFormatAndEngine, // 位掩码指定压缩格式和引擎类型
_Out_ PULONG CompressBufferWorkSpaceSize, // 接收压缩缓冲区所需的大小
_Out_ PULONG CompressFragmentWorkSpaceSize // 接收将压缩缓冲区解压缩为片段所需的大小
);
RtlCompressBuffer
// 压缩一个缓冲区
// 返回STATUS_SUCCESS,则表示成功;否则,失败。
NTSTATUS RtlCompressBuffer(
_In_ USHORT CompressionFormatAndEngine, // 指定压缩格式和引擎类型的位掩码
_In_ PUCHAR UncompressedBuffer, // 指向要压缩的数据缓冲区的指针
_In_ ULONG UncompressedBufferSize, // UncompressedBuffer 缓冲区的大小
_Out_ PUCHAR CompressedBuffer, // 指向压缩之后的数据缓存的缓冲区的指针,用于接收压缩数据
_In_ ULONG CompressedBufferSize, // CompressedBuffer缓冲区的大小
_In_ ULONG UncompressedChunkSize, // 压缩UncompressedBuffer缓冲区时使用的块大小(512、1024、2048或4096)
_Out_ PULONG FinalCompressedSize, // 指向调用方分配变量的指针,该变量接收存储在CompressedBuffer中的压缩数据的大小
_In_ PVOID WorkSpace // 指向压缩期间RtlCompressBuffer函数使用的调用方分配的工作空间缓冲区的指针
);
RtlDecompressBuffer
// 解压缩整个压缩缓冲区。
// 返回STATUS_SUCCESS,则表示成功;否则,失败。
NTSTATUS RtlDecompressBuffer(
_In_ USHORT CompressionFormat, // 指定压缩缓冲区压缩格式的位掩码
_Out_ PUCHAR UncompressedBuffer, // 指向存储解压缩数据的缓冲区的指针,该缓冲区从CompressedBuffer接收解压缩的数据
_In_ ULONG UncompressedBufferSize, // UncompressedBuffer缓冲区的大小
_In_ PUCHAR CompressedBuffer, // 指向包含要解压缩的数据的缓冲区的指针
_In_ ULONG CompressedBufferSize, // CompressedBuffer缓冲区的大小
_Out_ PULONG FinalUncompressedSize // 指向解压之后得到的数据大小的指针,该变量接收UncompressedBuffer中存储的解压缩数据的大小
);
数据压缩主要是通过调用RtlCompressBuffer函数来实现的,那么,具体的数据压缩实现流程如下所示。
首先,先调用LoadLibrary函数加载ntdll.dll,并获取ntdll.dll加载模块的句柄。再调用GetProcAddress函数来获取RtlGetCompressionWorkSpaceSize函数以及RtlCompressBuffer函数。
然后,直接调用RtlGetCompressionWorkSpaceSize函数来获取RtlCompressBuffer函数工作空间缓冲区的大小。其中,压缩格式和引擎类型设置为COMPRESSION_FORMAT_LZNT1和COMPRESSION_ENGINE_STANDARD。然后,根据工作空间缓冲区大小申请一个工作空间缓冲区给压缩数据使用。
最后,调用RtlCompressBuffer函数来压缩数据。数据压缩缓冲区的大小为4096字节,在成功压缩数据之后,便获取实际的压缩数据大小。此时需要将实际压缩数据大小和数据压缩缓冲区大小进行比较,如果数据压缩缓冲区太小,则需要释放原来的缓冲区,重新按照实际压缩数据的大小来申请一个新的数据压缩缓冲区,并且重新压缩数据。这样,才能获取所有的压缩数据。
那么,使用RtlCompressBuffer函数压缩数据的具体实现代码如下所示。
// 数据压缩
BOOL CompressData(BYTE *pUncompressData, DWORD dwUncompressDataLength, BYTE **ppCompressData, DWORD *pdwCompressDataLength)
{
BOOL bRet = FALSE;
NTSTATUS status = 0;
HMODULE hModule = NULL;
typedef_RtlGetCompressionWorkSpaceSize RtlGetCompressionWorkSpaceSize = NULL;
typedef_RtlCompressBuffer RtlCompressBuffer = NULL;
DWORD dwWorkSpaceSize = 0, dwFragmentWorkSpaceSize = 0;
BYTE *pWorkSpace = NULL;
BYTE *pCompressData = NULL;
DWORD dwCompressDataLength = 4096;
DWORD dwFinalCompressSize = 0;
do
{
// 加载 ntdll.dll
hModule = ::LoadLibrary("ntdll.dll");
if (NULL == hModule)
{
ShowError("LoadLibrary");
break;
}
// 获取 RtlGetCompressionWorkSpaceSize 函数地址
RtlGetCompressionWorkSpaceSize = (typedef_RtlGetCompressionWorkSpaceSize)::GetProcAddress(hModule, "RtlGetCompressionWorkSpaceSize");
if (NULL == RtlGetCompressionWorkSpaceSize)
{
ShowError("GetProcAddress");
break;
}
// 获取 RtlCompressBuffer 函数地址
RtlCompressBuffer = (typedef_RtlCompressBuffer)::GetProcAddress(hModule, "RtlCompressBuffer");
if (NULL == RtlCompressBuffer)
{
ShowError("GetProcAddress");
break;
}
// 获取WorkSpqce大小
status = RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD, &dwWorkSpaceSize, &dwFragmentWorkSpaceSize);
if (0 != status)
{
ShowError("RtlGetCompressionWorkSpaceSize");
break;
}
// 申请动态内存
pWorkSpace = new BYTE[dwWorkSpaceSize];
if (NULL == pWorkSpace)
{
ShowError("new");
break;
}
::RtlZeroMemory(pWorkSpace, dwWorkSpaceSize);
while (TRUE)
{
// 申请动态内存
pCompressData = new BYTE[dwCompressDataLength];
if (NULL == pCompressData)
{
ShowError("new");
break;
}
::RtlZeroMemory(pCompressData, dwCompressDataLength);
// 调用RtlCompressBuffer压缩数据
RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1, pUncompressData, dwUncompressDataLength, pCompressData, dwCompressDataLength, 4096, &dwFinalCompressSize, (PVOID)pWorkSpace);
if (dwCompressDataLength < dwFinalCompressSize)
{
// 释放内存
if (pCompressData)
{
delete[]pCompressData;
pCompressData = NULL;
}
dwCompressDataLength = dwFinalCompressSize;
}
else
{
break;
}
}
// 返回
*ppCompressData = pCompressData;
*pdwCompressDataLength = dwFinalCompressSize;
bRet = TRUE;
} while(FALSE);
// 释放
if (pWorkSpace)
{
delete[]pWorkSpace;
pWorkSpace = NULL;
}
if (hModule)
{
::FreeLibrary(hModule);
}
return bRet;
}
数据解压缩主要是通过调用RtlDecompressBuffer函数来实现的,相比于数据压缩,数据解压缩实现起来更为简单。那么,具体的数据解压缩实现流程如下所示。
首先,同样是先调用LoadLibrary函数加载ntdll.dll,并获取ntdll.dll加载模块的句柄。再调用GetProcAddress函数RtlDecompressBuffer函数。不需要获取RtlGetCompressionWorkSpaceSize函数的地址,因为数据解压缩操作不需要确定压缩工作空间缓冲区大小
然后,开始调用RtlDecompressBuffer函数来解压缩数据。其中,压缩格式和引擎类型必须设置为COMPRESSION_FORMAT_LZNT1。数据解压缩缓冲区的初始大小为4096字节,在成功解压数据之后,便获取实际的解压数据大小。此时需要将实际解压数据大小和数据解压缓冲区大小进行比较,如果数据解压缓冲区太小,则需要释放原来的缓冲区,重新按照实际解压数据的大小来申请一个新的数据解压缓冲区,并且重新解压缩数据。这样,才能获取所有的解压数据。
那么,使用RtlDecompressBuffer函数压缩数据的具体实现代码如下所示。
// 数据解压缩
BOOL UncompressData(BYTE *pCompressData, DWORD dwCompressDataLength, BYTE **ppUncompressData, DWORD *pdwUncompressDataLength)
{
BOOL bRet = FALSE;
HMODULE hModule = NULL;
typedef_RtlDecompressBuffer RtlDecompressBuffer = NULL;
BYTE *pUncompressData = NULL;
DWORD dwUncompressDataLength = 4096;
DWORD dwFinalUncompressSize = 0;
do
{
// 加载 ntdll.dll
hModule = ::LoadLibrary("ntdll.dll");
if (NULL == hModule)
{
ShowError("LoadLibrary");
break;
}
// 获取 RtlDecompressBuffer 函数地址
RtlDecompressBuffer = (typedef_RtlDecompressBuffer)::GetProcAddress(hModule, "RtlDecompressBuffer");
if (NULL == RtlDecompressBuffer)
{
ShowError("GetProcAddress");
break;
}
while (TRUE)
{
// 申请动态内存
pUncompressData = new BYTE[dwUncompressDataLength];
if (NULL == pUncompressData)
{
ShowError("new");
break;
}
::RtlZeroMemory(pUncompressData, dwUncompressDataLength);
// 调用RtlCompressBuffer压缩数据
RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, pUncompressData, dwUncompressDataLength, pCompressData, dwCompressDataLength, &dwFinalUncompressSize);
if (dwUncompressDataLength < dwFinalUncompressSize)
{
// 释放内存
if (pUncompressData)
{
delete[]pUncompressData;
pUncompressData = NULL;
}
dwUncompressDataLength = dwFinalUncompressSize;
}
else
{
break;
}
}
// 返回
*ppUncompressData = pUncompressData;
*pdwUncompressDataLength = dwFinalUncompressSize;
bRet = TRUE;
} while (FALSE);
// 释放
if (hModule)
{
::FreeLibrary(hModule);
}
return bRet;
}
直接运行上述程序,对“DDDDDDDDDDGGGGGGGGGGGG”字符串进行压缩得到压缩数据,再对压缩数据进行解压缩得到解压数据。得到的压缩数据和解压数据如下图: