主要源码如下
type
TDIcon = packed record // packed record
Pserial: Integer;
PID: Integer;
PIDSort: Integer;
PFUllName: ShortString;
PNickName: ShortString;
PAttr: boolean;
PPara: ShortString;
PIcon: TMemoryStream;
end;
PDA = ^TDIcon;procedure Tform1.LoadListViewIcon; //ListView 刷新图标
var
i: integer;
LI: TListItem;
p:PDA;
begin
listview1.items.beginupdate;
for i := listview1.items.Count - 1 downto 0 do //释放Point
begin
if listview1.items.item[i].Data <> nil then Dispose(PDA(listview1.items.item[i].Data)); //这句关键
//ListView1.Items.Item[i].Data := nil;
end;
ListView1.Items.Clear; //ListView1.Items.Clear; 触发 OnDeletion 事件
if Icon_Kind then //大图标
begin //ListView1.IconOptions.Arrangement := iaTop;
TImage.Width := 32;
TImage.Height := 32;
ListView1.ViewStyle := vsIcon;
end
else
begin //ListView1.IconOptions.Arrangement := iaLeft;
TImage.Width := 16;
TImage.Height := 16;
ListView1.ViewStyle := vsSmallIcon;
end;
for i := 0 to length(P_Icon) - 1 do //P_Icon 就是 TDIcon 的数组
begin
if P_Icon[i].PID = Item1 then
begin
new(P);
P.PFullName := P_Icon[i].PFUllName;
P.PSerial := P_Icon[i].Pserial;
P.Pid := P_Icon[i].PID;
P.PIDSort := P_Icon[i].PIDSort;
P.PAttr := P_Icon[i].PAttr;
P.PPara := P_Icon[i].PPara;
P.PIcon := P_Icon[i].PIcon;
Li := ListView1.Items.Add;
Li.Caption := P_Icon[i].PNickName;
Li.Data := PDA;
end;
end;
ListView1.Invalidate;
listview1.items.endupdate;
end;实际应用中 LoadListViewIcon 这一过程是经常调用的,所以ListView里的ICON是经常Clear后再载入,
但在释放ListView的item.Data指针这一句Dispose(PDA(listview1.items.item[i].Data)),也是采用了很多人认同的方式,就是采用Dispose
释放指针时,声明指针的类型.但使用(任务管理器)的内存实时监视程序运行中,不断反复执行LoadListViewIcon 这一过程时,程序占用内存还是不停以
4K的容量跳动上升的,这说明还是有内存没有释放,但经过跟踪,指针所占用的内存数据的确是已经被释放了.那么,问题就是:还有什么没有被释放?要怎样才能
释放这些内存?
type
TDIcon = packed record // packed record
Pserial: Integer;
PID: Integer;
PIDSort: Integer;
PFUllName: ShortString;
PNickName: ShortString;
PAttr: boolean;
PPara: ShortString;
PIcon: TMemoryStream;
end;
PDA = ^TDIcon;procedure Tform1.LoadListViewIcon; //ListView 刷新图标
var
i: integer;
LI: TListItem;
p:PDA;
begin
listview1.items.beginupdate;
for i := listview1.items.Count - 1 downto 0 do //释放Point
begin
if listview1.items.item[i].Data <> nil then Dispose(PDA(listview1.items.item[i].Data)); //这句关键
//ListView1.Items.Item[i].Data := nil;
end;
ListView1.Items.Clear; //ListView1.Items.Clear; 触发 OnDeletion 事件
if Icon_Kind then //大图标
begin //ListView1.IconOptions.Arrangement := iaTop;
TImage.Width := 32;
TImage.Height := 32;
ListView1.ViewStyle := vsIcon;
end
else
begin //ListView1.IconOptions.Arrangement := iaLeft;
TImage.Width := 16;
TImage.Height := 16;
ListView1.ViewStyle := vsSmallIcon;
end;
for i := 0 to length(P_Icon) - 1 do //P_Icon 就是 TDIcon 的数组
begin
if P_Icon[i].PID = Item1 then
begin
new(P);
P.PFullName := P_Icon[i].PFUllName;
P.PSerial := P_Icon[i].Pserial;
P.Pid := P_Icon[i].PID;
P.PIDSort := P_Icon[i].PIDSort;
P.PAttr := P_Icon[i].PAttr;
P.PPara := P_Icon[i].PPara;
P.PIcon := P_Icon[i].PIcon;
Li := ListView1.Items.Add;
Li.Caption := P_Icon[i].PNickName;
Li.Data := PDA;
end;
end;
ListView1.Invalidate;
listview1.items.endupdate;
end;实际应用中 LoadListViewIcon 这一过程是经常调用的,所以ListView里的ICON是经常Clear后再载入,
但在释放ListView的item.Data指针这一句Dispose(PDA(listview1.items.item[i].Data)),也是采用了很多人认同的方式,就是采用Dispose
释放指针时,声明指针的类型.但使用(任务管理器)的内存实时监视程序运行中,不断反复执行LoadListViewIcon 这一过程时,程序占用内存还是不停以
4K的容量跳动上升的,这说明还是有内存没有释放,但经过跟踪,指针所占用的内存数据的确是已经被释放了.那么,问题就是:还有什么没有被释放?要怎样才能
释放这些内存?
从代码看,记录的数据是从P_Icon数据复制过来的,shortstring的成员不会导致 写复制,整型的也不会导致此问题, 记录成员 PIcon保存的是指针,指向P_Icon的成员,只会增加引用计数, 也不会有导致内存的增加可能。
按照4k增长也可能是因为内存管理器太差,产生太多碎片所以每次都要申请新的空间
Li.Data := P 是正确的,但不管怎样,反复执行LoadListViewIcon 这一过程(不断清除和加载ICON),在任务管理器中,内存的占有量
的确是不断上升的.一个看上去既简单又复杂的问题,不知有大侠能深入指点一下吗?
sourceforge.net/projects/fastmm
看看使用说明,在ide里程序结束的时候会告诉你都有哪些东西没释放btw,1楼说的引用计数不知道哪里来的,他的代码里没有任何涉及引用计数的东西
TImage.Width := 32;
TImage.Height := 32;
ListView1.ViewStyle := vsIcon;
============================这里的 TImage 是啥东西?
测试以下代码,大量点击 Button1,内存占用很稳定。未发现楼主所说以4K增加的现象。
type
TDIcon = packed record // packed record
Pserial: Integer;
PID: Integer;
PIDSort: Integer;
PFUllName: ShortString;
PNickName: ShortString;
PAttr: boolean;
PPara: ShortString;
PIcon: TMemoryStream;
end;
PDA = ^TDIcon;procedure Tform1.LoadListViewIcon(); //ListView 刷新图标
var
i: integer;
LI: TListItem;
p:PDA;
begin
listview1.items.beginupdate;
for i := listview1.items.Count - 1 downto 0 do //释放Point
begin
if listview1.items.item[i].Data <> nil then
Dispose(PDA(listview1.items.item[i].Data)); //这句关键
//ListView1.Items.Item[i].Data := nil;
end;
ListView1.Items.Clear; //ListView1.Items.Clear; 触发 OnDeletion 事件
{ if Icon_Kind then //大图标
begin //ListView1.IconOptions.Arrangement := iaTop;
TImage.Width := 32;
TImage.Height := 32;
ListView1.ViewStyle := vsIcon;
end
else
begin //ListView1.IconOptions.Arrangement := iaLeft;
TImage.Width := 16;
TImage.Height := 16;
ListView1.ViewStyle := vsSmallIcon;
end;}
for i := 1 to 500 do //P_Icon 就是 TDIcon 的数组
begin
new(P);
P.PFullName := 'TEST Full';
P.PSerial := 10000;
P.Pid := 10000;
P.PIDSort := 10000;
P.PAttr := True;
P.PPara := 'TEST Data';
P.PIcon := nil;
Li := ListView1.Items.Add();
Li.Caption := 'TEST ' + IntToStr(i);
Li.Data := p;
end;
ListView1.Invalidate;
listview1.items.endupdate;
end;procedure TForm1.Button1Click(Sender: TObject);
begin
LoadListViewIcon();
end;
TDIcon = packed record // packed record
Pserial: Integer;
PID: Integer;
PIDSort: Integer;
PFUllName: ShortString;
PNickName: ShortString;
PAttr: boolean;
PPara: ShortString;
PIcon: TMemoryStream;
end;
PDA = ^TDIcon;procedure Tform1.LoadListViewIcon; //ListView 刷新图标
var
i: integer;
LI: TListItem;
p:PDA;
begin
listview1.items.beginupdate;
try //加入保护
for i := listview1.items.Count - 1 downto 0 do //释放Point
begin
if listview1.items.item[i].Data <> nil then Dispose(PDA(listview1.items.item[i].Data)); //这句关键
//ListView1.Items.Item[i].Data := nil;
end;
ListView1.Items.Clear; //ListView1.Items.Clear; 触发 OnDeletion 事件
if Icon_Kind then //大图标
begin //ListView1.IconOptions.Arrangement := iaTop;
TImage.Width := 32;
TImage.Height := 32;
ListView1.ViewStyle := vsIcon;
end
else
begin //ListView1.IconOptions.Arrangement := iaLeft;
TImage.Width := 16;
TImage.Height := 16;
ListView1.ViewStyle := vsSmallIcon;
end;
for i := 0 to length(P_Icon) - 1 do //P_Icon 就是 TDIcon 的数组
begin
if P_Icon[i].PID = Item1 then
begin
new(P);
P.PFullName := P_Icon[i].PFUllName;
P.PSerial := P_Icon[i].Pserial;
P.Pid := P_Icon[i].PID;
P.PIDSort := P_Icon[i].PIDSort;
P.PAttr := P_Icon[i].PAttr;
P.PPara := P_Icon[i].PPara;
P.PIcon := P_Icon[i].PIcon;
Li := ListView1.Items.Add;
Li.Caption := P_Icon[i].PNickName;
Li.Data := PDA;这句是明显错误
应该为:
Li.Data := P;
end;
end;
ListView1.Invalidate; //无须使用
finally
listview1.items.endupdate;
end;
end;
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
Cvs: TCanvas; //Bmp: TBitmap;
icon: TIcon;
begin
if Item.Data <> nil then
begin
Cvs := TCanvas.Create; //Bmp := TBitmap.Create;
icon := TIcon.Create;
Cvs.Handle := GetDC(Item.Handle); ------>>>就是这句导致内存不断增加
PDA(Item.Data).PIcon.Position := 0;
icon.LoadFromStream(PDA(Item.Data).PIcon);
Cvs.Draw(Item.Position.X, Item.Position.y, Icon);
Cvs.Free;
icon.Free;
end;
end;By the way: 下载安装了FastMM4,现在关闭程序时没有提示内存汇漏..( 但内存还是一直增加) . 以上这个过程是因为,加载ListView的图标我没有从ImageList中加载,而是
采用了在CustomDrawItem中,自已画ICON的方式,所以才需要 ListView1.Invalidate;
现在的终极问题是 为什么 Cvs.Handle := GetDC(Item.Handle); 这一句会导致内存不断增加,就算Free了也一样?
不然你打开任务管理器,在“进程”页中显示“GDI对象”,会看到每次执行完毕后GDI数量都会增加
GDI对象在整个windows系统中有数量限制的,总数是近16k,如果用光了GDI资源windows的显示就出问题了
再请教Seamour,多谢指点,因为对GDI资源不熟悉, 那么我现在应该怎样ReleaseDC才行呢? 样例代码应怎写才好呢?
很奇怪啊,装fastmm没啥问题那看英文应该没问题,而且知道用GetDC,那只要在msdn上看一下就该知道ReleaseDC用法了啊
var
Cvs: TCanvas; //Bmp: TBitmap;
icon: TIcon;
begin
if Item.Data <> nil then
begin
Cvs := TCanvas.Create; //Bmp := TBitmap.Create;
icon := TIcon.Create;
Cvs.Handle := GetDC(Item.Handle); ------>>>就是这句导致内存不断增加
PDA(Item.Data).PIcon.Position := 0;
icon.LoadFromStream(PDA(Item.Data).PIcon);
Cvs.Draw(Item.Position.X, Item.Position.y, Icon);
Cvs.Free;
icon.Free;
ReleaseDC(Item.Handle,DC); // 加上這句吧, 這東西大小有限制的。
end;
end;你為什麼剛開始不把這個也放出來啊,搞得上面兄弟們猜得好辛苦。
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
Cvs: TCanvas; //Bmp: TBitmap;
icon: TIcon;
begin
if Item.Data <> nil then
begin
Cvs := TCanvas.Create; //Bmp := TBitmap.Create;
icon := TIcon.Create;
Cvs.Handle := GetDC(Item.Handle); ------>>>就是这句导致内存不断增加
PDA(Item.Data).PIcon.Position := 0;
icon.LoadFromStream(PDA(Item.Data).PIcon);
Cvs.Draw(Item.Position.X, Item.Position.y, Icon);
Cvs.Free;
icon.Free;
ReleaseDC(Item.Handle,cvs.Handle); // 加上這句吧, 這東西大小有限制的。
end;
end;
呵呵,谢谢了,因为是半路自学出家,15年前从EGA,MONO VGA,打波斯王子一代和决战中南海开始接触计算机的,那时只知玩,没有基础,后来才开始自学VFP,VB,ASP
,SQL,C#,VB.NET 都只是皮毛,这二年才想深入些,就选了DELPHI,一直以来都想找个能有好的学习环境和气氛的团体或组织,都找不到.谢谢您和以上各位朋友百忙中抽空指教了,如不嫌弃和打扰您的话,我把您加为好友了. 昨晚上GOOGLE Search了下,当时把代码改成如下
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
Cvs: TCanvas; //Bmp: TBitmap;
icon: TIcon;
DC: HDC;
begin
if Item.Data <> nil then
begin
Cvs := TCanvas.Create; //Bmp := TBitmap.Create;
icon := TIcon.Create;
DC := GetDC(Item.Handle);
PDA(Item.Data).PIcon.Position := 0;
icon.LoadFromStream(PDA(Item.Data).PIcon);
Cvs.Handle :=DC;
Cvs.Draw(Item.Position.X, Item.Position.y, Icon);
ReleaseDC(Item.Handle, DC);
Cvs.Free;
icon.Free;
end;
end;运行通过了,个人理解 HDC=Handle吧,现在这部份基本完成了,但还有几个疑团没有冰释.
1)GDI资源没有正确释放而导致内存不断增加,但FastMM却检测不到内存泄漏,看来工具还不够人性化啊
2)如果当初采用ImageList加载ListView图标应该就不会出现如此问题了,但是从ImageList加载的图标很不美观,和提取的图标原形是不同的从这里可以看到除了TOOLS这个文件夹图标,其他三个都存在失真,无论提取图标用什么格式保存,BMP,JPG,只要用ImageList加载到ListView,图标就会出现失真,但采用在ListView的OnCustomDrawItem事件中自绘Icon就解决了这一问题,但在VB.NET 2005中就没有这个问题.
3)关于ListView中的图标,想用鼠标拖动某个Icon和任何其它一个Icon交换位置,这个问题我想了一个多月还没完全解决,因为Item的Index属性不能写,现在只是用了个最笨办法,在每个Icon的右键菜单中选择 上移 或 下移一个位置,这样只能一格格的移,每次也只能移动一次,有时需要移动多次才能交换2个Icon的位置.不知各位对用鼠标拖动Icon任意移动能否指点一下,这点在VB.net已解决了,但Delphi中不行. 如各位需要,可随时向我索取VB.net和Delphi的源码.
再说gdi资源是由windows内核分配的,x86平台的win内核在ring0下运行,fastmm一个用户态(ring3)的程序也不可能去检视它访问不了的资源
TImageView默认的size是16*16的,一般icon也是一个图包,里面会有好几种size。从效果上看,你没改大小ImageView的size吧,于是自动按16*16的图片载入,然后才放大成32*32的,当然难看了btw,我也只是业余玩玩编程而已
再次感谢,请问能不能介绍一些技术QQ群之类的交流环境?
另:
if Icon_Kind then //大图标
begin //ListView1.IconOptions.Arrangement := iaTop;
TImage.Width := 32;
TImage.Height := 32;
ListView1.ViewStyle := vsIcon;
end
else
begin //ListView1.IconOptions.Arrangement := iaLeft;
TImage.Width := 16;
TImage.Height := 16;
ListView1.ViewStyle := vsSmallIcon;
end;
这段是ListItem加入Icon时重设Icon Size的代码Icon_Small := TIcon.Create;
Icon_Big := TIcon.Create; SHGetFileInfo(pchar(FileInfo), 0, info, Sizeof(info), SHGFI_SMALLICON or SHGFI_ICON); //SHGFI_SMALLICON or SHGFI_ICON
Icon_Small.Handle := info.hIcon; SHGetFileInfo(pchar(FileInfo), 0, info, Sizeof(info), SHGFI_LARGEICON or SHGFI_ICON);
Icon_Big.Handle := info.hIcon;
这段是提取大小不同Icon Size的代码经这样处理 ,如果ListView加载Icon用ImageList还是会出现失真的.
注:在保存Icon时我已修改了D7 的Graphics单元的二处地方,令到Icon提取后保存可以有65535种Color,否则D7默认的Graphics单元处理保存Icon只有256种Color.
哦,最初的代码是这样的
ListView1.LargeImages := TImage_Large;
ListView1.SmallImages := TImage_Small;
初始化二个不同大小的TImageList,绑定到ListView,结果图标还是难看,后来不断试用了很多种方法都解决不了,最后才用Draw Icon的