تسرب الذاكرة باستخدام WMI في دلفي 7
-
20-08-2019 - |
سؤال
وأنا تعاني من تسرب الذاكرة عند استخدام WMI من دلفي 7 الاستعلام (البعيد) الكمبيوتر. حدوث تسرب للذاكرة فقط على ويندوز 2003 (ويندوز XP 64). ويندوز 2000 على ما يرام، وهكذا هو ويندوز 2008. وأنا أتساءل إذا كان أي شخص قد شهدت مشكلة مماثلة.
والحقيقة أن تسرب يحدث فقط في إصدارات معينة من Windows يعني أنه قد يكون مسألة يندوز، ولكن لقد تم البحث في الإنترنت ولم تكن قادرا على تحديد موقع الإصلاح لحل هذه المسألة. أيضا، قد يكون مسألة دلفي، لأن البرنامج مع وظائف مماثلة في C # لا يبدو أن يكون هذا التسرب. وقد قادني حقيقة الأخير إلى الاعتقاد بأنه قد يكون هناك آخر، أفضل طريقة للحصول على المعلومات التي أحتاجها في دلفي دون الحصول على تسرب الذاكرة.
ولقد تضمنت المصدر إلى برنامج صغير لكشف تسرب الذاكرة أدناه. إذا تم تنفيذ sObject.Path_
خط تحت تعليق { Leak! }
، يحدث تسرب للذاكرة. إذا أعلق بها، وليس هناك تسرب. (من الواضح، في برنامج "الحقيقي"، أفعل شيئا مفيدا مع نتيجة استدعاء الأسلوب 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
وأعتقد سؤالي هو: من أي شخص آخر واجهت هذه المشكلة؟ إذا كان الأمر كذلك، هل هو بالفعل قضية ويندوز، وهناك الإصلاح؟ أو (على الأرجح) هو قانون بلدي دلفي كسر، وليس هناك طريقة أفضل للحصول على المعلومات التي احتاجها؟
وستلاحظ في عدة مناسبات، يتم تعيين nil
إلى الأشياء، وتتعارض مع روح دلفي ... هذه هي كائنات COM التي لا ترث من TObject
، وليس لديهم المدمر يمكن أن أدعو. عن طريق تعيين nil
لهم، جامع القمامة ويندوز وينظف لهم.
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.
المحلول
ويمكنني استخراج السلوك، رمز تسرب الذاكرة على نظام التشغيل Windows XP 64 و لا على نظام التشغيل Windows XP. ومن المثير للاهتمام يحدث هذا فقط إذا تم قراءة خاصية Path_
والقراءة Properties_
أو Security_
مع نفس رمز لا تسرب أي ذاكرة. مشكلة ويندوز الإصدار محددة في WMI يشبه السبب الأكثر احتمالا من هذا. نظام بلدي هو ما يصل إلى التاريخ AFAIK، لذلك هناك ربما ليس الإصلاح لهذا أيضا.
وأود أن أعلق على هاتفك إعادة جميع المتغيرات البديل واجهة، وبالرغم من ذلك. تكتب
<اقتباس فقرة>وستلاحظ في عدة مناسبات، يتم تعيين شيء إلى الأشياء، وتتعارض مع روح دلفي ... هذه هي كائنات COM التي لا ترث من TObject، وليس لديهم المدمر يمكن أن أدعو. عن طريق تعيين شيء لهم، جامع القمامة ويندوز وينظف لهم.
اقتباس فقرة> وهذا غير صحيح، وبالتالي ليست هناك حاجة لضبط المتغيرات لnil
وUnassigned
. ويندوز لايوجد أحد هواة جمع القمامة، ما كنت تتعامل مع كائنات-عد المرجعية، التي يتم تدميرها فورا بمجرد عدد مرجع يصل 0. دلفي مترجم لا تضاف المكالمات اللازمة لزيادة وإنقاص عد المرجع عند الضرورة. المهام الخاصة بك إلى nil
وUnassigned
تنقيص عدد مرجع، وتحرير الكائن عندما يصل إلى 0.
وهناك مهمة جديدة إلى متغير، أو مغادر الإجراء رعاية هذا كذلك، وتحديد المهام إضافية حتى الآن (وإن لم يكن خطأ) لزوم لها وتقلل من وضوح التعليمات البرمجية. التعليمة البرمجية التالية يعادل تماما ولا تسرب أي ذاكرة إضافية:
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_;
end;
end;
وانا اقول ينبغي لأحد أن إعادة اجهات صراحة إلا إذا كان هذا لا فعلا تحرير كائن (حتى العد المرجع الحالي يجب أن يكون 1) وتدمير نفسه يجب أن يحدث حقا بالضبط في هذه المرحلة. أمثلة لهذه الأخيرة هي أن جزءا كبيرا من الذاكرة يمكن تحرير، أو أن ملف يحتاج إلى أن تكون مغلقة أو كائن مزامنة أن يطلق سراحه.
نصائح أخرى
ويجب تخزين قيمة الإرجاع
sObject.Path_;
وفي متغير وجعلها SWbemObjectPath. وهذا ضروري لجعل الحق حساب مرجع.