Delphi: Access violation after calling procedure in dll
Asked Answered
P

2

5

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

Pare answered 20/9, 2012 at 10:31 Comment(3)
Install stack tracer like Exception Dialog from Jedi CodeLibrary (used by Delphi IDE), Eureka, madExcept, etc... Run with debug-info. Check stacktrace of the exception to understand where the train went down the track.Grozny
stackoverflow.com/questions/258727Grozny
Did you alreay solve that issue? I have the some problem. But only on XP. On Win7 it runs fine. I just need to create and free the form in the dll, then that error occurres after the exe is closed. I am using DelphiXE.Taproom
O
6

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.

Optometrist answered 20/9, 2012 at 10:42 Comment(18)
we canot be sure what GetMoth is, but maybe more simple approach would be GetMonth := TGetMonth.create(Form1)Grozny
@Arioch'The That would also be a guess. Perhaps TGetMonth constructor receives something else.Optometrist
when the process shuts down, the window procedures are called By whom ? Can he fix it by calling TForm1.create(Application) ?Grozny
AFAIK the Application global variable does not pass over the DLL boundary, in a DLL the Application global variable is different to the one in the calling code, therefore you will find that the form still isn't freed. You have to free the form in the DLL, if form1 isn't needed after the call to printtopsellers then the correct approach is shown IMHO.Hanford
Wow I did not expect such a speedy response. I am implementing the suggested code now @David Heffernan. TGetMonth is a simple form that comes up with a combobox to select a month. Will give you feedback shortly.Pare
@Paul The DLL has its own instance of Application. When the DLL is unloaded, that instance is destroyed. Which means that objects that it owns are also destroyed. So the form will be freed.Optometrist
@David Heffernan I made the changes that you suggested, however, I still get an access violation after closing the calling exe, at a different address though - 4EC6F74EPare
I tried Form1 := TForm1.create(Application); but this gives me [Error] Catalog.dpr(39): Undeclared identifier: 'Application'Pare
Use the debugger to track it down. Remember that we can't see your code. Even better, add madExcept.Optometrist
Thanks for your assistance. I will try using the debugger. Don't know what madExcept is.Pare
Google then. I mentioned few stack tracers in the top comment. Google for them, install, make them have debuginfo they needed - and show the stack trace of your exception. Which exactly function chain calls the invalid AV address.Grozny
Thanks installing madExcept now.Pare
Unless this is the first time you've ever seen an "undeclared identifier" error, you already know what to do when you get it for Application. Add to your uses clause the unit where Application is declared. Both the documentation and experience should tell you it's the Forms unit.Redraft
@Pare at address 00BAC89C. Read of address 00BAC89C addresses are equal, that is important. at a different address though - 4EC6F74E - here u show only one address. in future u'd better show both. BTW that your second address looks strange according to In Win32, the default base address that an executable (in this case, NOT including DLLs) is loaded to is 0x400000. DLLs load at a base address of 0x1000,Grozny
@Arioch'The DLLs load wherever there is available space. They can load anywhere.Optometrist
@David that is true, yet the address seems very high for it. And it is not above 2GB eitherGrozny
@David, not if the DLL is opened by two application instances. I was simply pointing out better coding practice to support your answer :-)Hanford
@Paul No you are wrong. There's a one to one mapping between processes that load the DLL and instance's of the DLL's Application object. But your point about best practice is well made. It's better to control when these objects die rather than wait for unload.Optometrist
C
3

"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 ?

Callao answered 20/9, 2012 at 10:43 Comment(3)
True, but it doesn't matter for a parameterless procedure.Optometrist
The GetMonth form just shows with a combobox to select a month. Form1 shows GetMonth then uses the month to print a report.Pare
The use of dll's is preferred because ideally we would like to use dll's written in other languages in this app.Pare

© 2022 - 2024 — McMap. All rights reserved.