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.
Code: Select all
library TestDLL;
uses
DacVcl; // does nothing, just uses
end.
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
Code: Select all
src\build.cmd src\Test.dpr
Code: Select all
src\build.cmd src\TestDLL.dpr
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
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.
Code: Select all
procedure StartWait;
begin
if Assigned(StartWaitProc) then
StartWaitProc;
end;
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.
Code: Select all
{$IFDEF FMX}
if not Assigned(StartWaitProc) then
{$ENDIF}