在C/C++中应避免句柄泄露
起因
在获取最新.Net Runtime源码,看到"Always call CloseHandle in AsmMan::EmitManifest (#50372)",处理文件句柄泄露的代码.通过这个问题,说明即使在在C/C++开发的老司机也会在使用资源的,造成资源泄露的情况.修复文件句柄泄露
在SourceTree看到,代码只是将CloseHandle放大更大的作用域.修复文件句柄泄露.
看代码(部分代码):
m_dwMResSizeTotal = 0;
m_dwMResNum = 0;
for (i = 0; (pManRes = m_ManResLst.PEEK(i)); i++)
{
BOOL fOK = TRUE;
mdToken tkImplementation = mdFileNil;
if (!pManRes->m_fNew) continue;
pManRes->m_fNew = FALSE;
WszMultiByteToWideChar(g_uCodePage, 0, pManRes->szAlias, -1, wzUniBuf, dwUniBuf);
if (pManRes->szAsmRefName)
{
tkImplementation = GetAsmRefTokByName(pManRes->szAsmRefName);
if (RidFromToken(tkImplementation) == 0)
{
report->error("Undefined AssemblyRef '%s' in MResource '%s'\n", pManRes->szAsmRefName, pManRes->szName);
fOK = FALSE;
}
}
else if (pManRes->szFileName)
{
tkImplementation = GetFileTokByName(pManRes->szFileName);
if (RidFromToken(tkImplementation) == 0)
{
report->error("Undefined File '%s' in MResource '%s'\n", pManRes->szFileName, pManRes->szName);
fOK = FALSE;
}
}
else // embedded mgd.resource, go after the file
{
HANDLE hFile = INVALID_HANDLE_VALUE;
int j;
WCHAR wzFileName[2048];
WCHAR* pwz;
pManRes->ulOffset = m_dwMResSizeTotal;
for (j = 0; (hFile == INVALID_HANDLE_VALUE) && (pwzInputFiles[j] != NULL); j++)
{
wcscpy_s(wzFileName, 2048, pwzInputFiles[j]);
pwz = wcsrchr(wzFileName, DIRECTORY_SEPARATOR_CHAR_A);
#ifdef TARGET_WINDOWS
if (pwz == NULL) pwz = wcsrchr(wzFileName, ':');
#endif
if (pwz == NULL) pwz = &wzFileName[0];
else pwz++;
wcscpy_s(pwz, 2048 - (pwz - wzFileName), wzUniBuf);
hFile = WszCreateFile(wzFileName, GENERIC_READ, FILE_SHARE_READ,
0, OPEN_EXISTING, 0, 0); //打开文件
}
if (hFile == INVALID_HANDLE_VALUE)
{
report->error("Failed to open managed resource file '%s'\n", pManRes->szAlias);
fOK = FALSE;
}
else
{
if (m_dwMResNum >= MAX_MANIFEST_RESOURCES) //MAX_MANIFEST_RESOURCES 为1024
{
report->error("Too many resources (implementation limit: %d); skipping file '%s'\n", MAX_MANIFEST_RESOURCES, pManRes->szAlias);
fOK = FALSE;
}
else
{
m_dwMResSize[m_dwMResNum] = SafeGetFileSize(hFile, NULL);
if (m_dwMResSize[m_dwMResNum] == 0xFFFFFFFF)
{
report->error("Failed to get size of managed resource file '%s'\n", pManRes->szAlias);
fOK = FALSE;
}
else
{
m_dwMResSizeTotal += m_dwMResSize[m_dwMResNum] + sizeof(DWORD);
m_wzMResName[m_dwMResNum] = new WCHAR[wcslen(wzFileName) + 1];
wcscpy_s(m_wzMResName[m_dwMResNum], wcslen(wzFileName) + 1, wzFileName);
m_fMResNew[m_dwMResNum] = TRUE;
m_dwMResNum++;
}
//原先在这里使用CloseHandle 关闭打开的文件句柄,在读取的文件数量小于1024时,是不会出现文件句柄泄露的情况
//在打开文件数量大于的时,代码不会在else作用域执行,才会出现文件句柄泄露
//所以将CloseHandle的作用域放大,就能避免文件句柄泄露
}
CloseHandle(hFile);
}
}
if (fOK || ((Assembler*)m_pAssembler)->OnErrGo)
{
WszMultiByteToWideChar(g_uCodePage, 0, pManRes->szName, -1, wzUniBuf, dwUniBuf);
hr = m_pAsmEmitter->DefineManifestResource( // S_OK or error.
(LPCWSTR)wzUniBuf, // [IN] Name of the resource.
tkImplementation, // [IN] mdFile or mdAssemblyRef that provides the resource.
pManRes->ulOffset, // [IN] Offset to the beginning of the resource within the file.
pManRes->dwAttr, // [IN] Flags.
(mdManifestResource*)&(pManRes->tkTok)); // [OUT] Returned ManifestResource token.
if (FAILED(hr))
report->error("Failed to define manifest resource '%s': 0x%08X\n", pManRes->szName, hr);
}
}
具体代码在Runtime/src/coreclr/ilasm/asmman.cpp文件EmitManifest方法中.
注意事项
在Windows平台,调用函数返回HANDLE/HMODULE等,在不需要的时候,一定要使用CloseHandle进行关闭,避免资源泄露.在Linux平台,返回文件描述符的时候,在不使用的时候,记得使用close关闭.
在C语言,使用标准库,如fopen时,要用fclose进行关闭.
不管句柄还是文件描述符的,一定要成对使用.如open/close.
秋风
2021-05-19