前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >以金山界面库(openkui)为例思考和分析界面库的设计和实现——资源读取模块分析

以金山界面库(openkui)为例思考和分析界面库的设计和实现——资源读取模块分析

作者头像
方亮
发布2019-01-16 10:53:49
9140
发布2019-01-16 10:53:49
举报
文章被收录于专栏:方亮方亮

        按照软件的执行流程,我们首先遇到《以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题》中提出的最后一个问题:界面描述文件的放置位置。我们曾提出一种方案:将界面描述文件打包后放在资源文件中;在使用时,解析并读取资源文件。实际上Kui也是按照我们这个思路在做的,只是做得比我们要精巧。在阅读这部分代码的过程中,我发现其存在一定的编码缺陷以及设计缺陷。我会在文中适时指出问题并提出修正及改进的方案。(转载请指明出于breaksoftware的csdn博客)

        为了表述方便,我们将以KUI自带的例子工程Sample1为例。在该项目的res目录下,我们看到一个名字为sample1.kui的文件。

        在Sample1工程的资源文件中,上图中sample1.kui将作为一个类型为“SKIN”,名字为“KUIRED.DAT”的资源。

        从这个特殊的后缀名.kui可以猜测出,这个文件是一个压缩文件。

        这样,我们心里有了底,同时为我们阅读Kui的资源管理代码提供了视觉上的参考。         在openkui\KUILib\kscbase\src下有个文件kscres.cpp。它定义了资源文件处理逻辑。         首先,我们查看这段代码

代码语言:javascript
复制
KAppRes& KAppRes::Instance()
{
	static KAppRes _singleton;
	return _singleton;
}

        可以看出,这是个单例类。因为界面描述数据只需要读取和解析一次,所以这儿设计成单例类。以后使用它的地方,就不用重复读取和解析了。         我们再看下作为私有函数的构造函数,它显示该类执行的脉络

代码语言:javascript
复制
KAppRes::KAppRes() : m_hTempRes(INVALID_HANDLE_VALUE)
{
	PrepareRes();
	OpenResPack();
	LoadStringRes();
	LoadImageRes();
	LoadXmlRes();
	LoadFontRes();
}

        粗略看了函数名。可以得出如下流程

        除了“读取String”、“读取Image”和“读取字体”资源外,我们可能比较难以猜测到其他过程做了什么。如果按照我前一篇的思路,“预处理资源文件”可能对应于“读取指定资源”,“打开资源文件”可能对应于“将压缩包文件解压”,是不是如此呢?我们拭目以待。在解读之后的代码之前,我有个疑问,这些操作如果有一步没有成功,还有必要继续往下走么?怎么就没一个判断?放下这个问题,我们看之后的代码。

        我们先看

代码语言:javascript
复制
bool KAppRes::PrepareRes()
{
    bool retval = false;
    KFilePath pathRes = KFilePath::GetFilePath(g_hInstance);
    HRSRC hResInfo = NULL;
    HGLOBAL hResDat = NULL;
    PVOID pResBuffer = NULL;
    DWORD dwResBuffer;
    wchar_t szTempPath[MAX_PATH] = { 0 };
    wchar_t szTempFilePath[MAX_PATH] = { 0 };
    pathRes.RemoveExtension();
    pathRes.AddExtension(L"kui");

    if (GetFileAttributesW(pathRes) != INVALID_FILE_ATTRIBUTES)
    {
        m_strResPackPath = pathRes.value();
    }
    else
    {
        hResInfo = FindResourceW(_ModulePtr->GetResourceInstance(), L"kuires.dat", L"SKIN");
        if (!hResInfo)
            goto clean0;
        
        hResDat = LoadResource(_ModulePtr->GetResourceInstance(), hResInfo);
        if (!hResDat)
            goto clean0;

        pResBuffer = LockResource(hResDat);
        if (!pResBuffer)
            goto clean0;

        dwResBuffer = SizeofResource(_ModulePtr->GetResourceInstance(), hResInfo);
	      m_memZipRes.SetData(pResBuffer, dwResBuffer);
    }
    
    retval = true;

clean0:
    return retval;
}

        到12行,都是在Exe文件所在目录拼接出与Exe文件同名,但是后缀为kui的资源文件。比如我的电脑上,调试文件目录是D:\快盘\Code Project\openkui\Samples\Sample1\Debug\Sample1.exe,得到的pathRes对应的目录是D:\快盘\Code Project\openkui\Samples\Sample1\Debug\Sample1.kui。如果该资源文件独立存在于Exe目录下,则使用该文件做后续操作。如果该文件不存在,则从PE文件资源中,读取出类型为“SKIN”、名字为“kuires.data”的资源,并保存在memZipRes(一段内存中)中。

        这个流程,我们可以看出来,其大体思路和我之前猜测的一致,只是它增加了优先对独立的压缩包资源文件的处理。于是我们可以得出:Kui的界面描述文件,可以放在:         1 Exe文件所在的目录下,名字和Exe相同的、后缀为kui的文件(以后简称界面文件包)中         2 PE文件资源类型为“SKIN”、名字为“kuires.dat”的资源(以后简称界面内存块)中         其中1的优先级要高于2。         这种设计方案还是很有意思的。因为这个流程可以实现换肤功能。比如我们下载了A.kui、B.kui、C.kui和D.kui四套皮肤。如果用户选择了A皮肤,则我们可以将A.kui拷贝到Exe所在目录,并将其命名为与Exe同名、后缀为kui的名字。这样就实现了换肤。即使这套外置皮肤坏了,或者被删了,我们还可以使用资源中的那套皮肤。

        虽然想法很好,但是代码中的逻辑却存在一定的编码缺陷和设计缺陷,我们先说编码缺陷:

代码语言:javascript
复制
    if (GetFileAttributesW(pathRes) != INVALID_FILE_ATTRIBUTES)
    {
        m_strResPackPath = pathRes.value();
    }

        这步,可以用来判断一个文件是否存在么?其实不可以。因为如果我新建一个与压缩包同名的“文件夹”,GetFileAttributesW将返回FILE_ATTRIBUTE_DIRECTORY,这将导致这个错误的逻辑认为该文件夹是一个压缩文件,从而导致之后的逻辑出现处理异常。该函数应该写成

代码语言:javascript
复制
    if ( PathFileExists(pathRes) && 
         0 == ( GetFileAttributesW(pathRes) & FILE_ATTRIBUTE_DIRECTORY ) )
    {
        m_strResPackPath = pathRes.value();
    }

        其中还有个设计缺陷。假如我们是使用这个库的开发者,我们在调试过程中,难免会修改界面描述文件。那么难道我们每修改一次,都要将描述文件压缩成一个包么?这样不是很难调用?我觉得,可以在PrepareRes函数中,新增一段对debug情况的处理:在debug情况下我们应该获取工程res目录下一个特定的文件夹,该文件夹保存了未压缩的各个文件。这样我们就可以不用每次修改资源后都要打个资源包了。         我们在KAppRes类私有成员中增加

代码语言:javascript
复制
#ifdef DEBUG
    // 保存debug环境下界面描述文件文件夹目录
    std::wstring m_strResFloderPath;
#endif

        在PrepareRes的pathRes.RemoveExtension();之前新增

代码语言:javascript
复制
#ifdef DEBUG
    pathRes.RemoveFileSpec();
    pathRes.RemoveFileSpec();
    pathRes.AddBackslash();
    pathRes.Append(L"res");
    pathRes.AddBackslash();
    pathRes.Append(L"skin");
    pathRes.AddBackslash();
    if ( PathFileExists(pathRes) && GetFileAttributesW(pathRes) & FILE_ATTRIBUTE_DIRECTORY )
    {
        m_strResFloderPath = pathRes.value();
        return true;
    }
    else
    {
        _ASSERT_EXPR(FALSE, L"Debug环境下要求res目录下skin目录保存界面描述文件");
        return false;
    }
#endif

        这样我们将方便我们调试工作。         接下来我们看OpenResPack这个函数。在PrepareRes中,我们可能会得到界面文件包或者界面内存块。OpenResPack将先后尝试从这两个位置获取界面信息。在这个函数中,我们将看到,如何使用开源的Zlib代码去获取压缩包(内存)中文件的信息。

代码语言:javascript
复制
bool KAppRes::OpenResPack()
{
	bool retval = false;
	zlib_filefunc_def zip_funcs;
	std::string strPathAnsi;
	int nRetCode;

	HRSRC hResInfo = NULL;
	HGLOBAL hResDat = NULL;
	PVOID pResBuffer = NULL;
	DWORD dwResBuffer = 0;

        fill_win32_filefunc(&zip_funcs);
        strPathAnsi = UnicodeToAnsi(m_strResPackPath);
        m_pResPackData = unzOpen2(strPathAnsi.c_str(), &zip_funcs);

        if (m_pResPackData)
	    goto UNZRESPACKDATA;

        这段代码是尝试预处理界面文件包。我们注意下这儿使用了fill_win32_filefunc填充了zlib_filefunc_def结构体,还要注意下我们对unzOpen2传入了界面文件包的路径。我们接着看,预处理之后的流程

代码语言:javascript
复制
UNZRESPACKDATA:
	nRetCode = unzGoToFirstFile(m_pResPackData);
	while (UNZ_OK == nRetCode)
	{
		char szCurrentFile[260];
		unz_file_info fileInfo;
		uLong dwSeekPos;
		uLong dwSize;

		nRetCode = unzGetCurrentFileInfo(
			m_pResPackData, 
			&fileInfo, 
			szCurrentFile, 
			sizeof(szCurrentFile), 
			NULL, 
			0, 
			NULL, 
			0
			);
		if (nRetCode != UNZ_OK)
			goto clean0;

		dwSeekPos = unzGetOffset(m_pResPackData);
		dwSize = fileInfo.uncompressed_size;
		m_mapResOffset.insert(KResOffset::value_type(szCurrentFile, KResInfo(dwSeekPos, dwSize)));

		nRetCode = unzGoToNextFile(m_pResPackData);
	}

        这段代码,大致可以看出来,这种遍历方式和VC中遍历文件的一种方法——FindFirstFile、FindNextFile很相似。

        如此,便将压缩包中的文件信息保存到Map结构体对象m_mapResOffset中。其中信息包括文件的相对目录,文件的相对偏移和大小。         有了这组信息,我们之后读取单个文件,将变得非常方便了。         以上我们讨论了如何使用Zlib获取界面压缩包中文件信息的方法。现在我们再看下如何使用Zlib从界面内存块中获取压缩后的文件信息。

        是否还记得,之前我着重提到一点“使用了fill_win32_filefunc填充了zlib_filefunc_def结构体”。之所以着重,是因为我们现在解析界面内存块的信息时,将要自己填充zlib_filefunc_def结构体中各个回调函数。我们先看fill_win32_filefunc内部的实现

代码语言:javascript
复制
void fill_win32_filefunc (pzlib_filefunc_def)
  zlib_filefunc_def* pzlib_filefunc_def;
{
    pzlib_filefunc_def->zopen_file = win32_open_file_func;
    pzlib_filefunc_def->zread_file = win32_read_file_func;
    pzlib_filefunc_def->zwrite_file = win32_write_file_func;
    pzlib_filefunc_def->ztell_file = win32_tell_file_func;
    pzlib_filefunc_def->zseek_file = win32_seek_file_func;
    pzlib_filefunc_def->zclose_file = win32_close_file_func;
    pzlib_filefunc_def->zerror_file = win32_error_file_func;
    pzlib_filefunc_def->opaque=NULL;
}

        可以见得,它传递了“打开文件”、“读取文件”、“写入文件”、“移动读标识”和“关闭文件”等操作的函数地址。我粗略看下这些函数的实现,它们只是对CreateFile、ReadFile和WriteFile等文件操作的封装。对应的,对于不在磁盘上的文件,我们可以封装相应的操作内存的函数,然后将这些函数地址传递给该结构体对象。

代码语言:javascript
复制
zip_funcs.zopen_file = ZipOpenFunc;
zip_funcs.zread_file = ZipReadFunc;
zip_funcs.zwrite_file = ZipWriteFunc;
zip_funcs.ztell_file = ZipTellFunc;
zip_funcs.zseek_file = ZipSeekFunc;
zip_funcs.zclose_file = ZipCloseFunc;
zip_funcs.zerror_file = ZipErrorFunc;
zip_funcs.opaque=NULL;
m_pResPackData = unzOpen2((const char*)&m_memZipRes, &zip_funcs);

if (!m_pResPackData)
	goto clean0;

        我们注意下unzOpen2函数,该函数在声明时指明其是一个文件路径,而我们却将资源的内存块首地址传递进去了。那么unzOpen2可以正确处理么?我们看下ZipOpenFunc函数的实现,就知道这个问题是如何巧妙的解决掉的。

代码语言:javascript
复制
void* ZipOpenFunc(void* opaque, const char* filename, int mode) 
{
	return (void*)filename;
}

        看,它直接将filename返回了。可以想象ZipOpenFunc就是为了打开文件,并定位到首地址。既然传进来的就是内存块首地址,那么直接返回之就行了。而其他函数的实现,也是很简单的,和操作文件一样。比如

代码语言:javascript
复制
long ZipSeekFunc (void* opaque, void* stream, uLong offset, int origin)
{
	uLong ret = -1;
	CMemFile* pMemFile = (CMemFile*)stream;
	DWORD dwRetCode;

	if (!pMemFile)
		goto clean0;

	dwRetCode = pMemFile->SetFilePointer(offset, NULL, origin);
	if (INVALID_SET_FILE_POINTER == dwRetCode)
		goto clean0;

		ret = 0;

clean0:
	return ret;
}

        在调用解析界面内存块的函数前。OpenResPack还多了一个判断:判断已读取的m_memZipRes是否为空,如果为空,则再从资源文件中读取界面描述块到内存中。

代码语言:javascript
复制
if (strlen((const char*)&m_memZipRes) == 0)
{//防止.kui格式错误导致unzOpen2返回空的m_pResPackData
	hResInfo = FindResourceW(_ModulePtr->GetResourceInstance(), L"kuires.dat", L"SKIN");
	if (!hResInfo)
		goto clean0;

	hResDat = LoadResource(_ModulePtr->GetResourceInstance(), hResInfo);
	if (!hResDat)
		goto clean0; 

	pResBuffer = LockResource(hResDat);
	if (!pResBuffer)
		goto clean0;

	dwResBuffer = SizeofResource(_ModulePtr->GetResourceInstance(), hResInfo);
	m_memZipRes.SetData(pResBuffer, dwResBuffer);
}

这个代码一开始判断m_memZipRes是否为空,存在一定的漏洞:假如资源文件的第一个字符就是\0,则就会认为这段读取的数据为空了。当然,一般不存在这样的问题,因为目前压缩包文件的第一个字符肯定不是\0。但是从代码的严谨性上来说,应该给openkui\KUILib\Include\kscbase下kscmemfile.h中的CMemFile新增一个共有函数

代码语言:javascript
复制
BOOL IsEmpty()
{
    return m_buffer.GetCount() == 0 ? TRUE : FALSE;
}

        然后那个判断应该改成

代码语言:javascript
复制
If( m_memZipRes.IsEmpty()) 
{
……
}

        还有,这个if中的逻辑PrepareRes中读取资源逻辑一样。应该将其提炼出来,这样可以不会让代码看着十分冗余。我在之后附加的工程中,会将这个函数提炼到一个名字为 GetResInResfile的函数中。

        我们接着看之后对数据的读取和保存。

代码语言:javascript
复制
LoadStringRes();
LoadImageRes();
LoadXmlRes();
LoadFontRes();

        中前三个函数对应于

        KUI提供的例子中,都没有LoadFontRes对应的fonts.xml文件存在。所以我们可以先忽略字体处理这块逻辑。         我们以LoadXmlRes为例,讲解其执行过程。

代码语言:javascript
复制
bool KAppRes::LoadXmlRes()
{
	bool retval = false;
	void* pBuffer = NULL;
	unsigned long dwBuffer = 0;
	TiXmlDocument xmlDoc;
	const TiXmlElement* pXmlChild = NULL;
	const TiXmlElement* pXmlItem = NULL;

	if (!GetRawDataFromRes("xmls.xml", &pBuffer, dwBuffer))
		goto clean0;

	if (!xmlDoc.LoadBuffer((char*)pBuffer, (long)dwBuffer, TIXML_ENCODING_UTF8))
		goto clean0;

	pXmlChild = xmlDoc.FirstChildElement("xmls");
	if (!pXmlChild)
		goto clean0;

	pXmlItem = pXmlChild->FirstChildElement("xml");
	while (pXmlItem) 
	{
		std::string strId;
		std::string strPath;

		strId = pXmlItem->Attribute("id");
		strPath = pXmlItem->Attribute("path");

		if (strId.length() && strPath.length())
		{
			m_mapXmlTable[strId] = strPath;
		}

		pXmlItem = pXmlItem->NextSiblingElement("xml");
	}

    retval = true;

clean0:
	if (pBuffer)
	{
		FreeRawData(pBuffer);
	}

	return retval;

}

         第10行的GetRawDataFromRes是我们特别需要注意的一个函数。该函数传入一个文件相对路径、用于保存该文件内容的内存块首地址和该内存块的大小。

代码语言:javascript
复制
bool KAppRes::GetRawDataFromRes(
	const std::string& strId, 
	void** ppBuffer, 
	unsigned long& dwSize
	)
{
	bool retval = false;
	KResStore::iterator store;
	KResOffset::iterator offset;
	unsigned long dwOffset;
	int nRetCode;

	if (!ppBuffer)
		goto clean0;

	offset = m_mapResOffset.find(strId);
	if (offset == m_mapResOffset.end())
		goto clean0;

	dwOffset = offset->second.first;
	dwSize = offset->second.second;

	*ppBuffer = new unsigned char[dwSize+1];
	if (!*ppBuffer)
		goto clean0;

	nRetCode = unzSetOffset(m_pResPackData, dwOffset);
	if (nRetCode != UNZ_OK)
		goto clean0;

	nRetCode = unzOpenCurrentFile(m_pResPackData);
	if (nRetCode != UNZ_OK)
		goto clean0;

	nRetCode = unzReadCurrentFile(m_pResPackData, *ppBuffer, dwSize);
	if (0 == nRetCode)
		goto clean0;

	retval = true;

clean0:
	if (!retval)
	{
		if (ppBuffer)
		{
			if (*ppBuffer)
			{
				delete[] (*ppBuffer);
				*ppBuffer = NULL;
			}
		}
	}

	return retval;
}

         该函数先在保存文件信息的map中寻找传入的相对路径对应的文件信息,然后动态分配一段大小合适的空间(如果成功,则在函数外部释放,否则在函数内部释放),再使用unzSetOffset将压缩包读取位置设置到相应的偏移处,通过unzReadCurrentFile将指定文件读到内存中。是否还记得,我曾提出,这个库在设计时存在一定的缺陷:没有考虑debug情况下会经常修改界面文件的问题。我们之前在PrepareRes函数中获取了保存界面描述文件(非压缩)的路径。这样,我们可以对该函数做段修改,入参都不用改,我们只是让该函数读取指定文件的内容。

代码语言:javascript
复制
#ifdef DEBUG
    if ( ReadResFile(strId, ppBuffer, dwSize) ) {
        return true;
    }
    else {
        // _ASSERT_EXPR(FALSE, L"debug下从界面描述目录读取文件失败");
        return false;
    }
#endif

        我封装了一个读取文件的函数ReadResFile

代码语言:javascript
复制
#define NEWBUFFERSIZE 0x100

bool KAppRes::ReadResFile( 
    const std::string& strId,
    void** ppBuffer, 
    unsigned long& dwSize )
{
    std::string strFilePath = CW2A(m_strResFloderPath.c_str());
    strFilePath += strId;
    HANDLE hFile = CreateFileA(strFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
    if ( NULL == hFile ) {
        return false;
    }

    // 先分配读取的数据空间
    DWORD dwTotalSize = NEWBUFFERSIZE;                     // 总空间
    char* pchReadBuffer = new char[dwTotalSize];
    memset(pchReadBuffer, 0, NEWBUFFERSIZE);

    DWORD dwFreeSize = dwTotalSize;                 // 闲置空间

    bool bSuc = false;
    do {

        char chTmpReadBuffer[NEWBUFFERSIZE] = {0};
        DWORD dwbytesRead = 0; 

        // 用于控制读取偏移
        OVERLAPPED Overlapped;
        memset(&Overlapped, 0, sizeof(OVERLAPPED) );

        while (true) {   

            // 清空缓存
            memset(chTmpReadBuffer, 0, NEWBUFFERSIZE);

            // 读取管道
            BOOL bRead = ReadFile( hFile, chTmpReadBuffer, NEWBUFFERSIZE, &dwbytesRead, &Overlapped );
            DWORD dwLastError = GetLastError();

            if ( bRead ) {
                if ( dwFreeSize >= dwbytesRead ) {
                    // 空闲空间足够的情况下,将读取的信息拷贝到剩下的空间中
                    memcpy_s( pchReadBuffer + Overlapped.Offset, dwFreeSize, chTmpReadBuffer, dwbytesRead );
                    // 重新计算新空间的空闲空间
                    dwFreeSize -= dwbytesRead;
                }
                else {
                    // 计算要申请的空间大小
                    DWORD dwAddSize = ( 1 + dwbytesRead / NEWBUFFERSIZE ) * NEWBUFFERSIZE;
                    // 计算新空间大小
                    DWORD dwNewTotalSize = dwTotalSize + dwAddSize;
                    // 计算新空间的空闲大小
                    dwFreeSize += dwAddSize;
                    // 新分配合适大小的空间
                    char* pTempBuffer = new char[dwNewTotalSize];
                    // 清空新分配的空间
                    memset( pTempBuffer, 0, dwNewTotalSize );
                    // 将原空间数据拷贝过来
                    memcpy_s( pTempBuffer, dwNewTotalSize, pchReadBuffer, dwTotalSize );
                    // 保存新的空间大小
                    dwTotalSize = dwNewTotalSize;
                    // 将读取的信息保存到新的空间中
                    memcpy_s( pTempBuffer + Overlapped.Offset, dwFreeSize, chTmpReadBuffer, dwbytesRead );
                    // 重新计算新空间的空闲空间
                    dwFreeSize -= dwbytesRead;
                    // 将原空间释放掉
                    delete [] pchReadBuffer;
                    // 将原空间指针指向新空间地址
                    pchReadBuffer = pTempBuffer;
                }

                // 读取成功,则继续读取,设置偏移
                Overlapped.Offset += dwbytesRead;
            }
            else{
                if ( ERROR_HANDLE_EOF == dwLastError ) {
                    bSuc = TRUE;
                }
                break;
            }
        }

        if ( bSuc ) {
            *ppBuffer = pchReadBuffer;
            dwSize = dwTotalSize - dwFreeSize;
        }
        else {
            if ( NULL != pchReadBuffer ) {
                delete [] pchReadBuffer;
                pchReadBuffer = NULL;
            }
        }     
    } while (0);

    if ( NULL != hFile ) {
        CloseHandle(hFile);
        hFile = NULL;
    }

    return bSuc;
}

        这样,我们只要在res下新建一个skin文件夹,然后将我们的界面描述文件放在这个目录下即可。

        我们看一下xmls.xml文件内容

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<xmls>
	<xml id="IDR_KSC_SKIN" path="res/def_skin.xml" />
	<xml id="IDR_KSC_STYLE" path="res/def_style.xml" />
	<xml id="IDR_KSC_STRING" path="res/def_string.xml" />
	<xml id="IDR_DLG_MAIN" path="res/dlg_main.xml" />
</xmls>

        可以见到其中对应的文件是

        在使用KUI库的程序中,我们将使用到这些id。         我们看下最终的读取结果

        我们注意到res目录下三个文件这个时候并没有加载。为什么不加载,我们之后会在探索《以金山界面库(openkui)为例思考和分析界面库的设计和实现——问题》中“如何读取保存界面元素属性”问题时,对这个问题作出解释。         总体来说,KUI这套资源管理逻辑存在以下问题:         1 部分代码不严谨         2 设计缺乏对debug环境下的优化         3 读取资源代码容余,应该封装下

代码语言:javascript
复制
bool KAppRes::GetResInResfile()
{
    bool retval = false;
    HRSRC hResInfo = NULL;
    HGLOBAL hResDat = NULL;
    PVOID pResBuffer = NULL;
    DWORD dwResBuffer;

    hResInfo = FindResourceW(_ModulePtr->GetResourceInstance(), L"kuires.dat", L"SKIN");
    if (!hResInfo)
        goto clean0;

    hResDat = LoadResource(_ModulePtr->GetResourceInstance(), hResInfo);
    if (!hResDat)
        goto clean0;

    pResBuffer = LockResource(hResDat);
    if (!pResBuffer)
        goto clean0;

    dwResBuffer = SizeofResource(_ModulePtr->GetResourceInstance(), hResInfo);
    m_memZipRes.SetData(pResBuffer, dwResBuffer);

    retval = true;
clean0:
    return retval;
}

        也有其出彩的地方:         1 CMemFile类的编写         2 从内存中解压文件

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2013年03月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档