Reputation: 249
I'm struggling with some memory allocation and a frustratingly difficult to locate and fix exception whilst using XE2.
I have a factory class which creates and stores dynamically created objects for display on a form, but the factory creation fails in some cases. If i execute the following code as the first action after program launch then all is well - the memory is allocated nicely and all other functions work. However, if i say open another form, or an open file dialog and then execute the factory creation there is an exception:
Exception EAccessViolation: Access violation at address 0040AA1F in module
'UMTester.exe'. Write of address 00000069 (OS Exception) Exception occurred at
$0040AA1F (Module "System", Procedure "@DynArrayAsg", Unit "", Line 0)
The exception is always at the address 0040AA1F and the proc is always @DynArrayAsg.
Although the factory will be created instantly at launch i don't want to ignore the bug as that would just be bad form!
Here's the factory creation code:
constructor TFactory.Create(FactoryObjectClass : tclass;Capacity : integer);
var nn : integer;
fptr : pointer;
fObj : TFactoryObject;
begin
fClass := FactoryObjectClass;
fsize := fclass.InstanceSize;
fdata:=nil;
//Capacity represents the number of objects; trying to allocate small amounts
//of memory results in exceptions so i artificially increase it anything less
//than 20 throws an exception
if Capacity<19 then Capacity:=20;
//This seems to reduce exceptions as the requested memory is in whole blocks
while frac((Capacity*SizeOf(Pointer))/4)<>0.0 do Inc(Capacity);
fcapacity := Capacity;
//Allocate memory
getmem(fdata,fsize*fcapacity);
getmem(fDatalist,sizeof(Pointer)*fcapacity);
getmem(ffreelist,sizeof(Pointer)*fcapacity);
fdatacount :=0;
ffreecount :=0;
fptr := fdata;
//create a pointer to the factory object at each memory address
for nn := 0 to Capacity-1 do begin
fdatalist[fdatacount]:= fptr; <------- Exception is always here
fobj := Fclass.InitInstance(fptr) as TFactoryObject;
fobj.factory := self;
fObj.Create;
fptr := pointer(integer(fptr)+fsize);
inc(fDataCount);
end;
end;
The factories pointer list is instantiated from a TDictionary with basic form elements like labels, memo's and listboxes etc.
procedure TObjs.Initialize(AObjs : TDict);
var
nn :integer;
C : TRttiContext;
T : TRttiInstanceType;
V : TValue;
begin
//Process all objects requested
for nn := 0 to AObjs.Count-1 do begin
//Locate the class from the supplied reference in AObjs
T := (C.GetType(TClass(FindClass(AObjs[nn]['Type']))) as TRttiInstanceType);
//Invoke the creation of the object by calling its native constructor
V := T.GetMethod('Create').Invoke(T.metaClassType,[Application]);
//Adds each requested object to the repository and displays the object
Objs[nn].SetValue(V.AsObject,AObjs[nn]);
end;
end;
Observations and Thoughts:
The factory is created once here:
SetLength(Objs,AObjs.Count);
AFactory := TFactory.Create(TchObj,AObjs.Count);
//Initialize the full array of objects
for Index := 0 to AObjs.Count -1 do
Objs[Index] := Afactory.Request_obj as TchObj;
Class definition for TFactory is:
TchFactory = class
private
fdata : pointer;
fsize : integer;
fDataList : PPointerList; // from classes unit
fdatacount : integer;
fFreeList : PPointerList;
fFreeCount : integer;
fCapacity : integer;
fcLass : TClass;
public
constructor Create(FactoryObjectClass : Tclass;Capacity : integer);
destructor Destroy; override;
function Request_Obj : TchFactoryObject;
procedure Recycle(FactoryObject : TchFactoryObject);
property Capacity : integer read fCapacity;
property CountUsed : integer read fdataCount;
property CountFree : integer read fFreeCount;
end;
What can i look at?
Any ideas on how i debug this?
Or am i just doing something insanely wrong?
EDIT: Removing the "trial and error" lines David refers to and changing GetMem to AllocMem fixes the issue. So the final code for the factory constructor is:
constructor TchFactory.Create(FactoryObjectClass : tclass;Capacity : integer);
var Index : integer;
fptr : pointer;
fObj : TchFactoryObject;
begin
fClass := FactoryObjectClass;
fsize := fclass.InstanceSize;
fcapacity := Capacity;
fdata := AllocMem(fsize*fcapacity);
fdatalist:= AllocMem(sizeof(Pointer)*fcapacity);
fFreelist:= AllocMem(sizeof(Pointer)*fcapacity);
fdatacount :=0;
ffreecount :=0;
fptr := fdata;
for index := 0 to Capacity-1 do begin
fdatalist[fdatacount]:= fptr;
fobj := Fclass.InitInstance(fptr) as TchFactoryObject;
fobj.factory := self;
fObj.Create;
fptr := pointer(integer(fptr)+fsize);
inc(fDataCount);
end;
end;
Upvotes: 0
Views: 1777
Reputation: 613441
Am I just doing something insanely wrong?
Yes. Insanely wrong. To instantiate an instance you should simply call the constructor. You want to create a bunch of them at once? Run a loop and call the constructor inside that loop.
It is possible to instantiate objects that are contiguous in memory, but it takes skill and understanding to do so. And it involves relying on implementation details. You should only attempt to do that if there are sound performance reasons for doing so. From what I can see that is not the case. And so a simple for loop to instantiate instances in the usual way is what you need.
Upvotes: 3
Reputation: 26361
Afaik this is allowed, and without full declaration it is hard to see what goes wrong. But IIRC you must make sure your memory is zero-initialized.
If the class has automated types then that is required.
Upvotes: 0