Delphi Win64: memory is growing without leaks when using TThread.Synchronize() - delphi

I am facing an issue within the life application that memory (shown in Task Manager, or in GetProcessMemoryInfo -> PagefileUsage) is constantly growing while there is no any leaks reported by FastMM4.
Trying to narrow the issue, I did my best to create the smallest possible test case, but it still came out too big.
However, I am still posting it here because it's the only way I found to show the issue.
So, what this test does:
Checks occupied memory on startup reported by GetMemoryManagerState() and by GetProcessMemoryInfo().
Creates 10k threads, which allocate some memory.
After all 10k threads are terminated, all allocated memory is freed.
I double checked many times with Eurekalog and FastMM in full debug mode that all created objects do not leak.
Until the Stop button is hit, the process continues to Step 2.
So, despite that I'm sure that all objects are being released, occupied memory after the first run has increased dramatically by almost 170MB.
Digging more into System.SysUtils, I found out that a bit of memory is used for EventCache (which is not cleared automatically after all threads are done), however that doesn't explain 170MB for me (10k thread syncs spawns about 9k PEventItemHolder).
What is even more strange is that, on each next step, occupied memory continues to grow! Despite on each step, the application state should be completely the same (no more objects allocated, no more EventCache spawned).
Application log looks like this:
Before: Mem: Used 67 kb UsedOS: 4 MB
After: Mem: Used 323 kb UsedOS: 170 MB
After: Mem: Used 323 kb UsedOS: 178 MB
After: Mem: Used 323 kb UsedOS: 181 MB
After: Mem: Used 323 kb UsedOS: 176 MB
After: Mem: Used 323 kb UsedOS: 182 MB
After: Mem: Used 323 kb UsedOS: 178 MB
After: Mem: Used 323 kb UsedOS: 206 MB
Why is this happening? Are there any tools to help dig more into the issue, especially in the life application, which is very heavy?
The code:
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Generics.Collections, Generics.Defaults,
Math, psAPI, REST.Client,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
const
ThreadsCount = 10000;
type
TForm12 = class(TForm)
MemLabel: TLabel;
MemTimer: TTimer;
bStart: TButton;
bStop: TButton;
Memo1: TMemo;
procedure MemTimerTimer(Sender: TObject);
procedure bStartClick(Sender: TObject);
procedure bStopClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
procedure UpdateMemInfo;
public
procedure LogNextStep;
{ Public declarations }
end;
TOrder = packed record
sx: string[10];
Time: TDateTime;
x,y,z: double;
end;
POrder = ^TOrder;
TTestThread = class(TThread)
WaitFlag: boolean;
public
Num: integer;
ptr: POrder;
R: TRestRequest;
Name: string;
data: array [1..50] of double;
procedure Execute; override;
end;
var
Form12: TForm12;
implementation
{$R *.dfm}
var
GlobalUsedMem, GlobalUsedOSMem, AddedCount: integer; // MB
ThCount, ThGlobalID: integer;
JobDone, TestStopRequest: boolean;
ThList: TList<TTestThread>;
OrderList: TList<POrder>;
procedure CreateNewThread;
begin
TTestThread.Create(false);
end;
procedure CreateNewTest;
begin
TThread.CreateAnonymousThread(
procedure
var
k: integer;
begin
sleep(3000);
for k := 1 to ThreadsCount do
CreateNewThread;
end).Start;
end;
procedure CLeanOrders;
var
o: POrder;
begin
for o in OrderList do FreeMem(o);
OrderList.CLear;
end;
procedure TForm12.bStartClick(Sender: TObject);
begin
UpdateMemInfo;
Memo1.Clear;
Memo1.Lines.Add('Before: ' + MemLabel.Caption);
ThGlobalID := 0;
TestStopRequest := false;
JobDone := false;
AddedCount := 0;
CreateNewTest;
end;
procedure TForm12.bStopClick(Sender: TObject);
begin
TestStopRequest := true;
end;
procedure TForm12.FormClose(Sender: TObject; var Action: TCloseAction);
begin
TestStopRequest := true;
repeat
Application.ProcessMessages;
sleep(10);
until JobDOne;
ThList.Free;
OrderList.Free;
end;
procedure TForm12.FormCreate(Sender: TObject);
begin
JobDone := true;
ThList := TList<TTestThread>.Create;
OrderList := TList<POrder>.Create;
end;
procedure TForm12.LogNextStep;
begin
UpdateMemInfo;
Memo1.Lines.Add('After: ' + MemLabel.Caption);
end;
procedure TForm12.UpdateMemInfo;
var
m4: UInt64;
mx: integer;
st: TMemoryManagerState;
sb: TSmallBlockTypeState;
MemCounters: TProcessMemoryCounters;
begin
MemCounters.cb := SizeOf(MemCounters);
if GetProcessMemoryInfo(GetCurrentProcess, #MemCounters, SizeOf(MemCounters))
then GlobalUsedOSMem := round(MemCounters.PagefileUsage / 1000000);;
GetMemoryManagerState(st);
m4 := st.TotalAllocatedMediumBlockSize + st.TotalAllocatedLargeBlockSize;
for sb in st.SmallBlockTypeStates do begin
m4 := m4 + sb.UseableBlockSize * sb.AllocatedBlockCount;
end;
GlobalUsedMem := round(m4 / 1000);
MemLabel.Caption := Format(' Mem: Used %d kb UsedOS: %d MB O: %d Th: %d ',
[ GlobalUsedMem, GlobalUsedOSMem, OrderList.Count, ThCount ]);
end;
procedure TForm12.MemTimerTimer(Sender: TObject);
begin
UpdateMemInfo;
end;
procedure SyncProc(o: TTestThread);
begin
OrderList.Add(o.ptr);
o.R := TRestRequest.Create(nil);
o.Name := 'Test Thread ' + o.Num.toString;
ThList.Add(o);
inc(AddedCount);
end;
procedure SyncDel(o: TTestThread);
var
k: integer;
s: string;
begin
dec(AddedCount);
ThList.remove(o);
FreeAndNil(o.R);
If AddedCount = 0 then begin
ThGlobalID := 0;
thList.Clear;
CLeanOrders;
Form12.LogNextStep;
If not TestStopRequest then
CreateNewTest;
end;
If (not TestStopRequest) and (random(5) = 1) and (ThCount > 1000) then CreateNewThread;
end;
{ TTestThread }
procedure TTestThread.Execute;
var
k: integer;
pLarge, pSmall, pMed: pointer;
tm1: Uint64;
workingTime: integer;
const
IterCount = 50;
begin
WaitFlag := false;
AtomicIncrement(ThCount);
Num := AtomicIncrement(ThGlobalID) - 1;
sleep(random(500));
GetMem(pLarge, 100500);
GetMem(pMed, 25000);
GetMem(pSmall, 800);
FreeMem(pLarge);
New(ptr);
ptr.Time := Now;
Synchronize( procedure begin SyncProc(self); end );
FreeMem(pMed);
FreeMem(pSmall);
workingTime := 3000 + random (2000);
tm1 := GetTickCount64;
repeat
Sleep(100);
until TestStopRequest or (abs(GetTickCount64 - tm1) > workingTime);
sleep(random(1000));
k := AtomicDecrement(ThCount);
JobDone := (k=0);
FreeOnTerminate := true;
Synchronize( procedure begin SyncDel(self); end );
end;
end.

Related

How to get volume level in current sample? Delphi 7

On Delphi 7 I am running this code with NewAC Audio library. I am having short wav file, 44.100 kHz, mono, 16 bit.
unit Main;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ACS_Classes, ACS_DXAudio, ACS_Wave, ACS_Misc, ACS_Types, StdCtrls;
type
TForm1 = class(TForm)
AudioProcessor1: TAudioProcessor;
WaveIn1: TWaveIn;
DXAudioOut1: TDXAudioOut;
OpenDialog1: TOpenDialog;
Button1: TButton;
Button2: TButton;
procedure AudioProcessor1GetData(
Sender: TComponent;
var Buffer: Pointer;
var NBlockBytes: Cardinal);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure DXAudioOut1Done(Sender: TComponent);
procedure AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
procedure AudioProcessor1Flush(Sender: TComponent);
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.AudioProcessor1GetData(Sender: TComponent;
var Buffer: Pointer; var NBlockBytes: Cardinal);
var Tmp : Integer;
i : Integer;
list1: TStringList;
list2: TStringList;
b1, b2, b3, b4:byte;
si1, si2, si3, si4: ShortInt;
mono: Boolean;
values: array of word;
begin
list1 := TStringList.Create;
list2 := TStringList.Create;
AudioProcessor1.Input.GetData(Buffer, NBlockBytes);
if Buffer = nil then
Exit;
mono := false;
case AudioProcessor1.Input.BitsPerSample of
16 :
begin
B16 := Buffer;
setlength(values, NBlockBytes div 2);
for i := 0 to (NBlockBytes div 4) - 1 do
begin
Tmp := B16[i*2];
move(B16[i*2], b1, 1); // copy left channel
move(B16[i*2+1], b2, 1); // copy right channel
move(B16[i*2+2], b3, 1); // copy left channel
move(B16[i*2+3], b4, 1); // copy right channel
si1 := b1;
si2 := b2;
si3 := b3;
si4 := b4;
list1.add(''+inttostr(si1));
list2.add(''+inttostr(si2));
list1.add(''+inttostr(si3));
list2.add(''+inttostr(si4));
B16[i*2] := B16[i*2 + 1];
B16[i*2 + 1] := Tmp;
end;
end;
end;
list1.free;
list2.free;
end;
procedure TForm1.AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
begin
TAudioProcessor(Sender).Input.Init;
TotalSize := TAudioProcessor(Sender).Input.Size
end;
procedure TForm1.AudioProcessor1Flush(Sender: TComponent);
begin
TAudioProcessor(Sender).Input.Flush;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Button1.Enabled := False;
WaveIn1.FileName := OpenDialog1.FileName;
DXAudioOut1.Run;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DXAudioOut1.Stop;
end;
procedure TForm1.DXAudioOut1Done(Sender: TComponent);
begin
Button1.Enabled := True;
end;
end.
When I open the file in editing software I can see the amplitude of the sound and I see that the beginning values are 0. But when I run this program and I add the si1, si2, si3 and si4 to watch (in this order are the variables in watch), so I have these values in first iteration:
80,124,104,32.
I expected that these values should be 0 because there is silence on the begin.
First, may you explain why these are not zero?
Second, I am not sure what these values really represent. I know that si1 and si2 are first sample. But is it really level of the volume? How to correct the program to recognize the silence in the begin?
Tested file -> the section which should be passed to the function as first.
This part is not proccessed (because I processed only few cicles of the first loop):
I did some tests with file "silence plus", amplifications and see the first 8 cicles values.
Another test with word instead byte:
B16 := Buffer;
...
move(B16[i*2], w1, 2);
move(B16[i*2+1], w2, 2);
It really looks like the bits need to swap. I thought that in Windows XP I have little endian bit order. So I will write a swapper.
The main problems of my code were:
1) Reading 1 byte of sample instead 2 bytes of sample.
2) The sample is signed, not unsigned. So when I tried to read two bytes of word, I get wrong numbers (see the last table in question).
3) I also tried to use two bytes of SmallInt swapped, but that resulted to crazy numbers like -25345, -1281, 26624, -19968 ... This is because on my system I use Little endian (Windows XP). There is not need to swap it on Windows.
So the solution was to copy 16 bits to SmallInt, no swap.
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ACS_Classes, ACS_DXAudio, ACS_Wave, ACS_Misc, ACS_Types, StdCtrls;
type
TForm1 = class(TForm)
AudioProcessor1: TAudioProcessor;
WaveIn1: TWaveIn;
DXAudioOut1: TDXAudioOut;
OpenDialog1: TOpenDialog;
Button1: TButton;
Button2: TButton;
procedure AudioProcessor1GetData(
Sender: TComponent;
var Buffer: Pointer;
var NBlockBytes: Cardinal);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure DXAudioOut1Done(Sender: TComponent);
procedure AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
procedure AudioProcessor1Flush(Sender: TComponent);
private
{ Private declarations }
public
{ Public declarations }
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.AudioProcessor1GetData(Sender: TComponent;
var Buffer: Pointer; var NBlockBytes: Cardinal);
var
B16 : PBuffer16;
i, end_ : Integer;
si1, si2: SmallInt;
begin
AudioProcessor1.Input.GetData(Buffer, NBlockBytes);
if Buffer = nil then
Exit;
case AudioProcessor1.Input.BitsPerSample of
16 :
begin
B16 := Buffer;
end_ := (NBlockBytes div 2) - 1;
for i := 0 to end_ do
begin
move(B16[i*2], si1, 2);
move(B16[i*2+1], si2, 2);
end;
end;
end;
end;
procedure TForm1.AudioProcessor1Init(Sender: TComponent; var TotalSize: Int64);
begin
TAudioProcessor(Sender).Input.Init;
TotalSize := TAudioProcessor(Sender).Input.Size
end;
procedure TForm1.AudioProcessor1Flush(Sender: TComponent);
begin
TAudioProcessor(Sender).Input.Flush;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Button1.Enabled := False;
WaveIn1.FileName := OpenDialog1.FileName;
DXAudioOut1.Run;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DXAudioOut1.Stop;
end;
procedure TForm1.DXAudioOut1Done(Sender: TComponent);
begin
Button1.Enabled := True;
end;
end.
Here are the values:

Interfacing Octave and Lazarus/FreePascal with TProcess

I have also asked this question # the Lazarus forums, here
I am trying to communicate with Octave via a TProcess, but I don't seem to be able to read any bytes from it. Attached is the main form's unit; a full demo application is available as a zip from the forum under my post.
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Process;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
POctave: TProcess;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
if (not POctave.Running) then
begin
POctave.Executable := 'C:\Octave\Octave-4.2.0\bin\octave-cli.exe';
POctave.Parameters.Add('--no-gui');
POctave.Options := [poUsePipes];
WriteLn('-- Executing octave --');
POctave.Execute;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
command: string;
Buffer: string;
BytesAvailable: DWord;
BytesRead: longint;
NoMoreOutput: boolean;
begin
command := 'pwd' + #10;
if (POctave.Running) then
POctave.Input.Write(command, Length(command));
if (POctave.Running) then
begin
BytesAvailable := POctave.Output.NumBytesAvailable;
BytesRead := 0;
while BytesAvailable > 0 do
begin
SetLength(Buffer, BytesAvailable);
BytesRead := POctave.OutPut.Read(Buffer[1], BytesAvailable);
WriteLn(Buffer);
BytesAvailable := POctave.Output.NumBytesAvailable;
NoMoreOutput := False;
end;
end;
end;
initialization
POctave := TProcess.Create(nil);
finalization
POctave.Free;
end.
I've added sleep routines and changed the 'pwd' command's return to #1310, both without success.
procedure TForm1.Button2Click(Sender: TObject);
var
command: ansistring;
Buffer: string;
BytesAvailable: DWord;
BytesRead: longint;
NoMoreOutput: boolean;
begin
command := 'pwd'#13#10;
if (POctave.Running) then
POctave.Input.Write(command, Length(command));
Sleep(100);
if (POctave.Running) then
begin
Sleep(100);
BytesAvailable := POctave.Output.NumBytesAvailable;
BytesRead := 0;
while BytesAvailable > 0 do
begin
Sleep(100);
SetLength(Buffer, BytesAvailable);
BytesRead := POctave.OutPut.Read(Buffer[1], BytesAvailable);
WriteLn(Buffer);
BytesAvailable := POctave.Output.NumBytesAvailable;
NoMoreOutput := False;
end;
end;
end;
The problem was that I was calling this line:
POctave.Input.Write(command, Length(command));
instead of this:
POctave.Input.Write(command[1], Length(command));
After changing this (AND ADDING THE DELAY! It was absolutely critical to wait for the result, but my mistake was more fundamental.)
Remember: Pascal strings aren't C strings. Whoops...
It worked! Now I can send commands to Octave and retrieve the results via pipes. :)

2 delphi questions, copying code and randomizing

I'm making my first program in delphi and it's a space invaders rip off. So I have 2 questions:
First off, how would I copy code to every single object? This is what I have now:
procedure TForm2.Timer1Timer(Sender: TObject);
begin
//Label2.Caption := IntToStr(Form2.ClientWidth);
//Label1.Caption := IntToStr(Shape2.Left + Shape2.Width);
if smer = 1 then begin
Shape2.Left:=Shape2.left+56;
Shape3.Left:=Shape3.left+56;
Shape4.Left:=Shape4.left+56;
Shape5.Left:=Shape5.left+56;
Shape6.Left:=Shape6.left+56;
if Shape6.Left+Shape6.Width>Form2.ClientWidth then begin
Shape2.Top:=Shape2.Top+56;
Shape3.Top:=Shape3.Top+56;
Shape4.Top:=Shape4.Top+56;
Shape5.Top:=Shape5.Top+56;
Shape6.Top:=Shape6.Top+56;
smer:=0;
end;
end;
if smer = 0 then begin
Shape2.Left:=Shape2.left-56;
Shape3.Left:=Shape3.left-56;
Shape4.Left:=Shape4.left-56;
Shape5.Left:=Shape5.left-56;
Shape6.Left:=Shape6.left-56;
if Shape2.Left<=0 then begin
Shape2.Top:=Shape2.Top+56;
Shape3.Top:=Shape3.Top+56;
Shape4.Top:=Shape4.Top+56;
Shape5.Top:=Shape5.Top+56;
Shape6.Top:=Shape6.Top+56;
smer:=1;
end;
end;
end;
procedure TForm2.Timer2Timer(Sender: TObject);
begin
if MetakP.Visible=true then begin
MetakP.Top:=MetakP.Top-11;
end;
if MetakN.Visible=true then begin
MetakN.Top:=MetakN.Top+11;
end;
if MetakN.Top>Form2.Height then MetakN.Visible:=false;
if MetakP.Top<=0 then begin
MetakP.Left:=Image1.Left+16;
MetakP.Top:=Image1.Top;
MetakP.visible:=false;
Let:=0;
end;
if (MetakN.left>=Image1.Left) or (MetakN.Left+MetakN.Width >= Image1.left) then begin // da li je metak desno od kocke
if MetakN.left<=Image1.Left+Image1.Width then begin // da li je metak levo od kocke
If MetakN.Top<=Image1.Top+Image1.Height then begin // da li je metak ispod kocke
if MetakN.Top>=Image1.Top-Image1.Height then begin
if MetakN.Visible=true then begin
Image1.Visible:=false;//
MetakN.Left:=Image1.Left+16;
MetakN.Top:=Image1.Top;
MetakN.visible:=false;
Let:=0;
gub:=gub+1;
//Image1.Enabled:=false;
end;
end;
end;
end;
end;
if (MetakP.left>=Shape2.Left) or (MetakP.Left+MetakP.Width >= Shape2.left) then begin // da li je metak desno od kocke
if MetakP.left<=Shape2.Left+Shape2.Width then begin // da li je metak levo od kocke
If MetakP.Top<=Shape2.Top+Shape2.Height then begin // da li je metak ispod kocke
if MetakP.Top>=Shape2.Top-Shape2.Height then begin
if Shape2.Visible=true then begin
Shape2.Visible:=false;//
MetakP.Left:=Image1.Left+16;
MetakP.Top:=Image1.Top;
MetakP.visible:=false;
Let:=0;
pob:=pob+1;
//Shape2.Enabled:=false;
end;
end;
end;
end;
end;
end;
This is continued for all shapes. It's basically a hitbox check. Now, that's a lot of code, is there a way I could make it work for all the shapes separately?
Second off, how would I fire off a bullet out of a random shape? I have:
procedure TForm2.Timer4Timer(Sender: TObject);
var r:integer;
var rr:string;
begin
MetakN.Visible:=true;
if Shape2.Visible=false then MetakN.Visible:=false;
r:=2+random(5);
rr:=IntToStr(r);
MetakN.Top:= Shape2.top+Shape2.Height;
MetakN.Left:= Shape2.Left+Shape2.Width div 2;
end;
The r was supposed to be used as "Shape[r].top" and so on, but it doesn't work.
Programs are made up of 2 parts.
Code
Data structures
You are only using 1.
You need to get a data structure to hold your Aliens.
Because it's just a bunch of aliens a list will work fine.
Add a variable to your form to put your aliens in.
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections;
TForm1 = class(TForm)
....
private
Aliens: TList<TShape>;
You can initialize your shapes on form creation.
Something like this.
procedure TForm1.FormCreate(Sender: TObject);
var
i: integer;
AngryAlien: TShape;
begin
Aliens:= TList<TShape>.Create;
for i := 0 to 100 do begin
AngryAlien:= TShape.Create(Form1);
AngryAlien.Parent:= Form1;
AngryAlien.Shape:= stCircle;
AngryAlien.Brush.Color:= clWhite;
AngryAlien.Width:= 30;
AngryAlien.Height:= 30;
AngryAlien.Visible:= false;
Aliens.Add(AngryAlien);
end;
end;
Now you have a 100 101 aliens.
You can move the aliens around on a timer.
procedure TForm1.Timer1Timer(Sender: TObject);
var
i: integer;
Alien: TShape;
begin
//Move 4 aliens.
for i := 0 to 100 do begin
Alien:= Aliens[i];
Alien.Visible:= true;
Alien.Left:= Alien.Left + Random(30) - Random(20);
Alien.Top:= Alien.Top + Random(15) - Random(10);
end;
end;
Now you just use a loop to control every alien in turn.
If you want some game sample code, here something to get you started: http://delphi.about.com/od/gameprogramming/
More specifically: http://delphi.about.com/library/code/fdac_dodge_src.zip
Of course the above code is a bad example of copy-paste anti pattern and I would rewrite it like so:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
shp_player: TShape;
shp_enemy: TShape;
btnStart: TButton;
timercircle: TTimer;
shparea: TShape;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Edit1: TEdit;
Label5: TLabel;
Shape1: TShape;
Lbl_player: TLabel;
lbl_circle: TLabel;
lbl_enemy: TLabel;
procedure FormCreate(Sender: TObject);
procedure btnStartClick(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure timercircleTimer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
//my own category of variables
TLevelArray = Array [1 .. 30] of Boolean;
var
circle: array [1 .. 30] of TShape;
Speedx: array [1 .. 30] of Integer;
Speedy: array [1 .. 30] of Integer;
Level: array [1..30] of TLevelArray;
SpeedxCalculation: Integer;
SpeedyCalculation: Integer;
LevelStore: Integer = 1;
HighScore: Boolean = False;
procedure ShowCircles(Level: TLevelArray);
var
Count: Integer;
begin
for Count:= 1 to 30 do begin
circle[Count].Visible:= Level[Count];
end;
end;
procedure InitLevels;
var
i,j: integer;
begin
for i := 1 to 30 do begin
FillChar(Level[i], SizeOf(Level[i]),#0);
end;
for i := 1 to 30 do begin
for j := 1 to i do begin
Level[i][j]:= true;
end;
end;
end;
procedure Updatecircles; //if the circle needs to be visible for that level
var
Count: Integer;
begin
for Count:= 1 to 30 do begin
ShowCircles(Level[LevelStore]);
end;
end;
Procedure SpeedCalculation;
begin
circle[LevelStore].Left:= 8; //all the circles come from the same position
circle[LevelStore].Top:= 8;
repeat
Randomize; //their speeds are random for more interesting gameplay
SpeedxCalculation:= Random(10) + 1;
Speedx[LevelStore]:= 5 - SpeedxCalculation;
Randomize;
SpeedyCalculation:= Random(10) + 1;
Speedy[LevelStore]:= 5 - SpeedyCalculation;
until (speedy[LevelStore]) and (Speedx[LevelStore]) <> 0;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Count: Integer;
i: integer;
MyCircle: TShape;
begin
InitLevels;
for i := 1 to 30 do begin
MyCircle:= TShape.Create(Self);
MyCircle.Parent:= Self;
MyCircle.Width:= 10;
MyCircle.Height:= 10;
MyCircle.Brush.Color:= clmaroon;
MyCircle.Visible:= false;
MyCircle[i]:= MyCircle;
end;
Randomize;
shp_enemy.Left:= Random(clientwidth) - shp_enemy.width;
shp_enemy.Top:= Random(clientheight) - shp_enemy.height;
lbl_enemy.Left:= shp_enemy.Left;
lbl_enemy.Top:= shp_enemy.Top - 20;
SpeedCalculation;
updatecircles;
end;
procedure TForm1.btnStartClick(Sender: TObject);
begin
TimerCircle.enabled:= True;
btnStart.Visible:= False;
Label2.Caption:= '0';
Edit1.enabled:= False;
lbl_player.Visible:= False;
lbl_enemy.Visible:= False;
lbl_circle.Visible:= False;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
shp_player.Left:= x - shp_player.Width - 10;
shp_player.Top:= y - shp_player.Height - 10; //the green block follows the mouse
lbl_player.Left:= x - lbl_player.Width - 10;
lbl_player.Top:= y - lbl_player.Height - 30;
end;
procedure TForm1.timercircleTimer(Sender: TObject);
var
overlay: Trect;
Count: Integer;
begin
for Count:= 1 to LevelStore do begin
// Moves the circles
circle[Count].Left:= circle[Count].Left + speedx[Count];
circle[Count].Top:= circle[Count].Top + speedy[Count];
//bounces the circles off of the boundaries of the form
if circle[Count].Left > clientwidth - circle[Count].width then speedx[Count]:= -speedx[Count]
else if circle[Count].Left < 0 then speedx[Count]:= -speedx[Count];
if circle[Count].Top > clientheight - circle[Count].Height then speedy[Count]:= -speedy[Count]
else if circle[Count].Top < 0 then speedy[Count]:= -speedy[Count];
//detects a collision between a circle and the players block
if Intersectrect(overlay, circle[Count].BoundsRect, shp_player.BoundsRect) then begin
c1.Left:= 8;
c1.Top:= 8;
btnstart.caption:= 'Restart';
btnstart.Visible:= True;
LevelStore:= 1;
SpeedCalculation;
UpdateCircles;
timercircle.enabled:= false;
if HighScore = True then //if a new high score has been achieved
begin
Edit1.Enabled:= True;
HighScore:= False;
end;
lbl_player.Visible:= True;
lbl_enemy.Visible:= True;
lbl_circle.Visible:= True;
lbl_enemy.Left:= shp_enemy.Left;
lbl_enemy.Top:= shp_enemy.Top - 20;
end;
//detects a collision between the player block and target block
if Intersectrect(overlay, shp_enemy.BoundsRect, shp_player.BoundsRect) then begin
Label2.Caption:= inttostr(strtoint(Label2.Caption) + 1);
if strtoint(Label2.Caption) > strtoint(Label4.Caption) then begin
highscore:= True;
Label4.Caption:= Label2.Caption;
end;
Randomize;
repeat
//the target block goes to a new position on the form
shp_enemy.Left:= Random(clientwidth) + 2 * (shp_enemy.width);
shp_enemy.Top:= Random(clientheight) - 2 * (shp_enemy.height);
until ((shp_enemy.Left) > (Form1.Left + shp_enemy.Width)) and
((shp_enemy.Left) < (Form1.Left + clientwidth - 2 * (shp_enemy.Width))) and
((shp_enemy.Top) > (Form1.Top + shp_enemy.Height)) and
((shp_enemy.Top) < (Form1.Top + clientwidth - 2 * (shp_player.Width)));
LevelStore:= LevelStore + 1;
if LevelStore = 30 then // there are only 30 circles
begin
MessageDlg('Congratulations! - You have completed the game!', mtinformation, [mbOK], 0);
timercircle.enabled:= false;
btnstart.Visible:= True;
LevelStore:= 1;
SpeedCalculation;
UpdateCircles;
end else begin
SpeedCalculation;
UpdateCircles;
end;
end;
end;
end;
end.//FIN - Code by Si (c)
That way you don't repeat yourself.

NetShareEnum not displaying shares correctly

I'm developing a project to help me managing my remote network, as I need some very specific features I decided to code it.
I connect to the remote computers using WNetAddConnection2 and this part is working. But now I try to list all the shares (ADMIN$, C$, IPC$, and any shared folders) using the NetShareEnum function. I relied on this function and not on WNetEnumResource because I found more examples working with NetShareEnum, and it's working better for me. The problem is that my implementation of NetShareEnum is listing only some type of folders (looks like only folders that are shared but I have no access). It doesn't list normal folders (where I have access), ADMIN$, C$, IPC$, or anything else. Only shared folders that I'm without rights to access.
I still not sure if the behavior is the same on all servers, but the ones I tested it was. So far what I have is:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
HostFile: TStringList;
iHost: integer;
type
SharesThread = class(TThread)
strict private
IPAddress: String;
function Authenticate: bool;
procedure EnumShares(RemoteName: PWChar);
protected
constructor Create(const IPv4: string);
procedure Execute; override;
end;
type
_SHARE_INFO_502 = packed record
shi502_netname: PWideChar;
shi502_type: DWORD;
shi502_remark: PWideChar;
shi502_permissions: DWORD;
shi502_max_uses: DWORD;
shi502_current_uses: DWORD;
shi502_path: LPWSTR;
shi502_passwd: LPWSTR;
shi502_reserved: DWORD;
shi502_security_dsc: PSECURITY_DESCRIPTOR;
end;
SHARE_INFO_502 = _SHARE_INFO_502;
PSHARE_INFO_502 = ^SHARE_INFO_502;
LPSHARE_INFO_502 = PSHARE_INFO_502;
TShareInfo502 = SHARE_INFO_502;
PShareInfo502 = PSHARE_INFO_502;
type
TShareInfo502Array = Array [0..MaxWord] of TShareInfo502;
PShareInfo502Array = ^TShareInfo502Array;
function NetApiBufferFree(buffer: Pointer): DWORD; stdcall; external 'netapi32.dll';
function NetShareEnum(servername: PWideChar;
level: DWORD;
bufptr: PByteArray;
prefmaxlen: DWORD;
entriesread: PDWORD;
totalentries: PDWORD;
resume_handle: PDWORD): DWORD; stdcall; external 'netapi32.dll';
implementation
const
NERR_Success = 0;
MAX_PREFERRED_LENGTH = DWORD( -1 );
procedure StartThreads;
var
CurrentIP: string;
begin
if (iHost < HostFile.Count) then
begin
CurrentIP:= HostFile.Strings[iHost];
inc(iHost);
SharesThread.Create(CurrentIP);
end
else
Form1.Memo1.Lines.Add('finished');
end;
constructor SharesThread.Create(const IPv4: string);
begin
inherited Create(false);
FreeOnTerminate:= true;
IPAddress:= IPv4;
end;
function SharesThread.Authenticate;
var
lpNetResource: TNetResource;
myres: cardinal;
begin
with lpNetResource do
begin
dwType := RESOURCETYPE_ANY;
lpLocalName := nil;
lpProvider := nil;
lpRemoteName:= PChar('\\'+IPAddress);
end;
myres := WNetAddConnection2(lpNetResource, PChar('123456'), PChar('BlackNote'), 0);
if ( myres = NO_ERROR ) then
begin
Result:= true;
EnumShares(lpNetResource.lpRemoteName);
end
else
begin
Result:= false;
end;
end;
procedure SharesThread.EnumShares(RemoteName: PWChar);
var
p: PShareInfo502Array;
res, er, tr, resume, i: DWORD;
begin
repeat
res:=NetShareEnum(RemoteName, 502, #p, MAX_PREFERRED_LENGTH, #er, #tr, #resume);
if (res = ERROR_SUCCESS) or (res = ERROR_MORE_DATA) then
begin
for i:=1 to Pred(er) do
begin
Form1.Memo1.Lines.Add(String(p^[i].shi502_netname));
end;
NetApiBufferFree(p);
end;
until (res <> ERROR_MORE_DATA);
end;
procedure SharesThread.Execute;
begin
if Authenticate then
Form1.Memo1.Lines.Add(IPAddress + '=' + 'Listed shares above')
end;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
HostFile:= TStringList.Create;
HostFile.LoadFromFile('Hosts.txt');
iHost:= 0;
StartThreads;
end;
end.
I can post my IP address here to you try this project, but not sure if this is under the rules. Anyway, is something wrong with this code?
I think you have BAD multithreading issues.
First of all, check if the very API you use is thread-safe.
I did not found this particular information, but http://computer-programming-forum.com/82-mfc/8e7756aee43ed65a.htm
Maybe you just can not call that function from different threads at the same time.
Second: all hundreds of your threads do Form1.Memo1.Lines.Add(String(p^[i].shi502_netname)) - that is VERY wrong.
You CAN NOT access GUI objects from threads. CAN NOT. Period.
See Delphi 7 Occasional deadlock changing TLabel.Font.Style from IdHTTPListener event for example.
The very process of loading form from DFM-resource, initializing it, creating Windows and Delphi objects and binding them, together is complex.
When at the same time hundreds of threads are crashing into half-created from and updating half-created MEMO they literally do destroy actions of one another.
Basically you told us that Windows did not returned you all the shares - but what you mean is that half-created TMemo abused by hundreds of threads does not show you all the shares. That is not the same, that might mean Windows work badly, but it also might mean Windows works ok, but you fail to put all the results into VCL GUI. You have to ensure what exactly happened.
Try getting shares
1.1 only in one single thread!
1.2 and that should be MAIN thread, not extra ones.
1.3 and you only should start it after the form is created - for example from some button click event.
And check if there is the difference.
You should not add data by one line to the memo - it is VERY slow.
Make a simple test.
uses Hourglass; // http://www.deltics.co.nz/blog/posts/tag/delticshourglass
const cMax = 10000;
procedure TForm1.Button1Click( Sender: TObject );
var sl: TStrings; i: integer; t: cardinal;
begin
HourglassOn();
t := GetTickCount();
sl := TStringList.Create;
try
for i := 1 to cMax do
sl.Add(IntToStr(i));
Memo1.Lines.Clear;
Memo1.Lines.AddStrings(sl);
finally
sl.Destroy;
end;
t := GetTickCount - t;
ShowMessage('It took ' + IntoToStr(t) + '/1000 seconds');
end;
procedure TForm2.Button1Click( Sender: TObject );
var i: integer; t: cardinal;
begin
HourglassOn();
t := GetTickCount();
Memo1.Lines.Clear;
for i := 1 to cMax do begin
Memo1.Lines.Add(IntToStr(i));
// giving Windows chance to repaint the memo
// simulating access from extra threads
// when main thread is free to repaint forms time and again
Application.ProcessMessages;
end;
t := GetTickCount - t;
ShowMessage('It took ' + IntoToStr(t) + '/1000 seconds');
end;
So a little draft to test your issues might be like this
http://docwiki.embarcadero.com/Libraries/XE2/en/System.IOUtils.TFile.ReadAllLines
http://www.thedelphigeek.com/2010/06/omnithreadlibrary-20-sneak-preview-1.html
http://www.thedelphigeek.com/2010/11/multistage-processes-with.html
http://otl.17slon.com/book/chap04.html#highlevel-pipeline
Just a draft for you to look into generic approach
const WM_EnumEnded = WM_USER + 1;
type TFrom1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
....
public
var Enums: iOmniBlockingCollection;
procedure StartEnum( const SingleThread: boolean );
procedure ShowResults( var m: TMessage); message WM_EnumEnded;
.....
procedure TFrom1.StartEnum( const SingleThread: boolean );
var hosts: TStringDynArray; // TArray<string>, array of string....
Source: IOmniBlockingCollection;
iWorker: IOmniParallelLoop<T>; // variable only needed for if-then-else
begin
hosts := TFile.ReadAllLines('hosts.txt');
Self.Enums := TOmniBlockingCollection.Create; // Results accumulator
Source := TOmniBlockingCollection.Create;
Source.Add( TOmniValue.FromArray<string>(hosts) );
iWorker := Parallel.ForEach<string>( Source ).NoWait().OnStop(
procedure begin PostMessage( Self.Handle, WM_EnumEnded, 0, 0) end
);
if SingleThread then iWorker := iWorker.NumTasks(1);
iWorker.Execute(
procedure(const value: String)
var i: integer;
begin
....
res:=NetShareEnum(RemoteName, 502 { 503 better ?? } ... );
....
Self.Enums.Add( TOmniValue(String(p^[i].shi502_netname)) );
...
end;
);
end;
procedure TFrom1.ShowResults( var m: TMessage );
var sa: TArray<String>;
begin
Self.Enums.CompleteAdding;
sa := TOmniblockingCollection.ToArray<string>( Self.Enums );
Memo1.Clear;
Memo1.Lines.AddStrings( sa );
end;
procedure TFrom1.Button1Click(sender: Tobject);
begin
StartEnum( True );
end;
procedure TFrom1.Button2Click(sender: Tobject);
begin
StartEnum( False );
end;

Delphi XE5 string size exchange limitation between DLL and EXE

I have a Delphi 7 DLL function that returns large string and it works fine but in Delphi XE5 I get an access violation after a specific size.
I have written a sample demo, that reflects my actual code, that generates also a AV in Delphi XE5 that returns also a large string but again after a specific size, I get an Access Violation ?
13000 lines of 20 chars, it works fine but with 14000 lines it crashes.
I did some tests with Delphi 7 and it works fine also.
What am I doing wrong ? Can anyone help me out ?
Thanks.
Here is the code of my DLL :
function RetLargeStr(Buffer : pAnsiChar; var BufferSize: Integer) : boolean ; stdcall;
var l_ansiStr : string;
loop : integer;
begin
Result := False;
//13000 ok 14000+ fail ???
for loop := 1 to 15000 do
begin
l_AnsiStr := l_AnsiStr + 'String of 20 chars' + Char($0D) + Char($0A) ;
end;
if Assigned(Buffer) and (BufferSize >= Length(l_ansiStr) + 1) then
begin
//Buffer := pAnsiChar(AnsiString(l_AnsiStr));
move(l_AnsiStr, Buffer^, length(l_AnsiStr) + 1);
Result := True;
end;
//Return actual size of output string.
BufferSize := Length(l_AnsiStr) + 1;
end ;
Here’s the call from my EXE :
procedure TForm1.Button7Click(Sender: TObject);
var l_StrOut : pAnsiChar;
l_Str : ansistring;
p_Size : integer;
begin
p_Size := 600000;
SetLength(l_Str, p_Size);
l_strout := pAnsiChar(l_str);
Memo2.Lines.Clear;
if RetLargeStr(l_StrOut, p_Size)
then Memo2.Lines.Add( l_StrOut );
end;
The way you have it here it's probably just luck that it works at all.
In the DLL, when you do this:
Buffer := pAnsiChar(AnsiString(l_AnsiStr));
you are actually returning the string buffer allocated in the DLL to the calling EXE, even though you've explicitly allocated a receive buffer before the call. That receive buffer pointer gets overwritten.
The crash most likely occurs because the heap manager in the EXE is unprepared for freeing a memory block, which was allocated somewhere else (in the DLL).
Instead of assigning to buffer, you might try copying the content of the string to it, like this:
if Assigned(Buffer) and (BufferSize >= Length(l_ansiStr) + 1) then
begin
move(AnsiStr[1], Buffer^, length(AnsiStr) + 1));
Result := True;
end;
Test code (DLL):
library Project2;
uses
SysUtils,
Classes;
function RetLargeStr(Buffer : pAnsiChar; var BufferSize: Integer) : boolean ; stdcall;
var l_ansiStr : string;
loop : integer;
begin
Result := False;
//13000 ok 14000+ fail ???
for loop := 1 to 15000 do
begin
l_AnsiStr := l_AnsiStr + 'String of 20 chars' + Char($0D) + Char($0A) ;
end;
if Assigned(Buffer) and (BufferSize >= Length(l_ansiStr) + 1) then
begin
//Buffer := pAnsiChar(AnsiString(l_AnsiStr));
move(l_AnsiStr[1], Buffer^, length(l_AnsiStr) + 1);
Result := True;
end;
//Return actual size of output string.
BufferSize := Length(l_AnsiStr) + 1;
end ;
exports
RetLargeStr;
begin
end.
Test code (EXE):
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
function RetLargeStr(Buffer : pAnsiChar; var BufferSize: Integer) : boolean ; stdcall; external 'project2.dll';
procedure TForm3.Button1Click(Sender: TObject);
var l_StrOut : pAnsiChar;
l_Str : ansistring;
p_Size : integer;
begin
p_Size := 600000;
SetLength(l_Str, p_Size);
l_strout := pAnsiChar(l_str);
Memo1.Lines.Clear;
if RetLargeStr(l_StrOut, p_Size)
then Memo1.Lines.Add( l_StrOut );
end;
end.

Resources