Issue with dynamically loaded/unloaded DacVcl package.

Discussion of open issues, suggestions and bugs regarding ODAC (Oracle Data Access Components) for Delphi, C++Builder, Lazarus (and FPC)
Post Reply
worm2
Posts: 2
Joined: Sat 04 Dec 2010 09:40

Issue with dynamically loaded/unloaded DacVcl package.

Post by worm2 » Wed 24 Mar 2021 15:02

Hello,

We build Win32 VCL Delphi application with packages, as well as DLLs using those packages.
The application dynamically loads/unloads DLLs, in turn, they may load/unload needed BPLs.
We faced an issue using ODAC this way:
1) DLL (loaded with LoadLibrary) loads DacVcl (for Delphi XE 10.3 it will be DacVcl260.bpl)
2) After that, DLL is unloaded with FreeLibrary (thus unloading DacVcl260.bpl)
3) Another DLL, or .EXE itself, tries to connect any Oracle Database with ODAC, but NOT USING DacVcl260.bpl (using non-visual packages odac260.bpl/dac260.bpl only).
4) Exception (AV)

Steps to reproduce

0. Consider Delphi XE 10.3.3 installed into standard directory (C:\Program Files (x86)\Embarcadero\Studio\20.0), target platform is 32-bit Windows VCL (the problem probably exists in all platforms)
1. Prepare 3 directories "bin", "lib" and "src", with 12 files in them:
bin\rtl260.bpl, bin\vcl260.bpl, bin\dbrtl260.bpl — standard 32-bit VCL packages
bin\dac260.bpl, bin\odac260.bpl, bin\dacvcl260.bpl — ODAC 32-bit BPLs, consider latest version 11.4.1
lib\dac260.dcp, lib\odac260.dcp, lib\dacvcl260.dcp — their corresponding (32-bit) DCPs
src\Test.dpr - source for Test.exe:

Code: Select all

{$APPTYPE CONSOLE}
program Test;

uses
  Windows,
  SysUtils,
  Classes,
  Ora;

const
  PKGVersion = '260'; // to be customized
  DacVclPkgName = 'DacVcl' + PKGVersion + '.bpl';

var
  hDLL, hBPL: THandle;
  session: TOraSession;
begin
  try
    Write('Loading TestDLL.dll ... ');
    hDLL := LoadLibrary('TestDLL.dll');
    if hDLL <> 0
    then WriteLn('SUCCESS')
    else begin
      WriteLn(' FAILED');
      Exit;
    end;
    hBPL := GetModuleHandle(DacVclPkgName);
    if hBPL <> 0
    then WriteLn('Address of ' + DacVclPkgName + ' = 0x' + IntToHex(UIntPtr(hBPL), 2*SizeOf(Pointer)))
    else WriteLn('WARNING: ' + DacVclPkgName + ' not loaded!');
    Write('Unloading TestDLL.dll ... ');
    if FreeLibrary(hDLL)
    then WriteLn('SUCCESS')
    else begin
      WriteLn('FAILED');
      Exit;
    end;
    Write('Connecting ... ');
    session := TOraSession.Create(nil);
    try
      session.Options.Direct := True;
      session.ConnectPrompt := False;
      // To be customized:
      session.Server := 'server:1521:orcl920';
      session.Username := 'user';
      session.Password := 'password';
      //
      session.Connect;
      WriteLn('SUCCESS');
      WriteLn('Disconnecting ... ');
      session.Disconnect;
      WriteLn('SUCCESS');
    finally
      FreeAndNil(session);
    end;
  except
    on E: Exception do
      WriteLn('Exception ' + E.ClassName + ': ' + E.Message);
  end;
end.
src\TestDLL.dpr - source for TestDLL.dll:

Code: Select all

library TestDLL;

uses
  DacVcl; // does nothing, just uses

end.
src\build.cmd - batch script for compiling:

Code: Select all

@echo off

:: To be customized:
set DELPHI_VER=260
set DELPHI_DIR=C:\Program Files (x86)\Embarcadero\Studio\20.0
set ODAC_DCP_DIR=%~dp0..\lib
set OUT_DIR=%~dp0..\bin

if NOT "%1"=="" goto proceed
echo Usage:
echo build.bat ^<file to compile^>
goto :eof

:proceed

set DCC="%DELPHI_DIR%\bin\dcc32.exe"
set LIB="%DELPHI_DIR%\lib\win32\release";"%ODAC_DCP_DIR%"
set PKG=rtl;vcl;dbrtl;dac%DELPHI_VER%;odac%DELPHI_VER%;dacvcl%DELPHI_VER%
set NS=System;WinApi

%DCC% -U%LIB% -LU%PKG% -NS%NS% -E%OUT_DIR% %1
2. Compile:

Code: Select all

src\build.cmd src\Test.dpr

Code: Select all

src\build.cmd src\TestDLL.dpr
(Test.exe and TestDLL.dll should appear in "bin" directory)

3. Run bin\Test.exe

4. Output:

Code: Select all

Loading TestDLL.dll ... SUCCESS
Address of DacVcl260.bpl = 0x01010000
Unloading TestDLL.dll ... SUCCESS
Connecting ... Exception EAccessViolation: Access violation at address 01013478 in module 'Test.exe'. Read of address 01013478
The exception address is always the same relative to DacVcl260.bpl base address (offset 0x00003478)
If DEP is enabled for Test.exe we'll get "Execution of address" in message instead of "Read of address".

The cause of the problem is in DacVcl package, namely, in DacGui.inc:

Code: Select all

initialization
....
{$IFDEF FMX}
  if not Assigned(StartWaitProc) then
{$ENDIF}
    StartWaitProc := StartWait;
....
finalization
....
  // StartWaitProc (along with some other vars), is NOT deinitialized!
end.
When DacVcl260.bpl is unloaded, StartWaitProc (global variable defined in another module, odac260.bpl) still points to non-existent address @DacVcl260.StartWait. The consecutive use (unit MemData.pas):

Code: Select all

procedure StartWait;
begin
  if Assigned(StartWaitProc) then
    StartWaitProc;
end;
leads to execution of non-existent address and AV.

Workaround

We've changed DacGui.inc like this:

Code: Select all

...
finalization
(+)  SetCursorProc := nil;
(+)  ShowConnectFormProcFmx := nil;
(+)  ShowConnectFormProc := nil;
(+)  ShowDebugFormProc := nil;
(+)  StartWaitProc := nil;
(+)  StopWaitProc := nil;
(+)  ApplicationTitleProc := nil;
{$IFDEF WIN32_64}
...
end.
It turned out to be sufficient for us to fix the problem, but this solution may be inaccurate and needs to be refined for all platforms, i.e. for FMX. I don't understand why initialization part has this IFDEF:

Code: Select all

{$IFDEF FMX}
  if not Assigned(StartWaitProc) then
{$ENDIF}

MaximG
Devart Team
Posts: 1822
Joined: Mon 06 Jul 2015 11:34

Re: Issue with dynamically loaded/unloaded DacVcl package.

Post by MaximG » Thu 25 Mar 2021 15:29

Thank you for your patience. We have reproduced the issue and will investigate its origin.

MaximG
Devart Team
Posts: 1822
Joined: Mon 06 Jul 2015 11:34

Re: Issue with dynamically loaded/unloaded DacVcl package.

Post by MaximG » Mon 29 Mar 2021 16:38

The package DacVCL can be used only in VCL applications. This package isn't intended to be used in console applications (as in your sample project). If you encounter this issue in a VCL application, please send us a sample VCL project. For your convenience, please use the e-support form https://www.devart.com/company/contactform.html

worm2
Posts: 2
Joined: Sat 04 Dec 2010 09:40

Re: Issue with dynamically loaded/unloaded DacVcl package.

Post by worm2 » Tue 30 Mar 2021 14:14

Thank you!

We've encountered the issue in a VCL application, I only used console app to make minimal example. Made a VCL app example and sent it through e-support form.

MaximG
Devart Team
Posts: 1822
Joined: Mon 06 Jul 2015 11:34

Re: Issue with dynamically loaded/unloaded DacVcl package.

Post by MaximG » Fri 18 Jun 2021 12:20

We haven't received the project. Could you resend it?

Post Reply