Using the DelphiTwain files from http://delphitwain.sourceforge.net/ and am getting some weird behavior.
After each scan a little more memory is being held onto.
After an hour or so of repetitive scans, the image scanned is zoomed in approxamately 10 times, and just the upper-left square inch is stored.
Has anyone had similar issues, or have some suggestions?
Code below...
try
try
Twain := TDelphiTwain.Create(self);
Twain.OnTwainAcquire := TwainAcquireHandler; //manually set the event handler
Twain.OnSourceFileTransfer := TwainSourceFileTransfer;
Twain.OnSourceSetupFileXfer := TwainSourceSetupFileXfer;
Twain.LoadLibrary;
Twain.LoadSourceManager;
Twain.Source[0].Loaded := TRUE;
Twain.Source[0].TransferMode := ttmFile;
Twain.Source[0].EnableSource(false, false);
except on e : exception do
showmessage('Error loading Scanner.');
end;
try
while Twain.Source[0].Enabled do
Application.ProcessMessages;
except on e : exception do
showmessage('Error Scanning Packing List.');
end;
finally
Twain.Source[0].Loaded := FALSE;
Twain.UnloadSourceManager(true);
Twain.UnloadLibrary;
Twain.Destroy;
end;
Since the TDelphiTwain appears to be a component you are creating in code, I would recommend passing in nil for the constructor and calling the .Free method or (as suggested by Joseph) FreeAndNil.
Twain := TDelphiTwain.Create(nil);
try
try
Twain.OnTwainAcquire := TwainAcquireHandler; //manually set the event handler
Twain.OnSourceFileTransfer := TwainSourceFileTransfer;
Twain.OnSourceSetupFileXfer := TwainSourceSetupFileXfer;
Twain.LoadLibrary();
Twain.LoadSourceManager();
Twain.Source[0].Loaded := True;
Twain.Source[0].TransferMode := ttmFile;
Twain.Source[0].EnableSource(False, False);
except on e : exception do
showmessage('Error loading Scanner.');
end;
try
while Twain.Source[0].Enabled do
Application.ProcessMessages;
except on e : exception do
showmessage('Error Scanning Packing List.');
end;
Twain.Source[0].Loaded := False;
Twain.UnloadSourceManager(True);
Twain.UnloadLibrary();
finally
FreeAndNil(Twain);
end;
I would also recommend better exception handling, but not related to question you asked. The only thing users will see and report to you (or worse, the quiet guy in the corner responsible for your IT support who loves to get non-descriptive errors from users) is 'Error doing something'
Good luck
Another area to look at is if the scanner supports WIA (Windows Image Acquisition)
var
DevMgr: IDeviceManager;
Scanner: Idevice;
Picture: IItem;
Image: OleVariant;
AImage: IImageFile;
begin
DevMgr := CreateOleObject('WIA.DeviceManager') as IDeviceManager;
// Figure out which device is the scanner
Scanner:= DevMgr.DeviceInfos.Item[1].Connect;
//Command: Figure out which command scans..
Picture := Scanner.ExecuteCommand(Scanner.Commands.Item[1].CommandID);
//Transfer as JPG
Image := Picture.Transfer(Picture.Formats.Item[1]);
//Save the image
AImage := IImageFile(Image);
AImage.SaveFile('c:\wia_viaScanner\image.' + AImage.FileExtension);
end;
More info on the WIA library can be found here..
http://msdn.microsoft.com/en-us/library/ms629859(VS.85).aspx
Examining the code within these calls may be fruitful:
TwainAcquireHandler;
TwainSourceFileTransfer;
TwainSourceSetupFileXfer;
Do any of those create any objects without freeing them?
If you are using Delphi 2006 or higher, then you can add this line to your .DPR file:
ReportMemoryLeaksOnShutdown := True;
Then reproduce the memory leak, close your app... and it will describe the leaks in detail. A little more info about this can be found here.
On another note, I'd suggest replacing
Twain.Destroy;
with
FreeAndNil(Twain);
.Destroy will call the destructor directly, while FreeAndNil is a safer alternative that will also prevent the "Twain" variable from pointing anywhere dangerous. (See the accepted answer to this question).
I can't address the problem you're reporting but you have a busy loop there that will gobble CPU time.
What are you doing when you get the Image, did you keep in memory?
Or the library can have some memory leaks, you can check if it is true with FastMM4.
to KevinRF:
I need to use WIA automation in Delphi 7 project. I registered WIAAut.dll in my system, import this library into Delphi and past your programm code into my project and got some errors:
Scanner:= DevMgr.DeviceInfos.Item[1].Connect;
Types of actual and formal var parameters must be identical
in "Item" must be Item[var Index: OleVariant], but "1" is integer
What's wrong, what i need to made it works?
Related
I have been asked to correct an issue (not related to this question) in a legacy Delphi program. After fixing some issues with missing components, I am now stuck with some GDI Plus functionality, which stops me from compiling the program. One of the functions where this is used is:
function TDownLoadItem.LoadRawBitmapFromStream(var bm: TBitmap): Boolean;
var
image: TGPBitmap;
begin
Result := False;
if Content.Size = 0 then
exit;
// NOTE: Content is a TMemoryStream, declared globally.
image := GDIPlusHelper.LoadBitmapFromStream(Content); // <== This is where the problem is....
try
bm.Width := image.GetWidth;
bm.Height := image.GetHeight;
with TGPGraphics.Create(bm.Canvas.Handle) do
try
DrawImage(image, 0, 0, image.GetWidth, image.GetHeight);
Result := True;
finally
Free;
end;
finally
image.Free;
end;
end;
I think (not sure) the last Delphi version used was 2006, I am on Delphi Rio 10.3.
Online I have managed to find GDI+ 1.2, but this does not solve the problem. The procedure LoadBitmapFromStream does not exit in these libraries. GDIPlusHelper was apparently renamed to GDIPlusHelpers and most code has changed from classes to interfaces. I suspect an older edition of the GDI Plus libraries were used, but I cannot find these.
Reworking the code would be too complex as it would require Content to be an IStream instead of a TMemoryStream. Also, simply using a TBitmap is not feasible either as other code (not shown) uses functionality specific to TGPBitmap (e.g. RotateFlip).
Any suggestions on how to fix/work around this? Thanks in advance!
For this question please refer to a previous question I asked a while back: Do I need TThreads? If so can I pause, resume and stop them? LU RD answered the question with a provided demo and some comments.
I stopped using Delphi for quite a long time but now I am getting back into it and redoing a project. This project has time consuming operations such as opening a Gif, extracting the frames and then adding those frames (bitmaps) into a TImageList and TListView. This time I actually add the bitmaps directly into a TObjectList, as seen here: How to add and retrieve Bitmaps to and from a TList?
Typically nothing special needs doing here to speed it up as most Gif animations are small, but with medium to large Gifs the application can hang. This is going to get worse though as those bitmaps are going to be modified at runtime using various imaging filters such as grayscale, change hue etc. So I am sure I need multi-threading for this otherwise accessing each bitmap and then manipulating is going to be very slow (as I found out before).
So with that said, I am foolishly trying to adapt (without a clue of what I am doing) some of my procedures to work with the TThread example posted by LU RD I linked to at the top.
I wish I spent a bit more time with the original question to ask for more information but I guess I got sidetracked and moved onto something else, which meant I learned nothing.
Take this snippet from the threading example:
const
cWorkLoopMax = 500;
function TForm1.HeavyWork: boolean; // True when ready
var
i, j: integer;
begin
j := 0;
for i := 0 to 10000000 do
Inc(j);
Inc(workLoopIx);
Result := (workLoopIx >= cWorkLoopMax);
end;
For a start I have no idea what cWorkLoopMax is for, and why its value is set to 500?
Secondly I guess the HeavyWork procedure is just a sample, which runs in a loop a 10000000 times whilst incrementing the j variable?
Then we have the workLoopIx which I am unsure what is for? Maybe something to do with the position within the thread maybe?
So, here I have my current code (no threading) which handles opening the Gif and adding to the TListView and TImageList. The procedures I use are in another unit, if needed I will post it also, but this is what I use inside a TAction (actOpen):
if OpenPictureDialog.Execute then
begin
Screen.Cursor := crHourGlass;
try
BitmapCollection.AddFromGif(OpenPictureDialog.FileName, ImageList1);
ListView1.Items.BeginUpdate;
try
ListView1.Items.Clear;
for I := 0 to BitmapCollection.BitmapList.Count - 1 do
begin
with ListView1.Items.Add do
begin
Caption := 'bitmap' + IntToStr(I+1);
ImageIndex := I;
end;
end;
finally
ListView1.Items.EndUpdate;
end;
finally
Screen.Cursor := crDefault;
end;
end;
What I dont understand is how to put that into a thread procedure, such as HeavyWork? I just created a new one called Job_Open and did this:
procedure TForm1.actOpenExecute(Sender: TObject);
begin
if OpenPictureDialog.Execute then
begin
if not Assigned(MyThread) then
begin
workLoopIx := 0;
btnStartTask.Enabled := false;
btnPauseResume.Enabled := true;
btnCancelTask.Enabled := true;
MyThread := TWorkerThread.Create(Self.Handle, WM_MyProgress, Job_Open);
end;
end;
end;
function TForm1.Job_Open: boolean;
var
I: Integer;
begin
BitmapCollection.AddFromGif(OpenPictureDialog.FileName, ImageList1);
for I := 0 to BitmapCollection.BitmapList.Count - 1 do
begin
with ListView1.Items.Add do
begin
Caption := 'bitmap' + IntToStr(I+1);
ImageIndex := I;
end;
Inc(workLoopIx);
end;
Result := (workLoopIx >= BitmapCollection.BitmapList.Count);// cWorkLoopMax);
end;
This is clearly not right, the performance is slower and I am getting all kind of errors such as Invalid Handle.
I would be extremely grateful if someone could take some time to explain my comments, what I am doing wrong and what I should be doing instead, updated code and comments in source are welcome but I am hoping to learn a bit more of what is going on with the code ideally.
In a perfect world if there is a library of sorts that exists out there that is easy to use then that would be a massive help if I cannot understand what is happening above. Is there such a library that can do something like:
procedure DoSomething;
begin
BeginThreading();
HeavyWork;
StopThreading();
end;
Thanks in advance, and apologies for the lengthy post.
DISCLAIMER: While my answer is not an answer directly to your question it is a psobile solution to your problem.
After reading your questions I have one question to you:
Are you applying same image graphical effects on all ImageList images?
If you are then I must say that you started on working on your problem from wrong approach.
First you need to know that Imagelist doesen't store all those images seperately but that it is storing them all in same verry wide image. So when you read any ImageList image internally ImageList creates output bitmap and then uses Canvas.CopyRect which is quite. When you save image to image list it internally uses Canvas.Draw.
So when you do this many times you create lots of unnecessary data movment.
So instead of your approach where you work on seperate images I recomend you work on ImageLists internal image whose handle you can get using ImageList.GetImageBitmap. This will alow you to apply same graphical effect on all ImageList images at once. And if you don't need to apply graphical effects to all ImageList images I bet you can modify your mage processing method to work only on parts of the image.
In order to learn more about ImageList I recomed you read its documentation:
Image list explanation: http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.Controls.TImageList
Image list GetImageBitmap explanation
http://docwiki.embarcadero.com/Libraries/XE6/en/Vcl.ImgList.TCustomImageList.GetImageBitmap
Our programming dept just spent about a non-mythical man-month tracking down what we think is a bug in a 3rd party component, here's their copyrighted source code:
function TGDIPPicture.GetImageSizes: boolean;
var
multi: TGPImage;
pstm: IStream;
hGlobal: THandle;
pcbWrite: Longint;
begin
result := false;
if Empty then
Exit;
if FDataStream.Size = 0 then
Exit;
hGlobal := GlobalAlloc(GMEM_MOVEABLE, FDataStream.Size);
if (hGlobal = 0) then
raise Exception.Create('Could not allocate memory for image');
try
pstm := nil;
// Create IStream* from global memory
CreateStreamOnHGlobal(hGlobal, TRUE, pstm);
pstm.Write(FDataStream.Memory, FDataStream.Size,#pcbWrite);
multi := TGPImage.Create(pstm);
FWidth := multi.GetWidth;
FHeight := multi.GetHeight;
Result := true;
multi.Free;
finally
GlobalFree(hGlobal);
end;
end;
We found the problem was with TMS's AdvOfficeTabSet. If we added tabs, then it crashed, if we didn't add tabs then it didn't crash. (the crash was one of those un-debuggable app hangs that hits you 10 steps after the real problem).
Following Raymond Chen's advice I replaced GMEM_MOVEABLE with GPTR and it appears to have fixed the problem.
I'm wondering if anyone can tell me if the above code had any legitimate reason for using GMEM_MOVEABLE. AFAIK it's only for the clipboard and it should always be used with GlobalAlloc.
while I was typing this another programmer got an error in the GlobalFree function using my code. So, apparently this doesn't work either. Could really use some help here!
*CreateStreamOnHGlobal is a Windows API function. (which apparently prefers GMEM_MOVEABLE)
*TGPImage is part of TMS's implementation of the GDI+ library.
Jonathan has identified the obvious problem, that being the double free of the HGLOBAL. But as you have found, the use is GMEM_MOVEABLE is correct.
Frankly, the code seems needlessly complex. I suggest you use the built in stream adapter and avoid any GlobalAlloc. To get an IStream you just need to do this:
pstm := TStreamAdapter.Create(FDataStream);
That's it.
I've run into the following problem:
My Delphi7 program runs smoothly on most computers running WinXP/Vista/7 BUT on some older Windows XP installs (only a few) I'm getting the following problem:
I have a system image list, and I'm adding my own icons to a copy of the system image list. Upon adding my icons I get an "Invalid image size." EInvalidOperation error.
Here is the code in question:
function GetSystemLargeIconsList: TCustomImageList;
// This gets the system image list.
var
SysIL: HImageList;
SFI: TSHFileInfo;
MyImages: TCustomImageList;
begin
SysIL := SHGetFileInfo('', 0, SFI, SizeOf(SFI),
SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
if SysIL <> 0 then begin
MyImages:=TCustomImageList.Create(nil);
// Assign the system list to the component
MyImages.Handle := SysIL;
// The following prevents the image list handle from being
// destroyed when the component is.
MyImages.ShareImages := TRUE;
Result:=MyImages;
end;
end;
var
DocumentImgList: TCustomImageList;
IconToAdd: TIcon;
begin
DocumentImgList:=GetSystemLargeIconsList;
Documents.LargeImages:=DocumentImgList;
Documents.SmallImages:=DocumentImgList;
IconToAdd:=TIcon.Create;
DocumentListIcons.GetIcon(0, IconToAdd);
DocumentImgList.AddIcon(IconToAdd); ----> this is the line of the exception
To make the problem worse, I'm using the TPngImageList component, but according to the code, it just seems to call the standard Delphi function:
if TObject(Self) is TPngImageList
then if Image = nil
...
else begin
Patch := FindMethodPatch('AddIcon');
if Patch <> nil
then begin
Patch.BeginInvokeOldMethod;
try
Result := TCustomImageList(Self).AddIcon(Image); ----> this is where the exception happens
finally
Patch.FinishInvokeOldMethod;
end;
end
else Result := -1;
end;
I've recently found out that on one of the computers that have this problem, either uxtheme.dll or explorer.exe has been patched with some Windows-skinning program.
So I suppose that somebody or a program is hacking the system image list in a way that is making my Delphi program crash.
Any ideas on how to fix this?
Thanks!
One thing you could try would be to load your icon into a separate tBitmap, then resize it before adding it into the image list.
We have a program made in Borland Delphi that uses Word automation to create documents.
On an installation (terminal server) we are only able to get the Word automation to work when running as local administrator.
When runnnig as anoter user we get an error message "Opdracht mislukt -2146824090" (its dutch version of Office), wich I guess is translated to "Operation failed" or "Command failed".
The user has read/write access to the folder where the program try to put the new document.
Office 2010
64bits Windows server 2008 R2 standard
The applicaion is 32bit windows application.
If I add a delay (500ms) after the word.application is created, everything works as normall.
WordApp := CreateOleObject('Word.Application');
sleep(500);
Doc := WordApp.documents.Open(sFile,EmptyParam,true);
Anybody knows why the CreateOleObject command now returns before the Word application can be used?
If you want to track out that, you could use a tool like ProcessMonitor to trace the Word automation executions till the point which you can use the app.
Seems some kind of rights check is taking place - but half a second seems too much time just for this.
You could try to open the Document a few times, or is Word totally borked after it gave the error?
WordApp := CreateOleObject('Word.Application');
while True do
begin
try
Doc := WordApp.documents.Open(sFile,EmptyParam,true);
Break;
except
on E: EOleSysError do
begin
// raise error if it's not the expected "Command failed" error
if E.ErrorCode <> -2146824090 then
raise;
end;
end;
end;
Edit:
Please see my answer here which provides a better solution and an explanation why this happens.
The administrator account working wihtout delay, seems not to have anything with rights to do, but that Word happens to start much faster with this account than the normal domain user accounts.
I can live with the delay workaround, but if anyone knows a better way please let me know.
I realize this thread is quite old, but I solved this issue by making sure to close the document before quitting (oleDocument.Close). By doing so there is no need for any type of delays, etc. See Delphi code snippet below.
Example:
oleWord := Unassigned;
oleDocument := Unassigned;
Screen.Cursor := crHourGlass;
try
oleWord := CreateOleObject('Word.Application');
oleWord.Visible := False;
oleWord.DisplayAlerts := False;
oleDocument := oleWord.Documents.Open(Worklist.Filename);
oleDocument.SaveAs(Worklist.Filename, wdFormatDOSTextLineBreaks);
oleDocument.Close;
oleWord.Quit(False);
finally
oleDocument := Unassigned;
oleWord := Unassigned;
Screen.Cursor := crDefault;
end;