Я кажется либо наткнулся на огромный косяк в Делфах, либо я чего-то крупно не догоняю.
Предыстория:
Писался список - обертка для TList, который хранил бы не указатели, а варианты. Все было нормально, пока я не решил добавит возможность хранения объектов. Для того, чтобы корректно удалить объект из памяти после удаления элемента списка, надо определить, действительно ли там объект (вариант все-таки), и если он там есть, то удалить его. Для определения того, что в варианте находится указатель на объект, запись туда объекта производится так:
type varTmp: Variant;
objTmp: TObject;
TVarData(varTmp).VType:=vtObject;
TVarData(varTmp).VPointer:=Tmp;
При этом всегда можно проверить что хранится в варианте, через проверку TVarData(varTmp).VType, и извлечь оттуда сам объект через TVarData(varTmp).VPointer. Но тут всплыла очень странная проблема, которую проще всего проиллюстрировать кодом:
program class_variant;
{$APPTYPE CONSOLE}
var
varTmp: Variant;
objTmp: TObject;
begin
// создаем
objTmp:=TObject.Create;
TVarData(varTmp).VType:=vtObject;
TVarData(varTmp).VPointer:=objTmp;
// получаем
writeln(TObject(TVarData(varTmp).VPointer).ClassName);
// удаляем
TObject(TVarData(varTmp).VPointer).Free;
// объект по прежнему существует ?
writeln(objTmp.ClassName); // да! <== Здесь по идее AV
writeln(TObject(TVarData(varTmp).VPointer).ClassName); // да!!?
readln;
end.
Результат:
TObject
TObject
TObject
Все три вывода сработали! И никаких AV.
Упс! А что тогда я удалил?... Получается объект в варианте живет и после самого удаления, либо мы удалем совсем не то. Но если этот код еще можно осмыслить что-ли, то вот это:
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
03-01-2007 16:29 | Сообщение от автора вопроса
Кстати, то как я в итоговом коде запихиваю TObject в Variant делать нельзя, хотя бы потому , что написано в самом System.pas по этому поводу:
{ if adding new items, update Variants' varLast, BaseTypeMap and OpTypeMap }
А я этого не сделал.
Так что я изначально был не прав. Простите меня пожайлуста. Всем еще раз большое спасибо!
10-12-2006 03:49 | Комментарий к предыдущим ответам
>>> полностью освободить тот кусок памяти
Попробуй свернуть-развернуть программу и только после этого вызывать SayHello - вот теперь вероятность AV значительное повысится. То же самое происходит при вызове SetProcessWorkingSetSize (см. соответствующие обсуждения). То есть менеджер памяти будет удерживать кусок, пока операционка вежливо не попросит его вернуть все, что взял на место или пока программа не завершится. К нестандартным менеджерам памяти это может не относиться.
Так, походу я наворотил...
...должен заметить, что вы неправильно работаете с вариантами: константа vtObject не имеет к ним ни малейшего отношения, она связана с работой TVarRec и конструкции "array of const"
Да, я ошибся. Писал по аналогии - VarType - vt... А оказалось значения начинаются с префикса var. Буду знать теперь.
Насчет создания своего вариантного типа, думал над этим, но мне-то и надо всего хранить в варианте указатель на объект, и при удалении варианта вызвать деструктор объекта. И при этом точно знать, что в варианте хранится указатель, а не число. Для этого я вручную правил значение VType, но как оказалось неправильно.
VariantClear?
Насколько я понял, эта функция очистит только значение варианта, и никаких деструкторов при этом вызываться не будет. А я как раз хотел удалить не вариант, а то, куда ссылается указатель, хранящийся в нем.Уже после того, как я отравил вопрос, я нашел еще одну проблему в своем коде: я неправильно проверял "надежность" удаления объекта. Я это делал вызовом его метода, а как я обнаружил, если метод объекта не использует его поля либо является статическим, то он работает и до создания, и после удаления объекта. А вот если попытаться получить доступ к полю, то и получим желанную ошибку.
Вот работающий код:
Calling VarClear is equivalent to assigning the Unassigned constant to the Variant. V can be either a Variant or an OleVariant, but it must be possible to assign a value to it (it must be an lvalue).
After calling VarClear, the VarIsEmpty function returns true, and the VarType function returns varEmpty. Using an unassigned variant in an expression causes an exception to be thrown. Likewise, if you attempt to convert an unassigned Variant to another type (using VarAsType ), an exception is thrown.
Note: Do not confuse clearing a Variant, which leaves it unassigned, with assigning a Null value. A Null Variant is still assigned, but has the value Null. Unlike unassigned Variants, Null Variants can be used in expressions and can be converted to other types of Variants.
Объект это просто участок памяти, соответственно при удалении объекта эта память помечается как удалённая и более ничего. А AV вы получите только если менеджер памяти решит полностью освободить тот кусок памяти в котором помещался ваш объект, но вероятность этого довольно мала.
Потом должен заметить, что вы неправильно работаете с вариантами: константа vtObject не имеет к ним ни малейшего отношения, она связана с работой TVarRec и конструкции "array of const". В дельфи есть возможность создавать свои типы вариантов, но делается это намного сложнее вашего способа (смотрите Variants.pas), а так вы не получите ничего кроме ошибки, потому что ваша константа vtObject совпадает с varDate, то есть в ваших вариантах должна содержаться дата, а не объект.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.