希望你有好运气能编译上一节我提供的Dec7z的源码,如果你成功了,接下来,我就分享一下分析7z源码并修改到符合目的的过程。
首先进入
BOOL Extra7zFileToPath(WCHAR* sTargetPath,
HWND hwnd,
BOOL bUpdate,
char* pAllData,
DWORD iLength)
这个函数,
CFileInStream archiveStream;//包含HANDLE句柄,Read和Seek
CLookToRead lookStream;
CSzArEx db;//压缩包内的文件名称和大小?
SRes res;
ISzAlloc allocImp;//包括malloc和free函数
ISzAlloc allocTempImp;//Win32 HeapAlloc和HeapFree
这里主要的代码我都做了注释,关键点一是这个:
//(*函数指针Read,Seek,Skip等赋值
FileInStream_CreateVTable(&archiveStream);//这个好办
//这个尚未清楚
//也是用的上面的Read和Seek,但多了p->pos的操作
LookToRead_CreateVTable(&lookStream, False);
//*)
通过分析7z解压的源码,就知道7z解压的最后手段无非就是fopen,fwrite在Windows系统下用宏判断定义了,读取和Seek函数,
无非就是CreateFile(打开文件),ReadFile(读物数据到内存 ),SetFilePointer(设置文件指针),CloseHandle(关闭打开的文件句柄)。
7z用了结构体的面向技术,最关键的两个是
typedef struct
{
ISeekInStream s;
CSzFile file;
} CFileInStream;
typedef struct
{
SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */
SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
} ISeekInStream;
最关键的读取和移动文件指针函数是
WRes File_Read(CSzFile *p, void *data, size_t *size);
/* writes *size bytes */
WRes File_Write(CSzFile *p, const void *data, size_t *size);
WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin);
WRes File_GetLength(CSzFile *p, UInt64 *length);
其中ISeekInStream的Read函数指针通过分析正指向File_Read函数,而File_Read函数正调用了Win32的ReadFile函数,Seek函数指针对应Win32的SetFilePointer,7z这样做非常高明,这样7z的结构体不但有数据成员,同时也通过函数指针拥有了函数成员,达到了和c++相同的面向对象的功能,又通过宏判断以适应X86,Arm等平台和编译器。
假如我们首先获取了全部数据指针char* pAllData和数据长度iLength,用memcpy代替File_Read,用自动位置的代替File_Seek不就实现了不用CreateFileA打开文件,直接解压的目的了吗? 这个想法理论上是成立的,我在修改中也遇到了不少问题。首先ReadFile函数用memcpy来代替是可以的,但ReadFile不但读取了数据,也同时改变了HANDLE文件的位置(Seek的偏移),所以需要一个全局的位置,记住文件的偏移。我建立了func.h和func.c文件,里面定义了一些全局变量和修改过的函数。
#ifndef FUNC_H
#define FUNC_H
#ifndef kChunkSizeMax
#define kChunkSizeMax (1 << 22)
#endif
#include "7zFile.h"
#include "Types.h"
extern char* g_pAllData;
extern DWORD g_iAllDataLength;
extern DWORD g_iSeekPos;
extern HANDLE* g_handle;
void g_func_GetLowAndHeightFromDWORD(DWORD v, DWORD* iLow, LONG *iHeight);
UInt64 g_func_GetDWORDByLowAndHight(DWORD sizeLow,DWORD sizeHigh);
WRes g_func_File_Close(CSzFile *p);
WRes g_func_File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin);
WRes g_func_File_Read(CSzFile *p,
void *data,
size_t *size);
#endif // FUNC_H
最关键的是这两个
WRes g_func_File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin);
WRes g_func_File_Read(CSzFile *p,
void *data,
size_t *size);
我们要用这两个函数分别替换File_Seek和File_Read函数,才能达到去除文件HANDLE,内存直接解压的目的。g_ISeekPos是全局的文件指针偏移
我是先拿File_Read函数开刀,下面是关键的函数替换修改点(7zFile.c):
static SRes FileInStream_Read(void *pp, void *buf, size_t *size)
{
CFileInStream *p = (CFileInStream *)pp;
//return (File_Read(&p->file, buf, size) == 0) ? SZ_OK : SZ_ERROR_READ;
return (g_func_File_Read(&p->file, buf, size) == 0) ? SZ_OK : SZ_ERROR_READ;
//return SZ_OK;
}
static SRes FileInStream_Seek(void *pp, Int64 *pos, ESzSeek origin)
{
CFileInStream *p = (CFileInStream *)pp;
//return File_Seek(&p->file, pos, origin);
return g_func_File_Seek(&p->file, pos, origin);
}
这样,7z真实的Read和Seek函数便到了我们控制的函数里。
WRes g_func_File_Read(CSzFile *p,
void *data,
size_t *size)
{
size_t originalSize = *size;
if (originalSize == 0)
return 0;
*size = 0;
do
{
DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
DWORD processed = 0;
DWORD iCurrentPos = 0;
UInt64 iSeekPos = 0;
//BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL);
//获取当前文件指针位置
iCurrentPos = SetFilePointer(*g_handle,NULL, NULL, FILE_CURRENT);
//memcpy(data,g_pAllData+iCurrentPos,curSize);
memcpy(data,g_pAllData+g_iSeekPos,curSize);
processed = curSize;
data = (void *)((Byte *)data + processed);
originalSize -= processed;
*size += processed;
iSeekPos = (UInt64)processed;
g_func_File_Seek(p,&iSeekPos,SZ_SEEK_CUR);
if (processed == 0)
break;
}
while (originalSize > 0);
return 0;
}
真实的Read函数里直接用memcpy代替ReadFile函数,实现内存拷贝功能
WRes g_func_File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin)
{
LARGE_INTEGER value;
DWORD moveMethod;
DWORD iCurrentPos = 0;
// Int64 zzz;
value.LowPart = (DWORD)*pos;
value.HighPart = (LONG)((UInt64)*pos >> 16 >> 16);
// zzz = *pos;
switch (origin)
{
case SZ_SEEK_SET: moveMethod = FILE_BEGIN;g_iSeekPos=(DWORD)(*pos);break;
case SZ_SEEK_CUR: moveMethod = FILE_CURRENT;g_iSeekPos+=(DWORD)(*pos);break;
case SZ_SEEK_END: moveMethod = FILE_END;g_iSeekPos=g_iAllDataLength;break;
default: return ERROR_INVALID_PARAMETER;
}
//value.LowPart = SetFilePointer(p->handle, value.LowPart, &value.HighPart, moveMethod);
//iCurrentPos = SetFilePointer(*g_handle,NULL, NULL, FILE_CURRENT);
//printf("XXOK--iCurrentPos:%d--g_iSeekPos:%d\n",iCurrentPos,g_iSeekPos);
//g_iSeekPos = iCurrentPos;
// if (value.LowPart == 0xFFFFFFFF)
// {
// WRes res = GetLastError();
// if (res != NO_ERROR)
// return res;
// }
*pos = g_iSeekPos;
//*pos = ((Int64)value.HighPart << 32) | value.LowPart;
//printf("--zzz:%d--*pos:%d\n",zzz,*pos);
return 0;
}
真实的Seek函数直接用g_iSeekPos代替SetFilePointer,注意读取文件数据到内存的同时,文件偏移也变了,所以在
WRes g_func_File_Read(CSzFile *p,
void *data,
size_t *size)
里有这么一句
而对于获取文件大小的File_GetLength函数,则直接赋值即可。
WRes File_GetLength(CSzFile *p, UInt64 *length)
{
#ifdef USE_WINDOWS_FILE
*length = (UInt64)g_iAllDataLength;
return 0;
/*
DWORD sizeHigh;
DWORD sizeLow = GetFileSize(p->handle, &sizeHigh);
if (sizeLow == 0xFFFFFFFF)
{
DWORD res = GetLastError();
if (res != NO_ERROR)
return res;
}
*length = (((UInt64)sizeHigh) << 32) + sizeLow;
return 0;*/
#else
long pos = ftell(p->file);
int res = fseek(p->file, 0, SEEK_END);
*length = ftell(p->file);
fseek(p->file, pos, SEEK_SET);
return res;
#endif
}
通过这节的大刀阔斧的修改、增加源码,lib7z解压原理已经很清楚了,修改也已完毕,下一节就让我们试试吧。