Delphi 7 中使用 WMI 的内存泄漏
我在使用 Delphi 7 中的 WMI 查询(远程)电脑时遇到内存泄漏。 内存泄漏仅发生在 Windows 2003(和 Windows XP 64)上。 Windows 2000 可以,Windows 2008 也可以。不知道有没有人遇到过类似的问题。
事实上,泄漏仅发生在某些版本的 Windows 中,这意味着这可能是 Windows 问题,但我一直在网上搜索,但无法找到解决该问题的修补程序。 另外,这可能是 Delphi 问题,因为在 C# 中具有类似功能的程序似乎没有这种泄漏。 后一个事实使我相信可能有另一种更好的方法来获取 Delphi 中我需要的信息而不会发生内存泄漏。
我已经包含了一个小程序的源代码以暴露下面的内存泄漏。 如果 { 下方的
注释执行后,发生内存泄漏。 如果我把它注释掉,就不会有泄漏。 (显然,在“真实”程序中,我对 sObject.Path_ 方法调用的结果做了一些有用的事情:)。)sObject.Path_
行泄漏! }
通过在我的计算机上进行一些快速而肮脏的 Windows 任务管理器分析,我发现以下内容:
Before N=100 N=500 N=1000 With sObject.Path_ 3.7M 7.9M 18.2M 31.2M Without sObject.Path_ 3.7M 5.3M 5.4M 5.3M
我想我的问题是:还有其他人遇到过这个问题吗? 如果是这样,这确实是 Windows 问题吗?是否有修补程序? 或者(更有可能)我的 Delphi 代码是否已损坏,是否有更好的方法来获取我需要的信息?
您会注意到,在很多情况下,nil
被分配给对象,这与 Delphi 的精神相反...这些是 COM 对象,它们不继承自 TObject
,并且没有我可以调用析构函数。 通过为它们分配nil
,Windows 的垃圾收集器会清理它们。
program ConsoleMemoryLeak;
{$APPTYPE CONSOLE}
uses
Variants, ActiveX, WbemScripting_TLB;
const
N = 100;
WMIQuery = 'SELECT * FROM Win32_Process';
Host = 'localhost';
{ Must be empty when scanning localhost }
Username = '';
Password = '';
procedure ProcessObjectSet(WMIObjectSet: ISWbemObjectSet);
var
Enum: IEnumVariant;
tempObj: OleVariant;
Value: Cardinal;
sObject: ISWbemObject;
begin
Enum := (wmiObjectSet._NewEnum) as IEnumVariant;
while (Enum.Next(1, tempObj, Value) = S_OK) do
begin
sObject := IUnknown(tempObj) as SWBemObject;
{ Leak! }
sObject.Path_;
sObject := nil;
tempObj := Unassigned;
end;
Enum := nil;
end;
function ExecuteQuery: ISWbemObjectSet;
var
Locator: ISWbemLocator;
Services: ISWbemServices;
begin
Locator := CoSWbemLocator.Create;
Services := Locator.ConnectServer(Host, 'root\CIMV2',
Username, Password, '', '', 0, nil);
Result := Services.ExecQuery(WMIQuery, 'WQL',
wbemFlagReturnImmediately and wbemFlagForwardOnly, nil);
Services := nil;
Locator := nil;
end;
procedure DoQuery;
var
ObjectSet: ISWbemObjectSet;
begin
CoInitialize(nil);
ObjectSet := ExecuteQuery;
ProcessObjectSet(ObjectSet);
ObjectSet := nil;
CoUninitialize;
end;
var
i: Integer;
begin
WriteLn('Press Enter to start');
ReadLn;
for i := 1 to N do
DoQuery;
WriteLn('Press Enter to end');
ReadLn;
end.
I'm experiencing a memory leak when using WMI from Delphi 7 to query a (remote) pc. The memory leak only occurs on Windows 2003 (and Windows XP 64). Windows 2000 is fine, and so is Windows 2008. I'm wondering if anyone has experienced a similar problem.
The fact that the leak only occurs in certain versions of Windows implies that it might be a Windows issue, but I've been searching the web and haven't been able to locate a hotfix to resolve the issue. Also, it might be a Delphi issue, since a program with similar functionality in C# doesn't seem to have this leak. The latter fact has led me to believe that there might be another, better, way to get the information I need in Delphi without getting a memory leak.
I've included the source to a small program to expose the memory leak below. If the line sObject.Path_
below the { Leak! }
comment is executed, the memory leak occurs. If I comment it out, there's no leak. (Obviously, in the "real" program, I do something useful with the result of the sObject.Path_
method call :).)
With a little quick 'n dirty Windows Task Manager profiling on my machine, I found the following:
Before N=100 N=500 N=1000 With sObject.Path_ 3.7M 7.9M 18.2M 31.2M Without sObject.Path_ 3.7M 5.3M 5.4M 5.3M
I guess my question is: has anyone else encountered this problem? If so, is it indeed a Windows issue, and is there a hotfix? Or (more likely) is my Delphi code broken, and is there a better way to get the information I need?
You'll notice on several occasions, nil
is assigned to objects, contrary to the Delphi spirit... These are COM objects that do not inherit from TObject
, and have no destructor I can call. By assigning nil
to them, Windows's garbage collector cleans them up.
program ConsoleMemoryLeak;
{$APPTYPE CONSOLE}
uses
Variants, ActiveX, WbemScripting_TLB;
const
N = 100;
WMIQuery = 'SELECT * FROM Win32_Process';
Host = 'localhost';
{ Must be empty when scanning localhost }
Username = '';
Password = '';
procedure ProcessObjectSet(WMIObjectSet: ISWbemObjectSet);
var
Enum: IEnumVariant;
tempObj: OleVariant;
Value: Cardinal;
sObject: ISWbemObject;
begin
Enum := (wmiObjectSet._NewEnum) as IEnumVariant;
while (Enum.Next(1, tempObj, Value) = S_OK) do
begin
sObject := IUnknown(tempObj) as SWBemObject;
{ Leak! }
sObject.Path_;
sObject := nil;
tempObj := Unassigned;
end;
Enum := nil;
end;
function ExecuteQuery: ISWbemObjectSet;
var
Locator: ISWbemLocator;
Services: ISWbemServices;
begin
Locator := CoSWbemLocator.Create;
Services := Locator.ConnectServer(Host, 'root\CIMV2',
Username, Password, '', '', 0, nil);
Result := Services.ExecQuery(WMIQuery, 'WQL',
wbemFlagReturnImmediately and wbemFlagForwardOnly, nil);
Services := nil;
Locator := nil;
end;
procedure DoQuery;
var
ObjectSet: ISWbemObjectSet;
begin
CoInitialize(nil);
ObjectSet := ExecuteQuery;
ProcessObjectSet(ObjectSet);
ObjectSet := nil;
CoUninitialize;
end;
var
i: Integer;
begin
WriteLn('Press Enter to start');
ReadLn;
for i := 1 to N do
DoQuery;
WriteLn('Press Enter to end');
ReadLn;
end.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我可以重现该行为,该代码在 Windows XP 64 上会泄漏内存,但在 Windows XP 上不会泄漏内存。 有趣的是,只有读取
Path_
属性时才会发生这种情况,使用相同的代码读取Properties_
或Security_
不会泄漏任何内存。 WMI 中特定于 Windows 版本的问题看起来是最可能的原因。 据我所知,我的系统是最新的,因此可能也没有针对此问题的修补程序。不过,我想对您重置所有变体和接口变量发表评论。 你写
这是不正确的,因此不需要将变量设置为
nil
和Unassigned
。 Windows 没有垃圾收集器,您处理的是引用计数对象,一旦引用计数达到 0,这些对象就会立即销毁。Delphi 编译器会插入必要的调用来根据需要递增和递减引用计数。 对nil
和Unassigned
的赋值会减少引用计数,并在达到 0 时释放该对象。对变量的新赋值或过程的退出会负责这也是如此,因此额外的分配是多余的(尽管不是错误的)并且会降低代码的清晰度。 下面的代码是完全等价的,不会泄漏任何额外的内存:
我想说,只有当这确实释放了对象时才应该显式地重置接口(因此当前的引用计数必须为1)并且销毁本身应该确实发生在此刻。 后者的示例是可以释放大块内存,或者需要关闭文件或释放同步对象。
I can reproduce the behaviour, the code leaks memory on Windows XP 64 and does not on Windows XP. Interestingly this occurs only if the
Path_
property is read, readingProperties_
orSecurity_
with the same code does not leak any memory. A Windows-version-specific problem in WMI looks like the most probable cause of this. My system is up-to-date AFAIK, so there probably isn't a hotfix for this either.I'd like to comment on your resetting all variant and interface variables, though. You write
This is not true, and consequently there is no need to set the variables to
nil
andUnassigned
. Windows does not have a garbage collector, what you are dealing with are reference-counted objects, which are immediately destroyed once the reference count reaches 0. The Delphi compiler does insert the necessary calls to increment and decrement the reference count as necessary. Your assignments tonil
andUnassigned
decrement the reference count, and free the object when it reaches 0.A new assignment to a variable, or the exiting of the procedure take care of this as well, so additional assignments are (albeit not wrong) superfluous and decrease the clarity of the code. The following code is completely equivalent and does not leak any additional memory:
I'd say one should explicitly reset interfaces only if this does actually free the object (so the current ref count has to be 1) and the destruction itself should really happen exactly at this point. Examples for the latter are that a large chunk of memory can be freed, or that a file needs to be closed or a synchronization object to be released.
您应该将 的返回值存储
在变量中并将其设为 SWbemObjectPath。 这对于正确进行引用计数是必要的。
you should store the return value of
in a variable and make it SWbemObjectPath. This is necessary to make the reference counting right.