我正在构建一个包含大约30个表单的应用程序。我需要管理会话,所以我希望可以从所有表单访问全局LoggedInUser变量。 我读了“David Heffernan”关于全局变量的帖子,以及如何避免它们,但我认为拥有一个全局User变量而不是30个具有自己的User变量的表单会更容易。
所以我有一个单位:GlobalVars
unit GlobalVars;
interface
uses User; // I defined my TUser class in a unit called User
var
LoggedInUser: TUser;
implementation
initialization
LoggedInUser:= TUser.Create;
finalization
LoggedInUser.Free;
end.
然后在我的LoginForm的LoginBtnClick程序中,我做了:
unit FormLogin;
interface
uses
[...],User;
type
TForm1 = class(TForm)
[...]
procedure LoginBtnClick(Sender: TObject);
private
{ Déclarations privées }
public
end;
var
Form1: TForm1;
AureliusConnection : IDBConnection;
implementation
{$R *.fmx}
uses [...]GlobalVars;
procedure TForm1.LoginBtnClick(Sender: TObject);
var
Manager : TObjectManager;
MyCriteria: TCriteria<TUser>;
u : TUser;
begin
Manager := TObjectManageR.Create(AureliusConnection);
MyCriteria :=Manager.Find<TUtilisateur>
.Add(TExpression.Eq('login',LoginEdit.Text))
.Add(TExpression.Eq('password',PasswordEdit.Text));
u := MyCriteria.UniqueResult;
if u = nil then
MessageDlg('Login ou mot de passe incorrect',TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],0)
else begin
LoggedInUser:=u; //Here I assign my local User data to my global User variable
Form1.Destroy;
A00Form.visible:=true;
end;
Manager.Free;
end;
然后在另一种形式中,我想在Menu1BtnClick过程中访问此LoggedInUser对象:
Unit C01_Deviations;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
FMX.ListView.Types, FMX.ListView, FMX.Objects, FMX.Layouts, FMX.Edit, FMX.Ani;
type
TC01Form = class(TForm)
[...]
Menu1Btn: TButton;
[...]
procedure Menu1BtnClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
C01Form: TC01Form;
implementation
uses [...]User,GlobalVars;
{$R *.fmx}
procedure TC01Form.Menu1BtnClick(Sender: TObject);
var
Assoc : TUtilisateur_FonctionManagement;
ValidationOK : Boolean;
util : TUser;
begin
ValidationOK := False;
util := GlobalVars.LoggedInUser; // Here i created a local user variable for debug purposes as I thought it would permit me to see the user data. But i get "Inaccessible Value" as its value
util.Nom:='test';
for Assoc in util.FonctionManagement do // Here is were my initial " access violation" error occurs
begin
if Assoc.FonctionManagement.Libelle = 'Reponsable équipe HACCP' then
begin
ValidationOK := True;
break;
end;
end;
[...]
end;
当我调试时,我在用户的值列中看到“Inaccessible Value”。你知道为什么吗?
我试图在这个GlobalVar单元中放置一个整数,我可以从我的登录表单中设置它的值,并从我的其他表单中读取它。
我想我可以存储用户的id,这是一个整数,然后使用其id从数据库中检索用户。但它似乎真的很无效。
答案 0 :(得分:5)
“我将此数据分配给我的全球用户” - 这是什么意思?
通过我的水晶球,我在您的登录表单中看到与此相似的代码:
var
user: TUser;
begin
user := TUser.Create;
try
// assign properties/fields of user instance
LoggedInUser := user;
finally
user.Free;
end;
end;
之后,LoggedInUser
指向已释放(可能重用)的内存块。处理LoggedInUser
的任何属性/字段可能会导致访问冲突。
LoggedInUser:=u;
向Assign(aSource: TUser)
类添加方法TUser
,该类为所有字段/属性(不是引用分配)执行值的深层副本,并改为调用它:
LoggedInUser.Assign(u);
答案 1 :(得分:1)
或许可以添加更多解释,请考虑以下代码部分:
begin
Manager := TObjectManageR.Create(AureliusConnection);
MyCriteria :=Manager.Find<TUtilisateur>
.Add(TExpression.Eq('login',LoginEdit.Text))
.Add(TExpression.Eq('password',PasswordEdit.Text));
u := MyCriteria.UniqueResult;
现在,TUser
是一个引用类型,因此变量u
包含指向该对象的指针。在这种情况下,u
现在指向属于您的UniqueResult
实例的TObjectManager
对象。
if u = nil then
MessageDlg('Login ou mot de passe incorrect',TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],0)
else begin
LoggedInUser:=u;
//Here I assign my local User data to my global User variable
没有。在这里,您还要LoggedInUser
指向u
所指向的同一对象实例,该实例仍然是UniqueResult
的{{1}}。此外,由于TObjectManager
中的initialization
部分已经创建了GlobalVars
的实例(TUser
指向的实例),您刚刚覆盖了该引用并泄露了内存。
LoggedInUser
现在你释放了 Form1.Destroy;
A00Form.visible:=true;
end;
Manager.Free;
end;
,摧毁了TObjectManager
- UniqueResult
和u
所指向的对象。
LoggedInUser
首先,您不会泄漏内存,因为您现在将为以前构造的 LoggedInUser.Assign(u);
对象分配值(而不是覆盖您对它的唯一引用!)。其次,当您的TUser
被释放时,您不会销毁TObjectManager
引用的对象。
如果LoggedInUser
是自定义类,则需要实现自己的深层复制方法。见here for an example。您不需要从TUser
派生,也不需要使用TPersistent
,但您需要在Assign
课程中提供某种类型的深层复制功能。
或者,from the documentation,看看
OwnsObjects属性
如果为true(默认值),则在销毁时会销毁所有托管对象 TObjectManager对象被销毁。如果为false,则对象保留 存储器中。
因此,在您释放TUser
之前,只需执行以下操作:
TObjectManager
小心释放您不再需要的查询等返回的任何其他对象。这将允许您像现在一样分配引用,它将阻止Manager.OwnsObjects := false;
Manager.Free;
在它自身被销毁时释放它。
请注意,此解决方案仍然会让您遇到内存泄漏问题,因此应该通过检查实例并在进行新分配之前释放它或者不构建空TObjectManager
来处理它。
答案 2 :(得分:1)
因为您要在全局变量中创建一个对象实例,这表示您希望稍后为其分配值,而不是分配一个全新的对象。
您可以使用.Assign方法按照建议执行此操作,或者如果只有一些方法,您可以逐个复制相关字段。
你想要一个&#34;深拷贝&#34; TUser对象的字段,并确保如果TUser中有任何对象,您可以确定是否需要进行深度复制或者是否可以复制引用。
但这是人们常犯的错误。你想要一个或者另一个 - 创建一个实例并将VALUES复制到其中;或者不要创建实例并为其分配另一个实例,就像从TObjectManager获得的实例一样。但正如同样指出的那样,TObjectManager返回的生命周期可能不是你所期望的。 (如果它使用一个接口,它会自动引用计数并且可以安全地分配给另一个对象。但是如果不考虑是否是这种情况,它可能并不明显。)
由于TObjectManager返回对象引用或NIL,您只需将TObjectManager的值直接分配给全局变量即可。在这种情况下,请不要在初始化部分创建默认实例。
我也回应了将对象传递给表单的contstructors而不是使用全局变量的情绪。这可以让你设置单元测试。
我还想添加其他人没有提及的评论。
else begin
LoggedInUser:=u; //Here I assign my local User data to my global User variable
Form1.Destroy;
A00Form.visible:=true;
end;
这不是你破坏表格的方式!因为从技术上讲,在调用Form1.Destroy之后没有任何内容在有效实例内运行。它大部分时间都可能正常工作,因为堆栈可能没有被破坏;但是任何人都在猜测A00Form.visible:= true是否会起作用。
并且......那不是你如何关闭一个表格而是将焦点放在另一个表格上。这个表格不应该知道任何其他形式。同样,如果需要创建表单时应该注入的东西,在这种情况下它实际上不是。
一般来说,使用OnClose处理程序来完成表单关闭时要发生的事情。但在这种情况下,你甚至不想要那样。
您想要从其他地方实例化表单,然后使用ShowModal来显示它。当它关闭时,您需要设置某种返回状态,以指示该人是否正确登录。如果他们这样做了,那么打开你希望他们看到的下一个表格。如果没有,您可能希望再次向他们显示登录表单,并在其上显示一条消息。
这也暗示了如何将TUser记录注入到每个表单中 - 通过将表单打包在创建表单实例的方法内部,传入TUser对象(无论是通过构造函数还是属性),然后省去ShowModal返回后的表单。这可能意味着您只想隐藏关闭而不是免费的表单,允许控制Display方法在销毁表单之前访问表单数据! (我相信caHide是OnClose处理程序的默认Action,所以如果你想用caFree替换它,你只需要添加一个OnClose。那是因为IDE的默认行为是自动的 - 创建表单,在这种情况下,你不希望他们在关闭时释放自己。如果你不自动创建你的表单,那么你肯定需要在创建它们之后释放它们并用Show显示它们或ShowModal - 除非你选择让它们在内存中徘徊,否则会破坏首先绕过自动创建的目的。)