当前位置: 首页 > 编程日记 > 正文

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

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

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


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


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


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

KAppRes& KAppRes::Instance()
{static KAppRes _singleton;return _singleton;
}
可以看出,这是个单例类。因为界面描述数据只需要读取和解析一次,所以这儿设计成单例类。以后使用它的地方,就不用重复读取和解析了。
我们再看下作为私有函数的构造函数,它显示该类执行的脉络
KAppRes::KAppRes() : m_hTempRes(INVALID_HANDLE_VALUE)
{PrepareRes();OpenResPack();LoadStringRes();LoadImageRes();LoadXmlRes();LoadFontRes();
}
粗略看了函数名。可以得出如下流程

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

我们先看

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的名字。这样就实现了换肤。即使这套外置皮肤坏了,或者被删了,我们还可以使用资源中的那套皮肤。

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

    if (GetFileAttributesW(pathRes) != INVALID_FILE_ATTRIBUTES){m_strResPackPath = pathRes.value();}
这步,可以用来判断一个文件是否存在么?其实不可以。因为如果我新建一个与压缩包同名的“文件夹”,GetFileAttributesW将返回FILE_ATTRIBUTE_DIRECTORY,这将导致这个错误的逻辑认为该文件夹是一个压缩文件,从而导致之后的逻辑出现处理异常。该函数应该写成
    if ( PathFileExists(pathRes) && 0 == ( GetFileAttributesW(pathRes) & FILE_ATTRIBUTE_DIRECTORY ) ){m_strResPackPath = pathRes.value();}
其中 还有个设计缺陷。假如我们是使用这个库的开发者,我们在调试过程中,难免会修改界面描述文件。那么难道我们每修改一次,都要将描述文件压缩成一个包么?这样不是很难调用?我觉得,可以在PrepareRes函数中, 新增一段对debug情况的处理:在debug情况下我们应该获取工程res目录下一个特定的文件夹,该文件夹保存了未压缩的各个文件。这样我们就可以不用每次修改资源后都要打个资源包了。
我们在KAppRes类私有成员中增加

#ifdef DEBUG// 保存debug环境下界面描述文件文件夹目录std::wstring m_strResFloderPath;
#endif
在PrepareRes的pathRes.RemoveExtension();之前新增
#ifdef DEBUGpathRes.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代码去获取压缩包(内存)中文件的信息。
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传入了界面文件包的路径。我们接着看,预处理之后的流程
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内部的实现

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等文件操作的封装。对应的,对于不在磁盘上的文件,我们可以封装相应的操作内存的函数,然后将这些函数地址传递给该结构体对象。
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函数的实现,就知道这个问题是如何巧妙的解决掉的。
void* ZipOpenFunc(void* opaque, const char* filename, int mode) 
{return (void*)filename;
}
看,它直接将filename返回了。可以想象ZipOpenFunc就是为了打开文件,并定位到首地址。既然传进来的就是内存块首地址,那么直接返回之就行了。而其他函数的实现,也是很简单的,和操作文件一样。比如
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是否为空,如果为空,则再从资源文件中读取界面描述块到内存中。
if (strlen((const char*)&m_memZipRes) == 0)
{//防止.kui格式错误导致unzOpen2返回空的m_pResPackDatahResInfo = 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新增一个共有函数
BOOL IsEmpty()
{return m_buffer.GetCount() == 0 ? TRUE : FALSE;
}
然后那个判断应该改成
If( m_memZipRes.IsEmpty()) 
{
……
}
还有,这个if中的逻辑PrepareRes中读取资源逻辑一样。应该将其提炼出来,这样可以不会让代码看着十分冗余。我在之后附加的工程中,会将这个函数提炼到一个名字为 GetResInResfile的函数中。

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

LoadStringRes();
LoadImageRes();
LoadXmlRes();
LoadFontRes();
中前三个函数对应于

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

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是我们特别需要注意的一个函数。该函数传入一个文件相对路径、用于保存该文件内容的内存块首地址和该内存块的大小。
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函数中获取了保存界面描述文件(非压缩)的路径。这样,我们可以对该函数做段修改,入参都不用改,我们只是让该函数读取指定文件的内容。
#ifdef DEBUGif ( ReadResFile(strId, ppBuffer, dwSize) ) {return true;}else {// _ASSERT_EXPR(FALSE, L"debug下从界面描述目录读取文件失败");return false;}
#endif
我封装了一个读取文件的函数ReadResFile
#define NEWBUFFERSIZE 0x100bool 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文件内容

<?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 读取资源代码容余,应该封装下

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 从内存中解压文件

相关文章:

开发者如何赶上5G风口?

随着5G正式步入商用&#xff0c;5G 技术引发广泛关注。据信息通信研究院《5G经济社会影响白皮书》预测&#xff0c;2030年&#xff0c;5G将直接带动的总产出、经济增加值、就业机会分别为6.3万亿元、2.9万亿元和800万个。据BOSS直聘 《2019年5G相关人才数据观察》报告指出&…

使用C# 3.0编译器编译 Asp.Net 项目代码

只需要在 web.config 里添加这样的一段设置就OK了:<configuration><system.codedom><compilers><compiler language"c#;cs;csharp"extension".cs"type"Microsoft.CSharp.CSharpCodeProvider,System, Version2.0.0.0, Cultureneu…

java 它 引用(基本类型的包装,构造函数和析构函数c++不同)

一个&#xff1a;java 和c参考控制 他提到引用&#xff0c;我们会想到java它不喜欢c里面的指针。当然java内引用和c里面的引用是不同的。 比如&#xff1a; 比方C中&#xff0c;我对某一个函数的声明。int a(int &b)&#xff0c;b即为引用类型&#xff0c;函数内b的改动能够…

使用程序解决一道逻辑推理题

今天看朋友发了一个老问题&#xff0c;一道很有意思的推理题&#xff1a;&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 小明和小强都是张老师的学生&#xff0c;张老师的生日是M月N日&#xff0c;2人都知道张老师的生日是下列10组中的一天&#xff1a; 3月4…

AjaxControlToolKit之DragPanelExtender用法

1、将控件ToolkitScriptManager拖至页面中...2、定义3个Panel&#xff0c;用于实现窗体拖动效果&#xff0c;代码如下&#xff1a;1<body>2<form id"form1"runat"server">3<div>4<cc1:ToolkitScriptManager ID"ToolkitScriptMan…

自带数据线的迷你数显充电宝,旅途必备

还有20多天就过年了有件极其考验情商的事情也来临了就是我们这群90后过年最怕的事情——相亲但是在尴尬的场合手机可是一个缓解气氛的好东西不管是想要选择看电影&#xff0c;还是找附近的游玩只要有手机&#xff0c;就可以从容不迫的应对但是带手机最尴尬的事情莫过于结账的时…

SpringJDBC的简单应用

此处写上应用JdbcTemplate的dao操作数据库的一些代码&#xff08;含基本的增删改查&#xff0c;注&#xff1a;重点是查询出多条语句的写法&#xff09;&#xff1a; package org.sakaiproject.zhaorui.dao.impl;import java.sql.ResultSet;import java.sql.SQLException;impor…

WMI技术介绍和应用——查询硬件信息

这个月实在太忙了&#xff0c;一直没有时间去继续写WMI的应用例子。 本来是希望将《WMI技术介绍和应用》系列博文写的像WMI百科全书般&#xff0c;但是貌似对这个技术感兴趣的同学并不多&#xff0c;所以我决定对部分知识点点到为止&#xff0c;有需求的同学可以查询MSDN相关类…

微软开源的自动机器学习工具上新了:NNI概览及新功能详解

作者 | 宋驰来源 | 微软研究院AI头条&#xff08;ID: MSRAsia&#xff09;2018年9月&#xff0c;微软亚洲研究院发布了第一版 NNI (Neural Network Intelligence) &#xff0c;目前已在 GitHub 上获得 3.8K 星&#xff0c;成为最热门的自动机器学习&#xff08;AutoML&#xff…

10624 - Super Number

题目链接 题意&#xff1a;给出n到m的范围&#xff0c;求出一个数在前i位数组成的数字能被i整除。假设存在输出这个数&#xff0c;假设不存在。输出-1. 思路&#xff1a;回溯&#xff0c;每次放第i位&#xff0c;然后推断是否符合题意。这题踩着时间过去的2.6s&#xff08;看了…

2008找回企业久违的网速

曾几何时&#xff0c;单位上网访访问页面也是忽忽的&#xff0c;等待10秒简直是不可忍受&#xff1b;曾几何时&#xff0c;公司网络下载是嗖嗖的&#xff0c;转眼已是2M开外&#xff1b;曾几何时&#xff0c;办公室上网看视频是杠杠的&#xff0c;那流畅那画面都快赶上电视直播…

发现一个windows7(32bit或64bit)DirectUI的bug

前段时间发现一个windows7的一个bug&#xff0c;不是什么严重的问题&#xff0c;我在此记录下。&#xff08;转载请指明出于breaksoftware的csdn博客&#xff09; 重现步骤如下: 0 在文件夹的“更改您的视图”中选择下图中用红色叉叉标记的项 1 新建一个文件夹名为“Cs" 2…

阿里达摩院2020趋势第一弹:感知智能的“天花板”和认知智能的“野望”

作者 | Just出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;“感知智能与认知智能是相辅相成的关系。认知智能需要感知系统来进行信号处理和概念识别&#xff0c;而感知系统也需要认知系统的反馈来决定如何进行更有效的提取和识别。”1月2日&#xff0c;阿里巴巴达摩…

Java 对synchronized的补充Lock锁

Java并发编程&#xff1a;Lock 从Java 5之后&#xff0c;在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问&#xff0c;那就是Lock。 也许有朋友会问&#xff0c;既然都可以通过synchronized来实现同步访问了&#xff0c;那么为什么还需要提供Lock&#xff1…

有奖评选 | 2020年的AI技术公开课,你想听到哪些干货?

CSDN技术公开课有奖评选开始啦~~听过课的小伙伴们&#xff0c;哪位讲师的分享让你获益匪浅&#xff1f;记得给TA投票哦&#xff01;投票后获取入群方式&#xff0c;参与抽奖&#xff0c;奖品很丰厚哦~~进入付费时代&#xff0c;如今我们看似只要招招手&#xff0c;一切知识随手…

一个分析“文件夹”选择框实现方法的过程

在软件开发中&#xff0c;我们如果存在“导入导出”的场景时&#xff0c;难免会用到“文件夹”选择框。之前一直没有太关注过这个的实现过程。最近在工作中遇到了一些问题&#xff0c;我做了一些研究。在此记录下研究的过程。&#xff08;转载请指明出于breaksoftware的csdn博客…

Openssl req命令

一、简介 req指令用来创建和处理PKCS#10格式的证书 二、语法 openssl req [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-out filename] [-text] [-pubkey] [-noout] [-verify] [-modulus] [-nodes] [-subject] [-passin arg] [-passout arg] [-key filename] [-key…

使用windbg抓取崩溃文件和分析的过程

在软件编程中&#xff0c;崩溃的场景比较常见的。且说微软技术再牛X&#xff0c;也是会出现崩溃的场景。网上有一段Win98当着比尔盖茨蓝屏的视频非常有意思。 &#xff08;转载请指明出于breaksoftware的csdn博客&#xff09;我们身边的很多软件都引入了dump生成和收集机制。但…

TF 2.1.0-rc2发布,2020年停止支持Python 2

作者 | 神经星星来源 | HyperAI超神经&#xff08;ID:HyperAI&#xff09;【导读】2020 年 1 月 1 日&#xff0c;Python 2 停止维护&#xff0c;正式退休。Python 3 全面登场的时刻&#xff0c;TensorFlow 也在悄悄改变。近日 TensorFlow 官方 GitHub 账号中&#xff0c;发布了…

重新认识笔记本锂电池的保养

重新认识笔记本锂电池的保养 对于笔记本电脑来说&#xff0c;电池可以说是一个比较重要的部件&#xff0c;它的效能直接关系到笔记本电脑在缺少电源的环境中的工作能力。而电池在笔记本电脑的众组件中又算是一个不折不扣的消耗品&#xff0c;因此涉及到笔记本电脑电池的保养和合…

nginx转发及后端服务器获取真实client的IP

针对nginx的模块介绍可以查阅wiki:http://wiki.nginx.org/Modules常用模块&#xff1a;HTTP CoreProxyRewriteUpstream 原理&#xff1a;squid&#xff0c;varnish以及nginx等&#xff0c;在做反向代理的时候&#xff0c;因为要代替客户端去访问服务器&#xff0c;所以&#xf…

AJAX的组成应用

表示层XHTMLCSS 动态显示和数据 DOM (文档对象模型)数据交互和操作 XML,XSLT 异步数据获取 XMLHttpRequest 绑定和处理数据 JavaScript XMLhttpRequest对象属性:Number readyState 4 表示完成Function onreadystatechange 回调函数string responseText XMLDocument responseXM…

打开,保存文件框的文本溢出排查

工作中遇到的这个问题还是很有意思的。其中嵌套了很多奇葩性的问题。 &#xff08;转载请指明出于breaksoftware的csdn博客&#xff09;我们来看下故事的发生过程&#xff0c;QA同学发现我们存在如下的bug 看到如此多的串&#xff0c;可以认为这个是典型的溢出问题。后来我咨询…

2020年,为什么说入坑AI是最好的时机?

2019年可以说是AI全面落地和商用的一年&#xff0c;产业智能化成为各个行业重点关注的发展方向&#xff0c;交通、工业、农业、医疗等主流行业无一例外。随着人工智能技术的进一步发展和落地&#xff0c;深度学习、数据挖掘、自动程序设计等领域也将在更多应用场景中得到实现。…

IIS 伪静态配置(安装ISAPI_Rewrite配置)

第一&#xff1a;首先到官方网站下载ISAPI_Rewrite 我的机子是32位的就下32位免费版的&#xff0c;链接地址如下&#xff1a; http://www.helicontech.com/download/isapi_rewrite/ISAPI_Rewrite3_0064_Lite.msi 可以选择不同版本&#xff1a;http://www.helicontech.com/downl…

Github标星24k,127篇经典论文下载,这份深度学习论文阅读路线图不容错过

作者 | Floodsung翻译 | 黄海广来源 | 机器学习初学者(ID&#xff1a;ai-start-com&#xff09;【导读】如果你是深度学习领域的新手&#xff0c;那么你可能会遇到的第一个问题是“我应该从哪篇论文开始阅读&#xff1f;”本文就是一篇深度学习论文的阅读路线图&#xff01;该…

c/c++面试

1. static在c&#xff0c;c中有什么不同点2. 堆和栈的区别3. 纯虚函数4. 指针和引用的区别5. 如果构造函数出错&#xff0c;如何处理&#xff1f;6. 对设计模式是否熟悉&#xff0c;用过哪些&#xff1f;7. c如何使用c中的函数&#xff0c;为什么&#xff1f;整理&#xff1a;1…

一种解决启动进程传递参数过长的方法

工作中&#xff0c;QA同学在测试我们程序的时候&#xff0c;发现在XP下&#xff0c;我们的A进程无法启动我们的B进程。而在Win7 64bit系统下功能正常。RD同学调试后&#xff0c;发现我们A进程中使用ShellExcute去启动了B进程&#xff08;转载请指明出于breaksoftware的csdn博客…

Ubuntu“无法获得锁\加锁”解决方案

2019独角兽企业重金招聘Python工程师标准>>> 当你添加了源&#xff0c;更新源的时候&#xff0c;如果中途中断了更新&#xff0c;安装软件或者再次更新的时候就是出现如下提示&#xff0c; E: 无法获得锁 /var/lib/apt/lists/lock – open (11: 资源暂时不可用) E: …

一步一步学Silverlight 2系列(3):界面布局

概述 Silverlight 2 Beta 1版本发布了&#xff0c;无论从Runtime还是Tools都给我们带来了很多的惊喜&#xff0c;如支持框架语言Visual Basic, Visual C#, IronRuby, Ironpython&#xff0c;对JSON、Web Service、WCF以及Sockets的支持等一系列新的特性。《一步一步学Silverlig…