dll中设置TLabel字体大小,退出程序时出错

s11ss 2012-07-20 05:10:14
退出程序时报错:
[Quote]
---------------------------
Debugger Exception Notification
---------------------------
Project callDll.exe raised exception class EAccessViolation with message 'Access violation at address 0044A0E9 in module 'callDll.exe'. Read of address 00000297'.
---------------------------
Break Continue Help
---------------------------[/Quote]


dll代码:
library Project1;

{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }

uses
//SysUtils,
//Classes,
StdCtrls;
{$R *.res}

procedure setlabelfont(l: TLabel);
begin
l.Font.Size := 24
end;

exports
setlabelfont;

begin
end.


调用dll的程序代码:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure setlabelfont(l: TLabel); external 'project1.dll';

procedure TForm1.FormCreate(Sender: TObject);
begin
setlabelfont(Label1)
//Label1.Font.Size := 24
end;

end.

...全文
163 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
s11ss 2012-07-23
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 的回复:]
这个问题应该是类型不同引起的,dll中的使用到的类跟主程序中类,有些不是相同的
典型的就是这个TFont,像Cannot assign a TFont to a TFont这种错误都是这个原因

引用的地址会发生改变的

Label1.Font.Size:= 12;//主程序设置
setlabelfont(Label1);//调用dll函数,再设置,错

setlabelfont……
[/Quote]
今天深入研究了一下,有所收获!
类型是相同的,只不过类的地址不一样。比如dll中的TFont地址是123,exe中的TFont地址是456,假设dll导出一个函数:
function isfont(f: TObject): Boolean;
begin
Result := f is TFont;
end;
当exe中调用dll里面的这个函数时,会返回False。因为f is TFont实际上是在f不是nil的情况下取f对应类的地址(即f的头四个字节的值123),看它是不是与dll中TFont地址456一致,如果不一致则继续看f对应类的父类的地址是不是456,如果还不一致再比较父类的父类……这样一直比较,最终没有一致的,所以函数返回False。
上面的解释对应于System单元的_IsClass和TObject.InheritsFrom。

而至于以下
Label1.Font.Size:= 12;//主程序设置
setlabelfont(Label1);//调用dll函数,再设置,错

setlabelfont(Label1); //调用dll函数设置
Label1.Font.Size:=12;//返回主程序,再设置,错
第一条语句正确,而第2条语句报错,及只调用setlabelfont退出程序时报错,都是因为exe和dll的全局变量FontManager的值不同导致的——反向跟踪label1.font.size:=12:
1. TFont.SetSize
2. TFont.SetHeight
3. TFont.SetData
4. FontManager的ChangeResource:
procedure TResourceManager.ChangeResource(GraphicsObject: TGraphicsObject;
const ResData);
var
P: PResource;
begin
Lock;
try // prevent changes to GraphicsObject.FResource pointer between steps
P := GraphicsObject.FResource;{上一个FResource}
GraphicsObject.FResource := AllocResource(ResData);{分配资源}
if GraphicsObject.FResource <> P then GraphicsObject.Changed;
FreeResource(P);{释放上一个FResource,此方法内部报错}
finally
Unlock;
end;
end;

注意GraphicsObject实际上就是label1.font。
所以在dll分配资源,然后在exe释放;或在exe分配资源,然后在dll释放,都会导致报错!
5. 跟进FreeResource:
procedure TResourceManager.FreeResource(Resource: PResource);
var
P: PResource;
DeleteIt: Boolean;
begin
if Resource <> nil then
with Resource^ do
begin
Lock;
try
Dec(RefCount);
DeleteIt := RefCount = 0;
if DeleteIt then
begin
if Resource = ResList then
ResList := Resource^.Next
else{调整字体资源链表ResList,删除掉Resource所在节点}
begin
P := ResList;
while P^.Next <> Resource do P := P^.Next;
{这里报错,P^.Next最终会等于nil,这时如果再取P^.Next即nil^.Next,当然会报错!}

P^.Next := Resource^.Next;
end;
end;
finally
Unlock;
end;
if DeleteIt then
begin // this is outside the critsect to minimize lock time
if Handle <> 0 then DeleteObject(Handle);
FreeObjects(Resource);
FreeMem(Resource);
end;
end;
end;

因为exe和dll的全局变量FontManager的值不同,所以其内部的ResList自然也不同。
第一次设置字体大小时,FontManager1通过AllocResource把label1.font.FResource加入到了ResList1链表;
第2次设置字体大小时,FontManager2通过FreeResource试图从ResList2中找到label1.font.FResource并删除,最终在P^.Next <> Resource处报错!
至于我在帖子中只设置了一次也报错,是因为exe退出时释放TFont导致,其析构函数中也调了FreeResource,最终还是在P^.Next <> Resource处报错!

我暂时的解决办法是把Graphics单元复制一份到工程里,然后把FontManager及TResourceManager从implementation下剪切到其上,setlabelfont增加exe的FontManager作为参数。
procedure setlabelfont(m: TResourceManager; l: TLabel);
var
bak: TResourceManager;
begin
bak := FontManager;
FontManager := m;
l.Font.Size := 24;
FontManager := bak;
end;
exe调用时传入exe的FontManager:
setlabelfont(FontManager, Label1);
s11ss 2012-07-22
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 的回复:]
这个问题应该是类型不同引起的,dll中的使用到的类跟主程序中类,有些不是相同的
典型的就是这个TFont,像Cannot assign a TFont to a TFont这种错误都是这个原因

引用的地址会发生改变的

Label1.Font.Size:= 12;//主程序设置
setlabelfont(Label1);//调用dll函数,再设置,错

setlabelfont……
[/Quote]有点蒙。。。
s11ss 2012-07-21
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 的回复:]
把font的size做为参数传递是安全的
[/Quote]传递TLabel也可以啊,不然为什么调用之后字体大小改变了,只是退出程序时报错。
安全的是指线程安全吗?
lq8800835 2012-07-21
  • 打赏
  • 举报
回复
你要先声明dll的函数,然后再进行调用吧
newfang 2012-07-21
  • 打赏
  • 举报
回复
把font的size做为参数传递是安全的
kaikai_kk 2012-07-21
  • 打赏
  • 举报
回复
这个问题应该是类型不同引起的,dll中的使用到的类跟主程序中类,有些不是相同的
典型的就是这个TFont,像Cannot assign a TFont to a TFont这种错误都是这个原因

引用的地址会发生改变的

Label1.Font.Size:= 12;//主程序设置
setlabelfont(Label1);//调用dll函数,再设置,错

setlabelfont(Label1); //调用dll函数设置
Label1.Font.Size:=12;//返回主程序,再设置,错
s11ss 2012-07-20
  • 打赏
  • 举报
回复
delphi2007

5,379

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 开发及应用
社区管理员
  • VCL组件开发及应用社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧