My Delphi application has a form that uses the Acrobat Reader ActiveX control for viewing pdfs. When I use the control's functions (LoadFile, gotoNextPage, gotoPreviousPage, gotoFirstPage, gotoLastPage), then close the form, I get the following error: "Access violation at address 6AF5703C. Read of address 6AF5703C". When I run the app, but do not use the control's functions, and then close the form, the app will exit without error.
Anyone know of a fix or workaround for this issue?
My app is written using Delphi 5 (legacy app). I have Adobe Acrobat Reader DC v15.016.20045 installed.
As I said in a comment to Zam, with the current version downloaded today of Acrobat Reader DC , I get the exact same error as you.
Please try this code and let us know whether it avoids the error for you, because it certainly works for me and there is no AV, either in the FormClose or afterwards.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
Ref : Integer;
begin
Ref := AcroPdf1.ControlInterface._AddRef;
AcroPdf1.Src := '';
AcroPdf1.Free;
AcroPdf1 := Nil;
end;
This is my FormCreate, which contains my only other code.
procedure TForm1.FormCreate(Sender: TObject);
begin
AFileName := 'd:\aaad7\pdf\printed.pdf';
AcroPdf1.src := AFileName;
AcroPdf1.setZoom(200); // <- this line is to exercise the
// ControlInterface to provoke the AV on shutdown
end;
I have absolutely no idea why my FormClose avoids the AV problem, and before anybody else says so, yes, it looks mad to me, too! Hardly something that deserves the name "solution", but maybe it will suggest a proper solution to someone who knows more about COM and Ole controls than I do.
I originally included the Ref := AcroPdf1._AddRef just as an experiment. I noticed that after it, Ref's value was 9. After AcroPdf1.Src := '', calling AcroPdf1._Release in the debugger evaluator returned a value of 4. I was about to see if the AV was avoided by forcing the RefCount down by repeatedly calling _Release but then Presto!, there was no AV after my first trace into FormClose exited.
Update: I have not tested the following exhaustively, but this simplified FormClose also avoids the AV, on my system at any rate:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
Ref : Integer;
begin
Ref := AcroPdf1.ControlInterface._AddRef;
end;
Obviously, omitting the assignment to Ref shouldn't make any difference.
I'm using Delphi 10 Seattle on 64-bit Win10, btw.
The better solution is to edit the TPDF Object in "AcroPDFLib_Tlb.pas"
Just add the proper destructor to the Code to free the OLE Object:
Declaration
Type
TAcroPDF = class(TOleControl)
...
public
destructor Destroy; override; // <- New Line
...
end;
Implementation
destructor TAcroPDF.Destroy;
begin
FIntf := NIL;
inherited;
end;
Related
I stumbled upon a bug in Delphi 10 Seattle Update 1. Lets take the following code :
procedure TForm1.Button1Click(Sender: TObject);
begin
//----------We crash here----------------
FList.Items[0] := SplitString('H:E', ':');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FList := TList<TStringDynArray>.Create;
FList.Add(SplitString('H:E', ':'));
FList.Items[0] := SplitString('H:E', ':');
end;
At first glance, it would appear that TList<T> doesn't properly manage the lifetime of the dynamic arrays it contains, but then again, it works just fine if compiled in 64 bits, it only crash in 32 bits (I understand it doesn't mean the bug isn't present in 64 bits...).
Note that SplitString was used because if was the first function returning a dynamic array that came to my mind. The original problem was encountered with TList<TBookmark> which exhibits the same problem.
It is possible to work around the bug rewriting the procedure Button1Click like this :
procedure TForm1.Button1Click(Sender: TObject);
var MyArray : TStringDynArray;
begin
MyArray := FList.Items[0];
FList.Items[0] := SplitString('H:E', ':');
//----------Yeah! We don't crash anymore!-----------
end;
But going around all my applications modifying them to work around this bug would not really be my prefered option. I'd much prefer find the offending routine and patch it in-memory if possible.
If anyone encountered this problem and found a workaround, I'd be grateful. Otherwise, I'll post mine when/if I find a proper workaround.
Also, please comment if the problem is still present in Berlin.
After all, the bug was still there in 64 bits. It didn't crash for TStringDynArray, but it did for other dynamic array types.
The source of the problem is found in the following code in Generics.Collections :
procedure TListHelper.DoSetItemDynArray(const Value; AIndex: Integer);
type
PBytes = ^TBytes;
var
OldItem: Pointer;
begin
OldItem := nil;
try
CheckItemRangeInline(AIndex);
TBytes(OldItem) := PBytes(FItems^)[AIndex];
PBytes(FItems^)[AIndex] := TBytes(Value);
FNotify(OldItem, cnRemoved);
FNotify(Value, cnAdded);
finally
DynArrayClear(OldItem, FTypeInfo); //Bug is here.
end;
end;
What happen is, the wrong TypeInfo is passed to DynArrayClear. In the case of a TList<TStringDynArray>, TypeInfo(TArray<TStringDynArray>) is passed instead of TypeInfo(TStringDynArray). From what I can tell, the proper call is:
DynArrayClear(OldItem, pDynArrayTypeInfo(NativeInt(FTypeInfo) + pDynArrayTypeInfo(FTypeInfo).Name).elType^);
The procedure being private makes it complicated to intercept. I did so using the fact that record helper can still access the private section of records in Delphi 10. I guess it will be more complicated for Berlin's users.
function TMyHelper.GetDoSetItemDynArrayAddr: TDoSetItemDynArrayProc;
begin
Result := Self.DoSetItemDynArray;
end;
Hopefully, Embarcadero will fix it someday...
This follows on from my answer to this Q:
Can I change the display format for strings in the watch list?
It turns out that at some point between D7 and XE3, the implementation of the IDE's Watch Window changed from using a TListView to a TVirtualStringTree.
Although I posted an update to my answer that works with XE4 by ignoring the VST and getting the watch value from the clipboard, I'd still like to be able to get the watch value from the VST if I can. I think I know how to do that
once I have a reference to the VST but the problem is that my attempt to get one fails.
Following is an MCVE of the code I'm using in my custom package. Hopefully, what it does is self-explanatory. The problem is that the code in the block
if WatchWindow.Components[i] is TVirtualStringTree then begin
[...]
end;
never executes, DESPITE the classname "TVirtualStringTree" appearing in Memo1. Obviously the component with that classname fails the "is" test. I'm guessing that the reason is that the TVirtualTreeView compiled into the IDE is a different version that the one I'm using, v.5.3.0, which is the nearest predecessor I could find to XE4.
So, my question is, is that the likely explanation, and is there anything I can do about it? I suspect if someone can flourish the version of TVirtualStringTree that was used for XE4 from a hat, that might solve my problem.
type
TOtaMenuForm = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
WatchWindow : TForm;
VST : TVirtualStringTree;
end;
procedure TOtaMenuForm.FormCreate(Sender: TObject);
var
i : Integer;
S : String;
begin
WatchWindow := Nil;
VST := Nil;
// Iterate the IDE's forms to find the Watch Window
for i := 0 to Screen.FormCount - 1 do begin
S := Screen.Forms[i].Name;
if CompareText(S, 'WatchWindow') = 0 then begin
WatchWindow := Screen.Forms[i];
Break;
end;
end;
Assert(WatchWindow <> Nil);
if WatchWindow <> Nil then begin
Memo1.Lines.Add('Looking for VST');
for i := 0 to WatchWindow.ComponentCount - 1 do begin
Memo1.Lines.Add(IntToStr(i) + ':' + WatchWindow.Components[i].ClassName);
if WatchWindow.Components[i] is TVirtualStringTree then begin
VST := TVirtualStringTree(WatchWindow.Components[i]);
Memo1.Lines.Add('found VST');
Break;
end;
end;
if VST = Nil then
Memo1.Lines.Add('VST not found');
end;
end;
Btw, I realise that solutions that depend of implementational details of IDE are likely to be fragile, but this is just for amusement (I liked the challenge of getting string data out of a component that goes out of its way to avoid storing any).
May be You can try to use only published properties of embedded into IDE TVirtualStringTree implementation through RTTI methods to do what you want?
I have written a code with Delphi 2009 and updated my CodeGear Delphi to XE2. It compiled perfectly with Delphi 2009, but now it doesn't ! It gives me this error instead :
[DCC Error] Incompatible types: 'TFormStyle' and 'TTeeFontStyle'!
I tried creating a new Vcl Forms Application and wrote the command that generates this error :
Form1.FormStyle := FsNormal;
and it compiled perfectly too,I don't know why is this happening, although I believe there's nothing wrong with my syntax, please help, thanks.
This is the code that is not compiling :
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
var Handled: Boolean);
begin
begin
KeyPreview := True;
case Msg.message of
WM_KEYDOWN:
if Msg.wParam = 27 then
begin
form1.Menu:=mainmenu1;
fullscreen1.Checked:=false;
form1.formstyle:=fsnormal;
form1.BorderStyle:=bssizeable;
end
else
if msg.wParam=VK_f5 then
begin
browser.Navigate(memo2.Text);
end;
end;
end;
end;
There is name conflict with some TeeChart module, which is in "use" clause. You can write full-qualified identificator name to resolve this problem:
formstyle := Vcl.Forms.fsnormal;
P.S. Note that I deleted "form1." qualifier also. Normally it is not very useful in the form method body, and sometimes even harmful (imagine that you have multiple instances of TForm1)
In addition to the answer of MBo, I think it is better to use:
Self.formstyle := Vcl.Forms.fsnormal;
When you have multiple instances of TForm1, this will always adjust the instance you are using at that moment.
Qualify the value with the particular enum type that it comes from:
Form1.FormStyle := TFormStyle.fsNormal;
Or even:
Form1.FormStyle := Vcl.Forms.TFormStyle.fsNormal;
I have created a procedure in a dll that opens a form and then prints a report.
This procedure works perfectly from an exe.
I have wrapped the unit that contains this procedure and forms in a dll and exported the procedure as follows:
{$R *.res}
Procedure PrintTopSellers; stdcall;
begin
Form1 := TForm1.create(nil);
GetMonth := TGetMonth.create(nil);
Form1.PrintTopSellers;
end;
exports PrintTopSellers;
begin
end.
Now I call this procedure PrintTopSellers from an exe as follows:
procedure TForm1.Button5Click(Sender: TObject);
type
TRead_iButton = function :integer;
var
DLL_Handle: THandle;
Read_iButton: TRead_iButton;
Begin
DLL_Handle := LoadLibrary('c:\Catalog.dll');
if DLL_Handle <> 0 then
begin
#Read_iButton:= GetProcAddress(DLL_Handle, 'PrintTopSellers');
Read_iButton;
end;
application.ProcessMessages;
FreeLibrary(DLL_Handle);
end;
The call to the procedure works perfectly. However, after I close the calling exe, I get an access violation - "Access violation at address 00BAC89C. Read of address 00BAC89C."
Appreciate any assistance. I am using Delphi 7.
Thanks
You are creating Form1, a windowed control, in the DLL. But you never destroy it. Then you unload the DLL which unloads the code that implements the window procedures for all windows created by the DLL. Presumably when the process shuts down, the window procedures are called, but there is no code there anymore.
Fix the problem by destroying all objects that the DLL creates. It looks to me like the best approach is to do that when PrintTopSellers terminates.
Procedure PrintTopSellers; stdcall;
begin
Form1 := TForm1.create(nil);
try
GetMonth := TGetMonth.create(nil);
try
Form1.PrintTopSellers;
finally
GetMonth.Free;
end;
finally
Form1.Free;
end;
end;
In the code that loads the DLL, TRead_iButton is declared incorrectly. It should be
TRead_iButton = procedure; stdcall;
But that doesn't actually explain the problem here since the signature mismatch is benign for a parameterless procedure.
"TRead_iButton = function: integer; register;"
"Procedure PrintTopSellers; stdcall;"
Absolutely different conventions/types, ain't them ?
Make them the same.
And better ditch DLL and use packages (BPL), then compiler would make you safe from such errors
We also don't see the code neither in Form1.PrintTopSellers nor in TGetMonth. The all can leave some dangling pointers in the host exe, that would get accesses after DLL unloaded.
Show exactly chain of function calls leading to AV - it is called stack trace.
Debug info + some excaption interrupt like Jedi CodeLibrary (used by Delphi IDE) madExcept, EurekaLog, synopse log and a lot of other exist.
Display the call stack in a Delphi Win32 application
Does DLL or EXE use Runtime packages ?
To detect and prevent shutdown the computer I use very simple program. It has only one form and one private procedure like below:
TForm3 = class(TForm)
private
procedure WMQueryEndSession(var Msg : TWMQueryEndSession) ;
message WM_QueryEndSession;
end;
and the implementation
procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
Msg.Result := 0; //so I don't want to shutdown while my program is running
end;
I compiled it Delphi 5 and Delphi 2010. Both of them detect shutdown. But when I compiled in Delphi 2010; after preventing shutdown my program closes. (PC doesn't shutdown)
How do I get the same result from both of them?
EDIT: changed to intercept WM_ENDSESSION instead of WM_QUERYENDSESSION.
As you cannot directly change the behaviour of TApplication, you can install a TApplication message hook instead that neutralizes the WM_ENDSESSION message.
Installing such a hook is quite simple, you only have to add a method similar to the following to your mainform and register the hook in FormCreate.
function TForm25.HookEndSession(var Message: TMessage): Boolean;
begin
result := false;
if Message.Msg = WM_ENDSESSION then begin
Message.Result := 0;
result := true;
end;
end;
procedure TForm25.FormCreate(Sender: TObject);
begin
Application.HookMainWindow(HookEndSession);
end;
I usually run "shutdown -a" command. You can do the same from your code to interrupt Windows from shutdown.
Regards
This looks like a bug in Delphi. I suggest you to post this on Quality Central.
Edit: Here's an approach that doesn't work. Thanks
Procedure TMyForm.FormClose(Sender: TObject; Var Action: TCloseAction);
Begin
Action := caNone; //The form is not allowed to close, so nothing happens.
End; // Note: the OP says he tried this, doesn't help. See the comments.
Are you testing on the same OS? There are some application shutdown changes in Vista. Read this: Application Shutdown Changes in Windows Vista
If you are testing on the same OS, maybe Delphi 2010 handles WM_ENDSESSION messages in a different way. In Delphi 7, WM_ENDSESSION message are handled in Application.WndProc.
In all versions should you not be using the FormCloseQuery event?
procedure TForm3.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
Canclose := Not StillDoingImportantStuff;
end;
Oops - just read comments to "this does not work" :( Is win 7 different?
In all my apps this gets called if windows is trying to shut down...
ShutdownGuard is built with Delphi and it's open source, you can download it tweak it for your needs