/// this unit will patch the System.pas RTL to use a custom NON OLE COMPATIBLE
// WideString type, NOT using the slow Windows API, but FastMM4 (without COW)
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynFastWideString;

interface

(*
    This file is part of Synopse Framework.

    Synopse Framework. Copyright (C) 2022 Arnaud Bouchez
      Synopse Informatique - https://synopse.info

  *** BEGIN LICENSE BLOCK *****
  Version: MPL 1.1/GPL 2.0/LGPL 2.1

  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the License.

  The Original Code is Synopse Framework.

  The Initial Developer of the Original Code is Arnaud Bouchez.

  Portions created by the Initial Developer are Copyright (C) 2022
  the Initial Developer. All Rights Reserved.

  Contributor(s):

  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the GPL or the LGPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.

  ***** END LICENSE BLOCK *****

  DISCLAIMER:

   Purpose of this unit is to patch the system.pas unit for older versions
   of Delphi, so that WideString memory allocation would use FastMM4 instead
   of the slow BSTR Windows API.

   It will speed up the WideString process a lot, especially when a lot of
   content is allocated, since FastMM4 is much more aggressive than Windows'
   global heap and the BSTR slow API. It could be more than 50 times faster,
   especially when releasing the used memory.

   The WideString implementation pattern does NOT feature Copy-On-Write, so is
   slower than the string=UnicodeString type as implemented since Delphi 2009.
   This is the reason why this unit won't do anything on Unicode versions of
   the compiler, since the new string type is to be preferred.

  HOW TO USE:

   Just add the unit at the TOP of your .dpr uses clause, just after FastMM4
   (if you use it, and you should!) i.e. before all other units used by your
   program. It should be initialized before any WideString is allocated.

   Then the patch will be applied at runtime. Nothing to recompile!

    program MyProgram;

    uses
      {$I SynDprUses.inc}    // will enable FastMM4 prior to Delphi 2006
      SynFastWideString,     // will use FastMM4 prior to Delphi 2009
      ...

  NOTE:

   Since we add a trailing 0 byte at the end of the buffer, we need the
   memory manager to let enough place for it: oldest Borland MM does not :(
   SO IF YOU WORK WITH A VERSION PRIOR TO DELPHI 2006, ADD FASTMM4 TO YOUR .DPR

  WARNING:

   ------------------------------------------------------------------
    USING THIS UNIT MAY BREAK COMPATIBILITY WITH OLE/COM LIBRARIES !
   ------------------------------------------------------------------
   You won't be able to retrieve and release WideString/BSTR variables from an
   OleDB / ADO database provider, or any COM object.
   Do not use this unit if you are calling such external call!

   In practice, if you only SEND some BSTR content to the provider (e.g. if
   you use our SynOleDB unit without stored procedure call, or if you use
   TWideString fields for most SynDBDataSet classes), it will work.
   You would have issues only if you *retrieve* a BSTR from the COM object,
   or expect the COM object to *change* the BSTR size, e.g. with a "var"
   WideString parameter or a COM method returning a WideString. In this case,
   you could use the WideStringFree() procedure to release such an instance.

   It is for educational purpose only, and/or if you are 100% sure that your
   code will stay self-contained, under Delphi 7 or Delphi 2007, and need use
   of WideString instead of string=AnsiString.

   -----------------------------------------------
    YOU HAVE BEEN WARNED - USE AT YOUR OWN RISK !   :)
   -----------------------------------------------

*)

/// this low-level helper can be used to free a WideString returned by COM/OLE
// - WideString instances created with this unit can be safely sent to any
// COM/OLE object, as soon as they are constant parameters, but not a "var"
// parameter or a callback function result
// - any WideString instance returned by a COM object should NOT be released
// by Delphi automatically, since the following would create a memory error:
// ! TrueBSTRWideStringVariable := '';
// - if you are using SynFastWideString, you should use this procedure to
// release true BSTR WideString instance, as such:
// ! type
// !  _Catalog = interface(IDispatch)
// !    // this method will be safe to use with our unit
// !    function Create(const ConnectString: WideString): OleVariant; safecall;
// !    // this method won't be safe, since it returns a true BSTR as WideString
// !    function GetObjectOwner(const ObjectName: WideString; ObjectType: OleVariant;
// !                            ObjectTypeId: OleVariant): WideString; safecall;
// ! end;
// !...
// !function CheckCatalogOwner(const catalog: _Catalog): string;
// !var bstr: WideString;
// !begin
// !  try // force manual handling of this true BSTR instance lifetime
// !   bstr := catalog.GetObjectOwner('name',null,null);
// !   result := bstr; // conversion to string will work
// !  finally
// !    WideStringFree(bstr); // manual release, and set bstr := nil
// !  end;
// !end;
// - do a regular TrueBSTRWideStringVariable := '' since Delphi 2009, or
// call the low-level oleaut32.dll API for older versions, as expected by COM
procedure WideStringFree(var TrueBSTRWideStringVariable: WideString);


implementation

{$ifdef UNICODE}
  // since Delphi 2009, use string=UnicodeString type, which features CopyOnWrite
  // -> do not patch anything
  {$define NOOVERRIDE}
{$endif}
{$ifdef FPC}
  // our low-level Delphi Win32 specific hack won't work with FPC
  {$define NOOVERRIDE}
{$endif}

{$ifdef NOOVERRIDE}

procedure WideStringFree(var TrueBSTRWideStringVariable: WideString);
begin
  TrueBSTRWideStringVariable := ''; // regular handling via System.pas
end;

{$else}

uses
  Windows;

{$RANGECHECKS OFF}
{$STACKFRAMES OFF}
{$OPTIMIZATION ON}
{$DEBUGINFO OFF}

type // some types here since we do not want any dependency on any other units
  PByteArray = ^TByteArray;
  TByteArray = array[0..MaxInt-1] of byte;

// we need to patch the oleaut32.dll library calls as defined in System.pas
// -> retrieve CALL address from low-level funtions asm, then the API slot

function _SysAllocStringLen: pointer;
asm
  lea eax,System.@WStrFromPWCharLen+8
end;

function _SysReAllocStringLen: pointer;
asm
  lea eax,System.@WStrAsg+8
end;

function _SysFreeString: pointer;
asm
  lea eax,System.@WStrClr+8
end;

procedure PatchAPI(source,dest: pointer);
var RestoreProtection,Ignore: DWORD;
begin
  while PByte(source)^<>$e8 do  // search first CALL within the function code
    inc(PByte(source));
  inc(PByte(source));
  inc(PByte(source),PInteger(source)^+SizeOf(integer)); // go to the CALL stub
  if PWord(source)^<>$25ff then // expect "jmp dword ptr []" asm
    halt;
  inc(PWord(source));
  source := PPointer(source)^;  // get "dword ptr []" address of API redirection
  if VirtualProtect(source,SizeOf(source),PAGE_EXECUTE_READWRITE,RestoreProtection) then begin
    PPointer(source)^ := dest;  // replace oleaut32.dll API with our own function
    VirtualProtect(source,SizeOf(source),RestoreProtection,Ignore);
    FlushInstructionCache(GetCurrentProcess,source,SizeOf(source));
  end;
end;


// those are the 3 redirected API calls -> just use AnsiString for allocation

function SysAllocStringLen(P: PAnsiChar; Len: integer): pointer; stdcall;
begin
  result := nil;
  Len := Len*2;
  SetString(AnsiString(result),P,Len);
  PByteArray(result)[Len+1] := 0; // ensure finishes with a #0 WideChar
end;

function SysReAllocStringLen(var S: AnsiString; P: PAnsiChar; Len: integer): LongBool; stdcall;
begin
  Len := Len*2;
  SetString(S,P,Len);
  PByteArray(S)[Len+1] := 0; // ensure finishes with a #0 WideChar
  result := true;
end;

procedure SysFreeString(S: pointer); stdcall;
begin
  AnsiString(S) := '';
end;

procedure OleAut32SysFreeString(S: pointer); stdcall;
  external 'oleaut32.dll' name 'SysFreeString'

procedure WideStringFree(var TrueBSTRWideStringVariable: WideString);
begin
  if pointer(TrueBSTRWideStringVariable)<>nil then begin
    OleAut32SysFreeString(pointer(TrueBSTRWideStringVariable));
    pointer(TrueBSTRWideStringVariable) := nil;
  end;
end;

initialization
  PatchAPI(_SysAllocStringLen,@SysAllocStringLen);
  PatchAPI(_SysReAllocStringLen,@SysReAllocStringLen);
  PatchAPI(_SysFreeString,@SysFreeString);

{$endif NOOVERRIDE}

end.
