////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Wrapper for using shared memory manager.                                   //
// NB: ALWAYS use this unit as first unit in USES clause list.                //
// NB: To use (.dll/.so) plugins, main module must export GetMemoryManager.   //
//     Plugins must use common memory manager exported by main module.        //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20230426 - Created, A.K.                                                   //
// 20240324 - ExtMM,ExtMM_Allocated                                           //
// 20241031 - IsUsingSharedMemoryManager,IsUsingCommonMemoryManager           //
// 20250206 - alignment with _crw_align_abi.inc                               //
////////////////////////////////////////////////////////////////////////////////

unit _crw_sharemem; //  Wrapper to use Shared Memory Manager.

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

///////////////////////////////////////////////////////
{$I _crw_align_abi.inc} // NB: UNIT MUST BE ALIGNED !!!
///////////////////////////////////////////////////////

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 {$IFDEF UNIX} cthreads, dl, {$ENDIF}
 {$IFDEF WINDOWS} windows, {$ENDIF}
 {$IFDEF USES_SHAREMEM}
  {$IFDEF CPUX64}
   {$IFDEF USES_SYNFPCX64MM} synfpcx64mm, {$ENDIF}
   {$IFDEF USES_NEWFPCX64MM} mormot.core.fpcx64mm, {$ENDIF}
  {$ENDIF}
 {$ENDIF}
 sysutils;

var
 ShareMemStorage : record
  DefMM: TMemoryManager;     // Default  Memory Manager
  CurMM: TMemoryManager;     // Current  Memory Manager defined by user
  ExtMM: TMemoryManager;     // Extended Memory Manager to control memory usage
  ExtMM_Allocated: SizeInt;  // ExtMM allocated memory counter
  ExtMM_Enabled:Boolean;     // ExtMM usage is Enabled
  MainModuleHandle: record   // Uses to get CommonMemoryManager
   case Integer of           // Union
    0: ( P:Pointer );        // as Pointer
    1: ( H:HModule );        // as Handle
  end;
  GetCommonMemoryManager: procedure(var aMemMgr: TMemoryManager);
 end;

function IsUsingSharedMemoryManager:Boolean;
function IsUsingCommonMemoryManager:Boolean;

implementation

function IsUsingSharedMemoryManager:Boolean;
begin
 Result:={$IFDEF USES_SHAREMEM} true {$ELSE} false {$ENDIF};
end;

function IsUsingCommonMemoryManager:Boolean;
begin
 Result:=IsLibrary
     and IsUsingSharedMemoryManager
     and Assigned(ShareMemStorage.MainModuleHandle.P)
     and Assigned(ShareMemStorage.GetCommonMemoryManager)
     and (@ShareMemStorage.GetCommonMemoryManager<>@GetMemoryManager);
end;

procedure LockedAdd(var Target:SizeInt; Value:SizeInt); inline;
begin
 {$IFDEF CPU64}
 if (Value<>0) then System.InterlockedExchangeAdd64(Target,Value);
 {$ELSE}
 if (Value<>0) then System.InterlockedExchangeAdd(Target,Value);
 {$ENDIF}
end;

function extMemSize(p:Pointer):SizeInt; inline;
begin
 if Assigned(p)
 then Result:=ShareMemStorage.CurMM.MemSize(p)
 else Result:=0;
end;

function extGetmem(Size:PtrUInt):Pointer;
begin
 Result:=ShareMemStorage.CurMM.Getmem(Size);
 LockedAdd(ShareMemStorage.ExtMM_Allocated,extMemSize(Result));
end;

function extFreemem(p:Pointer):PtrUInt;
begin
 LockedAdd(ShareMemStorage.ExtMM_Allocated,-extMemSize(p));
 Result:=ShareMemStorage.CurMM.Freemem(p);
end;

function extFreememSize(p:Pointer; Size:PtrUInt):PtrUInt;
begin
 LockedAdd(ShareMemStorage.ExtMM_Allocated,-extMemSize(p));
 Result:=ShareMemStorage.CurMM.FreememSize(p,Size);
end;

function extAllocMem(Size:PtrUInt):Pointer;
begin
 Result:=ShareMemStorage.CurMM.AllocMem(Size);
 LockedAdd(ShareMemStorage.ExtMM_Allocated,extMemSize(Result));
end;

function extReAllocMem(var p:pointer;Size:ptruint):Pointer;
var n1,n2:SizeInt;
begin
 n1:=extMemSize(p);
 Result:=ShareMemStorage.CurMM.ReAllocMem(p,Size);
 n2:=extMemSize(p);
 LockedAdd(ShareMemStorage.ExtMM_Allocated,n2-n1);
end;

procedure Init_crw_sharemem;
{$IFDEF USES_SHAREMEM} var gmm:Pointer; {$ENDIF}
begin
 with ShareMemStorage do begin
  MainModuleHandle.P:=nil;
  GetMemoryManager(DefMM);
  GetCommonMemoryManager:=@GetMemoryManager;
  ExtMM_Enabled:={$IFDEF USES_EXTMM} true {$ELSE} false {$ENDIF};
  {$IFDEF USES_SHAREMEM}
  if IsLibrary then begin
   gmm:=nil;
   {$IFDEF UNIX}
   MainModuleHandle.P:=dlopen(nil, RTLD_LAZY);
   gmm:=dlsym(MainModuleHandle.P,'GetMemoryManager');
   {$ENDIF}
   {$IFDEF WINDOWS}
   MainModuleHandle.H:=GetModuleHandle(nil);
   gmm:=GetProcAddress(MainModuleHandle.H,'GetMemoryManager');
   {$ENDIF}
   if (gmm<>nil) then GetCommonMemoryManager:=gmm;
   GetCommonMemoryManager(CurMM);
   SetMemoryManager(CurMM);
  end;
  {$ENDIF}
  GetMemoryManager(CurMM);
  GetMemoryManager(ExtMM);
  if ExtMM_Enabled then begin
   ExtMM.Getmem:=extGetmem;
   ExtMM.Freemem:=extFreemem;
   ExtMM.FreememSize:=extFreememSize;
   ExtMM.AllocMem:=extAllocMem;
   ExtMM.ReAllocMem:=extReAllocMem;
   SetMemoryManager(ExtMM);
  end;
 end;
end;

procedure Free_crw_sharemem;
begin
 with ShareMemStorage do begin
  if ExtMM_Enabled
  then SetMemoryManager(CurMM);
  {$IFDEF USES_SHAREMEM}
   {$IFDEF UNIX}
     if IsLibrary then SetMemoryManager(DefMM);
   {$ENDIF}
   {$IFDEF WINDOWS}
     if IsLibrary then SetMemoryManager(DefMM);
   {$ENDIF}
  {$ENDIF}
 end;
end;

initialization

 Init_crw_sharemem;

finalization

 Free_crw_sharemem;

end.

//////////////
// END OF FILE
//////////////

