我一直在编写一个程序,理想情况下可以在后台服务器上运行,而不会关闭--因此,任何内存泄漏都不存在是很重要的。我的程序包括使用(wtsapi32.dll)检索实时会话信息,而且由于信息必须是活动的,所以函数每隔几秒钟运行一次,我发现调用WTSEnumerateSessionsEx
函数会导致相当大的内存泄漏。按照MSDN文档中的指示,对WTSFreeMemoryEx
的调用似乎没有任何影响,但我没有收到来自这两个调用的错误消息。
总之:由于返回了有效的数据,所以问题不在WTSEnumerateSessionsEx
的执行中;内存根本没有被释放,这将导致在长时间运行时出现问题。
目前,短期解决方案是在使用内存超过阈值时重新启动进程,但是这似乎不是一个令人满意的解决方案,纠正此泄漏将是最理想的。
枚举类型直接取自Microsoft文档。
附件是相关的源文件。
unit WtsAPI32;
interface
uses Windows, Classes, Dialogs, SysUtils, StrUtils;
const
WTS_CURRENT_SERVER_HANDLE = 0;
type
WTS_CONNECTSTATE_CLASS = (WTSActive, WTSConnected, WTSConnectQuery,
WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown,
WTSInit);
type
WTS_TYPE_CLASS = (WTSTypeProcessInfoLevel0, WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1);
type
WTS_SESSION_INFO_1 = record
ExecEnvId: DWord;
State: WTS_CONNECTSTATE_CLASS;
SessionId: DWord;
pSessionName: LPtStr;
pHostName: LPtStr;
pUserName: LPtStr;
pDomainName: LPtStr;
pFarmName: LPtStr;
end;
type
TSessionInfoEx = record
ExecEnvId: DWord;
State: WTS_CONNECTSTATE_CLASS;
SessionId: DWord;
pSessionName: string;
pHostName: string;
pUserName: string;
pDomainName: string;
pFarmName: string;
end;
TSessions = array of TSessionInfoEx;
function FreeMemoryEx(WTSTypeClass: WTS_TYPE_CLASS; pMemory: Pointer;
NumberOfEntries: Integer): BOOL; stdcall;
external 'wtsapi32.dll' name 'WTSFreeMemoryExW';
function FreeMemory(pMemory: Pointer): DWord; stdcall;
external 'wtsapi32.dll' name 'WTSFreeMemory';
function EnumerateSessionsEx(hServer: THandle; var pLevel: DWord;
Filter: DWord; var ppSessionInfo: Pointer; var pCount: DWord): BOOL;
stdcall; external 'wtsapi32.dll' name 'WTSEnumerateSessionsExW';
function EnumerateSessions(var Sessions: TSessions): Boolean;
implementation
function EnumerateSessions(var Sessions: TSessions): Boolean;
type
TSessionInfoExArr = array[0..2000 div SizeOf(WTS_SESSION_INFO_1)] of WTS_SESSION_INFO_1;
var
ppSessionInfo: Pointer;
pCount: DWord;
hServer: THandle;
level: DWord;
i: Integer;
ErrCode: Integer;
Return: DWord;
begin
pCount := 0;
level := 1;
hServer := WTS_CURRENT_SERVER_HANDLE;
ppSessionInfo := NIL;
if not EnumerateSessionsEx(hServer, level, 0, ppSessionInfo, pCount) then
begin
ErrCode := GetLastError;
ShowMessage('Error in EnumerateSessionsEx - Code: ' + IntToStr(ErrCode)
+ ' Message: ' + SysErrorMessage(ErrCode));
en
else
begin
SetLength(Sessions, pCount);
for i := 0 to pCount - 1 do
begin
Sessions[i].ExecEnvId := TSessionInfoExArr(ppSessionInfo^)[i].ExecEnvId;
Sessions[i].State := TSessionInfoExArr(ppSessionInfo^)[i].State;
Sessions[i].SessionId := TSessionInfoExArr(ppSessionInfo^)[i].SessionId;
Sessions[i].pSessionName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pSessionName);
Sessions[i].pHostName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pHostName);
Sessions[i].pUserName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pUserName);
Sessions[i].pDomainName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pDomainName);
Sessions[i].pFarmName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pFarmName);
end;
if not FreeBufferEx(WTSTypeSessionInfoLevel1, ppSessionInfo, pCount);
begin
ErrCode := GetLastError;
ShowMessage('Error in EnumerateSessionsEx - Code: ' + IntToStr(ErrCode)
+ ' Message: ' + SysErrorMessage(ErrCode));
end;
ppSessionInfo := nil;
end;
end;
end.
下面是一个最小的SSCCE,它演示了这个问题。当这个程序执行时,它会在短时间内耗尽可用的内存。
program SO17839270;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
const
WTS_CURRENT_SERVER_HANDLE = 0;
type
WTS_TYPE_CLASS = (WTSTypeProcessInfoLevel0, WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1);
function WTSEnumerateSessionsEx(hServer: THandle; var pLevel: DWORD;
Filter: DWORD; var ppSessionInfo: Pointer; var pCount: DWORD): BOOL; stdcall;
external 'wtsapi32.dll' name 'WTSEnumerateSessionsExW';
function WTSFreeMemoryEx(WTSTypeClass: WTS_TYPE_CLASS; pMemory: Pointer;
NumberOfEntries: Integer): BOOL; stdcall;
external 'wtsapi32.dll' name 'WTSFreeMemoryExW';
procedure EnumerateSessionsEx;
var
ppSessionInfo: Pointer;
pCount: DWORD;
level: DWORD;
begin
level := 1;
if not WTSEnumerateSessionsEx(WTS_CURRENT_SERVER_HANDLE, level, 0,
ppSessionInfo, pCount) then
RaiseLastOSError;
if not WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, ppSessionInfo, pCount) then
RaiseLastOSError;
end;
begin
while True do
EnumerateSessionsEx;
end.
发布于 2013-07-25 09:41:48
总之,我认为WTS库代码中存在一个错误,困扰着WTSEnumerateSessionsEx
和WTSFreeMemoryEx
函数。我在问题中添加的SSCCE非常清楚地说明了这一点。
因此,解决错误的选择似乎是:
WTSEnumerateSessionsEx
。这样你打的电话就少了。你仍然会有漏洞,但我怀疑你需要很长一段时间才会遇到问题。WTSEnumerateSessions
,然后调用WTSQuerySessionInformation
以获取所需的任何额外信息。从我的试验来看,WTSEnumerateSessions
似乎并没有像WTSEnumerateSessionsEx
那样受到同样的问题的困扰。发布于 2013-07-25 09:08:23
我在MSVC中创建了相同的示例:
#include <Windows.h>
#include <WtsApi32.h>
#pragma comment(lib, "wtsapi32")
int _tmain(int argc, _TCHAR* argv[])
{
DWORD Level = 1;
PWTS_SESSION_INFO_1 pSessionInfo;
DWORD Count = 0;
BOOL bRes;
while (WTSEnumerateSessionsEx(WTS_CURRENT_SERVER_HANDLE, &Level, 0, &pSessionInfo, &Count))
{
if (!WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, pSessionInfo, Count))
{
break;
}
}
return 0;
}
我在任务管理器中观察到了同样的行为,尽管任务管理器不是一个跟踪内存泄漏的工具,但这种行为显然是一个漏洞,看起来就像是一个bug。在x86和x64构建中都会发生这种情况(x64使用x64版本的WtsApi32.dll)。
发布于 2018-09-20 23:22:27
使用完数组后,通过调用WTSFreeMemoryEx函数释放它。您还应该将指针设置为NULL. (C) https://learn.microsoft.com/en-us/windows/desktop/api/wtsapi32/nf-wtsapi32-wtsenumeratesessionsexa
https://stackoverflow.com/questions/17839270
复制相似问题