Hydra (version 6.3.0.1255) and DevExpress components

Hi,

I am developing a new app and both server and client require a strong modularity. I use the whole RO/DA/HY suite for both the server and client parts.

In the client side I have a problem when I create visual plugins with devexpress components.

I have already inserted, as suggested in another post, the call (in the modulecontroller) of the dxInitialize and dxFinalize methods.

Practically going into debug the problem occurs when the plugin is unloaded via FreeLibrary and remains hanging

I modified the Visual Plugin sample to verify the problem

Any help?

Thank you very much

Best Regards

Simple Visual.7z (18.9 KB)

Hi,

the only solution that I found is this: but this requires changes of the Hydra source code:

unit uHYCLRHelpers I changed the function IsVCLCompatible:

function IsVCLCompatible(const FileName: string): boolean;
var
  Handle: THandle;
  MydxInitialize: procedure; stdcall; //<--added
  MydxFinalize: procedure; stdcall; //<--added
begin
  Result := false;
  Handle := LoadLibrary(PChar(FileName));
  try
    if Handle = INVALID_HANDLE_VALUE then exit;
    @MydxInitialize := GetProcAddress(Handle, PChar('dxInitialize')); //<--added
    if Assigned(MydxInitialize) then //<--added
      MydxInitialize(); //<--added
    result := Assigned(GetProcAddress(Handle,name_HYGetModuleController));
  finally
    @MydxFinalize := GetProcAddress(Handle, PChar('dxFinalize')); //<--added
    if Assigned(MydxFinalize) then //<--added
      MydxFinalize(); //<--added
    FreeLibrary(Handle);
  end;
end;

And the unit uHYVCLModule this methods:

constructor THYVCLModule.Create(aFileName: string);
var getmodulecontrollerfunc: TGetModuleControllerFunc;
    initproc: TInitializeFinalizeHydraModule;
    lLastError: Integer;
	MydxInitialize: procedure; stdcall; //<--added
begin
  fHandle := LoadLibrary(PChar(aFileName));
  if (fHandle = 0) then begin
    lLastError := GetLastError;
    raise EHYException.CreateFmt(err_CannotLoadModule, [aFileName, lLastError, SysErrorMessage(lLastError)])
  end;
  
  @MydxInitialize := GetProcAddress(Handle, PChar('dxInitialize')); //<--added
  if Assigned(MydxInitialize) then //<--added
    MydxInitialize(); //<--added
  

  inherited Create(aFileName);

  @getmodulecontrollerfunc := GetProcAddress(fHandle, PChar(name_HYGetModuleController));
  if (@getmodulecontrollerfunc=NIL)
    then raise EHYException.CreateFmt(err_NotAnHydraModule, [aFileName])
    else fModuleController := getmodulecontrollerfunc;

  @initproc := GetProcAddress(fHandle, PChar(name_HYInitializeModule));
  if (@initproc<>NIL) then initproc;
end;

destructor THYVCLModule.Destroy;
var
  finproc: TInitializeFinalizeHydraModule;
  Host : IHYHostAware;
  MydxFinalize: procedure; stdcall; //<--added
begin
  if (fHandle>0) then try
    if Supports(fModuleController, IHYHostAware, Host) then begin
      Host.Host := nil;
      Host := nil;
    end;

    @finproc := GetProcAddress(fHandle, PChar(name_HYFinalizeModule));
    if (@finproc<>NIL) then finproc;
	
    @MydxFinalize := GetProcAddress(Handle, PChar('dxFinalize')); //<--added
    if Assigned(MydxFinalize) then //<--added
      MydxFinalize(); //<--added
  finally
    FreeLibrary(fHandle);
  end;

  inherited;
end;

In the dpr of the plugin I export the dxInitialize and dxFinalize methods:

library VisualModule;

uses
  SysUtils,
  Classes,
  dxCore, //<--added
  mcModuleController in 'mcModuleController.pas' {VisualModuleController: THYModuleController},
  fVisualPlugin in 'fVisualPlugin.pas' {VisualPlugin: THYVisualPlugin};

{#HYDRAMODULE}
{$R *.res}

exports //<--added
  dxInitialize, //<--added
  dxFinalize; //<--added

begin
end.

Conceptually it is necessary to call dxInitialize as soon as the dll is loaded, and dxFinalize just before releasing it.

Whit this changes plugins with DevExpress components work fine.

Is it possible to find a solution so that Hydra already comes out with these changes? Or an alternative that does not require the modification of Hydra considering that calling dxInitialize and dxFinalize in the initialization and finalization of a unit does not work (or at least I could not get it to work)

Best Regards

Hi,

i did some further tests and what i found is that with original Hydra and without exporting and calling dxInitialize and dxFinalize the plugin with DevExpress components works without problems.

But theoretically it shouldn’t work.

Hi,

are required any actions from our side here?

Hi Evgenyk

I’m looking for a confirmation. From an old post about a similar problem you suggested using this solutions

https://supportcenter.devexpress.com/Ticket/Details/T547019/using-form-withcx-component-in-a-dll-causes-an-exception/

or this

https://supportcenter.devexpress.com/ticket/details/ka18891/how-to-use-devexpress-controls-in-dll

I had an initial access violation but then I discovered it was due to my mistake on a custom package. In the meantime, however, I had introduced solution 1 (which doesn’t work) and solution 2 which works but not always. Sometimes an access violation occurs when the plugin is released.
Starting from scratch (I removed the changes I made to Hydra) I realized that a Visual Plugin with Hydra (standard) without the call to dxInitialie / dxFinalize works perfectly and I can load and unload the plugin without problems.

To this, in relation to the two solutions you had posted on the use of DevExpress components in a dll, I have some doubts that everything is ok (even if it works perfectly)

Have you made any tests on this? Can you confirm it?

Thank you very much and best regards

Hi,

AFAIR, all changes can be made on plugin level w/o modification of Hydra itself.
your solution looks ok:

unit mcModuleController;
..
initialization
  dxInitialize;
  ..
finalization
  dxFinalize;
  ..
end.

at least other users didn’t complain about it :slight_smile:

Hi Evegenyk

but not work.

I tried with Delphi 10.4, Hydra 6.3.0.1255 and DevExpress 20.1.4

I attached two sample (from original Sample Visual)

  1. Simple Visual_without_dxinit: in this sample I addedd to plugin some deveExpress components. I add to host and to the plugin the devexpress skin. All work fine. I can load and unload plugin without problems without call to methods dxInitialize and dxFinalize

  2. Simple Visual_with_dxinit: this is the same sample above with in the module controller the call to methods dxInitialize and dxFinalize. In this sample when you try to load the plugin the system raise an acces violation when try to call the FreeLibrary (and more precisely when making the call to dxFinalize). The error occurs when the Hydra check if the plugin is a VCL plugin.

Simple Visual_with_dxinit.7z (51.3 KB) Simple Visual_without_dxinit.7z (51.4 KB)

Hi,

you can use LoadUnmanagedCrossPlatformModule/LoadVCLModule methods instead of LoadModule.
in this case IsVCLCompatible method isn’t fired and plugin is loaded once only

Hi EvegenyK

Great now work! I just had to make this change in the module controller. dxFinalize must be the last call otherwise the system raises an access violation if the dxFinalize is called before the FreeAndNil.

unit mcModuleController;
..
initialization
  dxInitialize;
  VisualModuleController := TVisualModuleController.Create('Visual Module');

finalization
  FreeAndNIL(VisualModuleController);
  dxFinalize; //<-- this is must be the last call otherwise the system raises an access violation if the dxFinalize is called before the FreeAndNil 

end.

The only problem I see now is that the module unloads no longer occurs.
To verify this I putted a breakpoint in the finalization section of the module controller and the system stops only when I exit not when I release the module.

When I load the module with LoadUnmanagedCrossPlatformModule do I need a different way to unload the plugin?

To unload the plugin the example uses this code:

procedure TForm1.bUnloadClick(Sender: TObject);
begin
  // Releases the reference to the plugin (equivalent to "fPluginForm := NIL")
  ModuleManager.ReleaseInstance(fPluginForm);

  // Unloads the module
  ModuleManager.UnloadModule(0);

  SwitchButtons;
end;

Thank you very much!

not yet. you just remove IsVCLCompatible checking:

function THYBaseModuleManager.LoadModule(const aFileName: string;
  const aDefaultDomain: THYClrDomain): integer;
begin
  case GetExecutableType(aFilename) of
    etWin32, etWin64: begin
      if IsVCLCompatible(aFileName) then
        result := IntLoadVCLModule(aFilename)
      else
        result := IntLoadUnmanagedCrossPlatformModule(aFilename);
    end;
..

you may try to use this code in your plugin .dpr:

procedure ROProc(Reason:integer);
begin
  case Reason of
    DLL_PROCESS_ATTACH: begin
      ...
    end;

    DLL_PROCESS_DETACH: begin
      dxFinalize; 
    end;
  end
end;

begin
  DLLProc:=@ROProc;
  ROProc(DLL_PROCESS_ATTACH)
end.

Hi EvegenyK

not work. The DLL_PROCES_DETACH occurs only when I close the app and not when I unload the module from memory.

In addition, after the call to dxFinalize the system raises an access violation.

If you want I can send you the two examples. In both logics you can see that the module is unloaded only when the application is closed and the call to the unloadModule seems to have no effect.

Thank you very much!

Hi,

in general, dxFinalize should be called at closing host and not when you unload plugin.
if you compile host with DevEx package(s), you may forgot about issues with dxInitialize & dxFinalize in plugins.

if you haven’t any issues with DevEx in this sample, why you should use dxInitialize and dxFinalize ?

Hi EvgenyK

my host (and my modules) are compiled with my custom package RTL/VCL/RO and for the Client DevExpress.

So from what I understand from your last answer (which would be consistent with what I verified) in plugins being both Host and DLL compiled with packages I don’t have to worry about to call the dxInitialize and dxFinalize methods. Right?

The sample

Visuale semplice_without_dxinit.7z

work with this logic, Host and plugin compiled with DevExp package, work fine without call dxInitialize and dxFinalize.

Now if I use LoadUnmanagedCrossPlatformModule instead of LoadModule I can avoid, as you suggested, the double loading of the module but the problem is that if I try to unload it, it is no longer unloaded.

Only when I close Host the module is correctly unloaded.

If instead I load the module through the call to

ModuleManafer.LoadModule ('VisualModule.dll');

I can unload it without problems and it is correctly unloaded

  ModuleManager.ReleaseInstance(fPluginForm);

  // Unloads the module
  ModuleManager.UnloadModule(0);

I would like to understand the reason for this different behavior. I would expect the same logic

just because I got an access violation when I unloaded the module and reading some posts on the Hydra channel that was suggested to use this logic.

Next I re-checked and found that the access violation was raised due to my mistake in the custom package.

So I removed the calls to the dxInitialize / dxFinalize methods and wanted to make sure the way was right :wink:

I have been using Hydra from many years but have always used it as a server-side services plug-in.
On the Client never in production, only for some tests especially with C# plugins

Now that I have started using it also on the Client side, it is showing all its power!

Thank you very much!

Yes, because DevEx will be initialized and finalized by host in usual way that is expected for usual GUI applications.

this is weird because the same code is used:

function THYBaseModuleManager.LoadModule(const aFileName: string;
  const aDefaultDomain: THYClrDomain): integer;
begin
..
      result := IntLoadUnmanagedCrossPlatformModule(aFilename);
function THYModuleManager.LoadUnmanagedCrossPlatformModule(const aFileName: string): integer;
begin
  result := IntLoadUnmanagedCrossPlatformModule(aFileName);
end;

Hi EvgenyK

I attached a sample source code and video. I don’t know where I’m wrong but I confirm that if I use LoadUnmanagedCrossPlatformModule to load a plugin the unload occurs only whe I close the host and not when I call the UnloadModule.

See this video:

Sample source code:

Simple Visual_unload_problem.7z (51.7 KB)

Thank you very much!

Hi EvgenyK

maybe i found the problem.

When you load the module with LoadModule method and you want unload is invoked the method Destroy of the class THYVCLModule

destructor THYVCLModule.Destroy;
var
  finproc: TInitializeFinalizeHydraModule;
  Host : IHYHostAware;
begin
  if (fHandle>0) then try
    if Supports(fModuleController, IHYHostAware, Host) then begin
      Host.Host := nil;
      Host := nil;
    end;

    @finproc := GetProcAddress(fHandle, PChar(name_HYFinalizeModule));
    if (@finproc<>NIL) then finproc;
  finally
    FreeLibrary(fHandle);
  end;

  inherited;
end;

When you load the module with LoadUnmanagedCrossPlatformModule method and you want unload is invoked the method Destroy of the class THYUnmanagedCrossPlatformModule.

But the Destroy method has a problem: the call of the FreeLibrary is invoked only if you run it on a delphi version older than XE7

destructor THYUnmanagedCrossPlatformModule.Destroy;
begin
  if assigned(fModuleController) then
    FreeAndNil(fModuleController);

  {$IFNDEF DELPHIXE7UP} <-- why?
  if fHandle <> 0 then
    FreeLibrary(fHandle);
  {$ENDIF}

  inherited;
end;

In this case the FreeLibrary method it never invoked.

Why the destructor between the VCL and Unmanaged part is so different?

Best Regards

Hi,

I see here only one issue: you have VCL plugin, so better to use LoadVCLModule method instead of LoadUnmanagedCrossPlatformModule.

LoadVCLModule loads plugin via HYGetModuleController exported method of plugin.
LoadUnmanagedCrossPlatformModule loads plugin via HYGetCrossPlatformModule exported method of plugin.


looks like we had some issue with cross-platform plugins because unloading plugins were disabled for XE7+:

destructor THYUnmanagedCrossPlatformModule.Destroy;
begin
...
  {$IFNDEF DELPHIXE7UP}
  if fHandle <> 0 then
    FreeLibrary(fHandle);
  {$ENDIF}

I’ll investigate this issue and detect why this condition was added


Edit: we had issue with unloading FMX plugins in XE7 in 2014.
by some reasons after refactoring of Hydra that code was migrated to THYUnmanagedCrossPlatformModule

1 Like

Thanks, logged as bugs://84787

1 Like

Hi,

as I can see, FMX plugin cannot be correctly unloaded - External exception 87A is raised in dxgi.dll when fmx.FMX.Forms.FinalizeForms is executed.

Looks like it was a reason, why FMX plugins weren’t unloaded

1 Like

bugs://84787 got closed with status fixed.

1 Like