TMS FNC Core update: Printing and Design-time Editors

Included in the version 2.6 release of the TMS FNC Core package is a print library
that is capable of creating files and send them to the printer. 
And two design-time editors to make it easier to customize the TTMSFNCGraphicsFill and TTMSFNCGraphicsStroke in your FNC products.

TMS FNC Printing

The print library supports creating documents,
adding pages and page content such as HTML formatted text, plain text, drawing
primitives and images, with practically the same code on all different frameworks.

This has been implemented on three different levels.

The lowest level of implementation for the print library is the use of TMSFNCPrinter, this is done in a similar way as TPrinter is used on VCL, Lazarus, FMX Windows and MacOS. With TMSFNCPrinter there is now support for FMX Android and iOS and for TMS WEB Core.

The difference with TPrinter is that the drawing should be defined in the OnDrawContent procedure. 

uses
  …, FMX.TMSFNCPrinters, FMX.TMSFNCGraphicsTypes;

procedure Click(Sender:TObject);
begin

  TMSFNCPrinter.OnDrawContent :=

  procedure
  begin
    TMSFNCPrinter.Graphics.Font.Color := gcBlue;
    TMSFNCPrinter.Graphics.Font.Size := 40;
    TMSFNCPrinter.Graphics.DrawText(0, 20, TMSFNCPrinter.PageWidth, 100, 'Hello', False, gtaCenter, gtaCenter);

    TMSFNCPrinter.Graphics.Fill.Color := gcRed;
    TMSFNCPrinter.Graphics.DrawEllipse(100,200, TMSFNCPrinter.PageWidth - 100, 300);

    TMSFNCPrinter.Graphics.DrawBitmap(50,400,TMSFNCPrinter.PageWidth - 50, TMSFNCPrinter.PageHeight - 50, Image1.Bitmap, True, True);

    TMSFNCPrinter.EndDoc;
  end;

  TMSFNCPrinter.BeginDoc;
end; 

On top of the TMSFNCPrinter, there is the TMSFNCGraphicsPrintIO component, which gives the ability to further define the layout of the document with a header, footer and page number and to add certain FNC components.

The TTMSFNCRichEditorPrintIO and TTMSFNCGridPrintIO are added In the TMS FNC UI PackThese components have some additional properties to make the export of the TTMSFNCRichEditor or TTMSFNCGrid even more customizable.


Design-time Editors

Working with FNC controls should be fast and simple. And for that reason we’re adding two new design-time editors in TMS FNC Core.

These help with setting the properties for the TTMSFNCGraphicsFill and TTMSFNCGraphicsStroke.

Read More

Read More

TMS FNC Core update: Printing and Design-time Editors

Included in the version 2.6 release of the TMS FNC Core package is a print library
that is capable of creating files and send them to the printer. 
And two design-time editors to make it easier to customize the TTMSFNCGraphicsFill and TTMSFNCGraphicsStroke in your FNC products.

TMS FNC Printing

The print library supports creating documents,
adding pages and page content such as HTML formatted text, plain text, drawing
primitives and images, with practically the same code on all different frameworks.

This has been implemented on three different levels.

The lowest level of implementation for the print library is the use of TMSFNCPrinter, this is done in a similar way as TPrinter is used on VCL, Lazarus, FMX Windows and MacOS. With TMSFNCPrinter there is now support for FMX Android and iOS and for TMS WEB Core.

The difference with TPrinter is that the drawing should be defined in the OnDrawContent procedure. 

uses
  …, FMX.TMSFNCPrinters, FMX.TMSFNCGraphicsTypes;

procedure Click(Sender:TObject);
begin

  TMSFNCPrinter.OnDrawContent :=

  procedure
  begin
    TMSFNCPrinter.Graphics.Font.Color := gcBlue;
    TMSFNCPrinter.Graphics.Font.Size := 40;
    TMSFNCPrinter.Graphics.DrawText(0, 20, TMSFNCPrinter.PageWidth, 100, 'Hello', False, gtaCenter, gtaCenter);

    TMSFNCPrinter.Graphics.Fill.Color := gcRed;
    TMSFNCPrinter.Graphics.DrawEllipse(100,200, TMSFNCPrinter.PageWidth - 100, 300);

    TMSFNCPrinter.Graphics.DrawBitmap(50,400,TMSFNCPrinter.PageWidth - 50, TMSFNCPrinter.PageHeight - 50, Image1.Bitmap, True, True);

    TMSFNCPrinter.EndDoc;
  end;

  TMSFNCPrinter.BeginDoc;
end; 

On top of the TMSFNCPrinter, there is the TMSFNCGraphicsPrintIO component, which gives the ability to further define the layout of the document with a header, footer and page number and to add certain FNC components.

The TTMSFNCRichEditorPrintIO and TTMSFNCGridPrintIO are added In the TMS FNC UI PackThese components have some additional properties to make the export of the TTMSFNCRichEditor or TTMSFNCGrid even more customizable.


Design-time Editors

Working with FNC controls should be fast and simple. And for that reason we’re adding two new design-time editors in TMS FNC Core.

These help with setting the properties for the TTMSFNCGraphicsFill and TTMSFNCGraphicsStroke.

Read More

Read More

Delphi Data in JetBrains Developers Survey

  

JetBrains conducts one of the largest yearly developers surveys, along with StackOverflow. Given its breadth (over 30K developers), the survey is significant for the industry overall, even if skewed towards the communities and programming languages they offer IDEs and tools for. Also the largest amount of respondents (59%) are under 30, which might not represent the overall developers population. You can read the summary at:


www.jetbrains.com/lp/devecosystem-2021/

The key take away is not a surprise, with JavaScript leading the programming languages in popularity, Python growing and Java maintaining a key spot in the ranking. (I personally don’t consider HTML and SQL as “programming languages”, but that’s a separate point).
Delphi is present with a small 1%, but given the target audience that’s significant. Now what’s very interesting to me is the details about which platforms Delphi developers target. Considering that overall web development (backend and frontend) is well above desktop and mobile, for Delphi we can see the following split (among developers using it):

Desktop = 83%
Mobile = 33%
Web (Back-end) = 61%
Web (Front-end) = 52%

One interesting element in this data is that among all languages in the survey Delphi has the strongest desktop focus (that is, no other languages is used as much for desktop development): next at 61% are Visual Basic and Assembly.
A final interesting piece of information is the following: “Delphi is the primary language of 3% of the developer population in Brazil”. We might get more from the raw data, but the survey is a treasure trove of information about developers, their tools and their lifestyle… including favorite drink by age 😉

Read More

TMS WEB Core v1.8 Sirolo released

TMS WEB Core v1.8 Sirolo is released!

We are excited to inform that TMS WEB Core v1.8 Sirolo is released today. This new version is the result of months of hard work of the team to bring several new milestones. Take advantage of the summer-time to expand your horizons and dive into the wonderful web world for Object Pascal developers. 
Here is a brief overview of the core new features included in TMS WEB Core v1.8 

1) Miletus support for macOS and Linux

After Miletus targeted Windows in TMS WEB Core v1.7, our team now accomplished the phenomenal next step to create cross-platform web technology based desktop applications for Windows, macOS and Linux. And all that with a minimal footprint. On Windows and Linux this means single executable file apps and for macOS the typical application folder but also here with a single application executable file. Deployment can be done via ultra simple XCOPY deployment.
In a nutshell, you can create desktop applications from a single source code base from your Delphi IDE on Windows. You do actually not need an Apple mac machine or Linux box to create these applications. The GUI of these applications is rendered in the browser and therefore empowered by HTML5 and CSS3. You can take advantage of existing web application templates to create stunning GUIs
Not familiar with Miletus? You can learn all about it from this webinar replay

2) New Miletus Interbase and Firebird local database support

In this new version, we added two more possible local databases that can be directly used from a Miletus application and that is Interbase and Firebird. No need here to create a REST API interface, you can directly connect a client dataset to local databases. In v1.8 this is now: MS Access, MS SQL, mySQL, SQLite, Interbase and Firebird. 
            

3) Miletus support to access INI files, registry, OS version info

Convenient for storing application settings, INI files or registry is what developers frequently use. So, from a Miletus app you can now easily access these INI files and the registry in the same way as you can from a VCL or Firemonkey application. Of course, there is no registry on macOS or Linux and here there is automatic fallback on INI files. In addition, a new API is added to retrieve operating system version information about the operating system where the Miletus executable is running.

4) Two new UI components: chatbox + rating control

We have added two new controls to TMS WEB Core v1.8. This is a chatbox control and a rating panel. With the TWebChatbox control, you can write web chat applications with little to no code. Drop this component on the form and all you need to do is write some lines of code to send the message to your chat server of choice or receive messages from there. The other new component is the TWebRatingPanel. This is the classic UI pattern of rating via clicking a number of stars (or other icons of choice). The rating panel offers setting rates in units of 1, 0.5 or fully fractional.

5) SHA1 hashing support in the TWebCrypto component

As SHA1 is still from time to time needed in specific communication protocols, we extended the TWebCrypto component that is internally using the browser crypto API to also offer SHA1 support.

6) TWebLocalStorage, TWebSessionStorage components

Previously available as classes, this is now more convenient to use as non-visual components with the new OnChange event exposed to let your app become aware when other instances have changed the local or session storage.

7) Grid enhancements

TWebStringGrid, TWebDBGrid have been improved and new properties to set selection color, selection text color, grid line color, border color have been added. A new event OnClickCell was also added.

8) Various smaller new features

Now it is easier to load a TFrame also dynamically at runtime with the new CreateNew() constructor. The TWebMemo has a SpellCheck property to control whether the browser should use its integrated spell check on the memo content or not.  The TWebClientConnection that bridges a TWebClientDataSet to a server offers settings for using custom HTTP(s) commands as well as set PostData for the HTTP(s) POST request. The TStringList got a promise based LoadFromFileAsync() method to use file loading in combination with await(). For the TBitmap, there is now access to the opacity value per pixel…

Availability

TMS WEB Core v1.8 Sirolo release is now available to all TMS WEB Core or TMS ALL-ACCESS users with an active license. Download the new version after login on our website or via the TMS Subscription Manager. If you had a prior version installed, perform an uninstall first and then install the new v1.8. We didn’t do breaking changes to APIs or class interfaces, so your existing apps should continue to run fine with the new version.

What’s next

The next step is bringing TMS WEB Core for Visual Studio Code on par with its Delphi companion by also integrating Miletus support. You can expect a beta for TMS WEB Core for Visual Studio Code very soon. At the same time, our team is already heavily researching key v1.9 new features, some of which are ground-breaking. We aim to bring some more hot news about it this summer!

Learn & get started

Holidays are a good period to learn and get your feet wet with TMS WEB Core. There are plenty of resources to get started:

1) TMS WEB Core book from colleague, expert and evangelist Holger Flick:

2) Webinar replays about TMS WEB Core you can find on our TMS Web Academy (fully developed with TMS WEB Core by the way)
3) TMS WEB Core training course offered by our colleague Wagner R. Landgraf 
4) Over 100 sample projects included in the product (see in C:\Users\USERNAME\AppData\Local\tmssoftware\TMS WEB Core\Demo folder)
5) Over 100 blog posts about TMS WEB Core 
6) Tons of instructional videos we created
7) Consult with our partners

Share

We create TMS WEB Core with love for you so we love to hear your feedback, your comments, your inputs, your ideas. Tell us about your experiences, tell us what we can do better, what new features you wish, let fellow Delphi developers see what exciting projects you created with TMS WEB Core! Our team is listening.

Read More

Read More

TMS WEB Core v1.8 Sirolo released

TMS WEB Core v1.8 Sirolo is released!

We are excited to inform that TMS WEB Core v1.8 Sirolo is released today. This new version is the result of months of hard work of the team to bring several new milestones. Take advantage of the summer-time to expand your horizons and dive into the wonderful web world for Object Pascal developers. 
Here is a brief overview of the core new features included in TMS WEB Core v1.8 

1) Miletus support for macOS and Linux

After Miletus targeted Windows in TMS WEB Core v1.7, our team now accomplished the phenomenal next step to create cross-platform web technology based desktop applications for Windows, macOS and Linux. And all that with a minimal footprint. On Windows and Linux this means single executable file apps and for macOS the typical application folder but also here with a single application executable file. Deployment can be done via ultra simple XCOPY deployment.
In a nutshell, you can create desktop applications from a single source code base from your Delphi IDE on Windows. You do actually not need an Apple mac machine or Linux box to create these applications. The GUI of these applications is rendered in the browser and therefore empowered by HTML5 and CSS3. You can take advantage of existing web application templates to create stunning GUIs
Not familiar with Miletus? You can learn all about it from this webinar replay

2) New Miletus Interbase and Firebird local database support

In this new version, we added two more possible local databases that can be directly used from a Miletus application and that is Interbase and Firebird. No need here to create a REST API interface, you can directly connect a client dataset to local databases. In v1.8 this is now: MS Access, MS SQL, mySQL, SQLite, Interbase and Firebird. 
            

3) Miletus support to access INI files, registry, OS version info

Convenient for storing application settings, INI files or registry is what developers frequently use. So, from a Miletus app you can now easily access these INI files and the registry in the same way as you can from a VCL or Firemonkey application. Of course, there is no registry on macOS or Linux and here there is automatic fallback on INI files. In addition, a new API is added to retrieve operating system version information about the operating system where the Miletus executable is running.

4) Two new UI components: chatbox + rating control

We have added two new controls to TMS WEB Core v1.8. This is a chatbox control and a rating panel. With the TWebChatbox control, you can write web chat applications with little to no code. Drop this component on the form and all you need to do is write some lines of code to send the message to your chat server of choice or receive messages from there. The other new component is the TWebRatingPanel. This is the classic UI pattern of rating via clicking a number of stars (or other icons of choice). The rating panel offers setting rates in units of 1, 0.5 or fully fractional.

5) SHA1 hashing support in the TWebCrypto component

As SHA1 is still from time to time needed in specific communication protocols, we extended the TWebCrypto component that is internally using the browser crypto API to also offer SHA1 support.

6) TWebLocalStorage, TWebSessionStorage components

Previously available as classes, this is now more convenient to use as non-visual components with the new OnChange event exposed to let your app become aware when other instances have changed the local or session storage.

7) Grid enhancements

TWebStringGrid, TWebDBGrid have been improved and new properties to set selection color, selection text color, grid line color, border color have been added. A new event OnClickCell was also added.

8) Various smaller new features

Now it is easier to load a TFrame also dynamically at runtime with the new CreateNew() constructor. The TWebMemo has a SpellCheck property to control whether the browser should use its integrated spell check on the memo content or not.  The TWebClientConnection that bridges a TWebClientDataSet to a server offers settings for using custom HTTP(s) commands as well as set PostData for the HTTP(s) POST request. The TStringList got a promise based LoadFromFileAsync() method to use file loading in combination with await(). For the TBitmap, there is now access to the opacity value per pixel…

Availability

TMS WEB Core v1.8 Sirolo release is now available to all TMS WEB Core or TMS ALL-ACCESS users with an active license. Download the new version after login on our website or via the TMS Subscription Manager. If you had a prior version installed, perform an uninstall first and then install the new v1.8. We didn’t do breaking changes to APIs or class interfaces, so your existing apps should continue to run fine with the new version.

What’s next

The next step is bringing TMS WEB Core for Visual Studio Code on par with its Delphi companion by also integrating Miletus support. You can expect a beta for TMS WEB Core for Visual Studio Code very soon. At the same time, our team is already heavily researching key v1.9 new features, some of which are ground-breaking. We aim to bring some more hot news about it this summer!

Learn & get started

Holidays are a good period to learn and get your feet wet with TMS WEB Core. There are plenty of resources to get started:

1) TMS WEB Core book from colleague, expert and evangelist Holger Flick:

2) Webinar replays about TMS WEB Core you can find on our TMS Web Academy (fully developed with TMS WEB Core by the way)
3) TMS WEB Core training course offered by our colleague Wagner R. Landgraf 
4) Over 100 sample projects included in the product (see in C:\Users\USERNAME\AppData\Local\tmssoftware\TMS WEB Core\Demo folder)
5) Over 100 blog posts about TMS WEB Core 
6) Tons of instructional videos we created
7) Consult with our partners

Share

We create TMS WEB Core with love for you so we love to hear your feedback, your comments, your inputs, your ideas. Tell us about your experiences, tell us what we can do better, what new features you wish, let fellow Delphi developers see what exciting projects you created with TMS WEB Core! Our team is listening.

Read More

Read More

console – When is System.IsConsole true in Delphi? – Stack Overflow

  

Some highlights from [WayBack] console – When is System.IsConsole true in Delphi? – Stack Overflow:

Project/Options/Linking/Generate console application and {$APPTYPE CONSOLE} are two separate things. – [WayBack] Andreas Rejbrand.
The -cc commandline parameter for dcc32.exe (which could be in your ProjectX.cfg file!) is equivalent to “Generate console application):
Found it: the executable has been created using dcc32.exe and the dpr / cfg file, the cfg contains a line
-cc
which creates a console application. – [WayBack] mjn

Note that in Delphi XE2, OSX applications can not use this variable as it is always true. See QC Entry 98956 and Why Does My OSX FireMonkey App Think It Is a Console App ? – [WayBack] mjn

A few notes I have found out myself
The linker option “Project/Options/Linking/Generate console application” is the same as “DCC_ConsoleTarget” in the .dproj file:

<PropertyGroup Condition=”‘$(Base_Win32)’!=””>
<DCC_ConsoleTarget>true</DCC_ConsoleTarget>
</PropertyGroup>

Even setting IsConsole to True does not allocate a new console, so the STD_OUTPUT_HANDLE handle does not work!
The below method in the System unit, then does not show output:

procedure WriteErrorMessage;
{$IFDEF MSWINDOWS}
var
Dummy: Cardinal;
begin
if IsConsole then
begin
with TTextRec(Output) do
begin
if (Mode = fmOutput) and (BufPos > 0) then
TTextIOFunc(InOutFunc)(TTextRec(Output)); // flush out text buffer
end;
// Leave #0 off end of runErrMsg
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), @runErrMsg, Sizeof(runErrMsg) – 1, Dummy, nil);
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), @sLineBreak[1], 2, Dummy, nil);
end
else if not NoErrMsg then
MessageBoxA(0, runErrMsg, errCaption, 0);
end;

For console output, you have to enable the linker option DCC_ConsoleTarget allocates a console (if not yet allocated), enables the “STD_OUTPUT_HANDLE” handle, and sets IsConsole to True.
–jeroen

Read More

Some useful FastMM related methods to track memory usage

  

Below, for my link archive, some searches and relevant posts on FastMM related method calls to track or report memory usage.
Searches:

LogMemoryManagerStateToFile
FastGetHeapStatus {Returns summarised information about the state of the memory manager. (For
backward compatibility.)}
GetMemoryManagerState (InternalBlockSize, UseableBlockSize, AllocatedBlockCount, ReservedAddressSpace) {Returns statistics about the current state of the memory manager}GetMemoryManagerUsageSummary {Returns a summary of the information returned by GetMemoryManagerState}
GetMemoryMap {Non-POSIX only; Gets the state of every 64K block in the 4GB address space}
ScanMemoryPoolForCorruptions; {Scans the memory pool for any corruptions. If a corruption is encountered an “Out of Memory” exception is raised.}

It is very costly in CPU usage, but helps finding heap corruption quickly.

function GetCurrentAllocationGroup: Cardinal;

{Returns the current “allocation group”. Whenever a GetMem request is serviced
in FullDebugMode, the current “allocation group” is stored in the block header.
This may help with debugging. Note that if a block is subsequently reallocated
that it keeps its original “allocation group” and “allocation number” (all
allocations are also numbered sequentially).}

procedure PushAllocationGroup(ANewCurrentAllocationGroup: Cardinal);
procedure PopAllocationGroup;

{Allocation groups work in a stack like fashion. Group numbers are pushed onto
and popped off the stack. Note that the stack size is limited, so every push
should have a matching pop.}

LogAllocatedBlocksToFile

{Logs detail about currently allocated memory blocks for the specified range of
allocation groups. if ALastAllocationGroupToLog is less than
AFirstAllocationGroupToLog or it is zero, then all allocation groups are
logged. This routine also checks the memory pool for consistency at the same
time, raising an “Out of Memory” error if the check fails.}

SetMMLogFileName

{Specify the full path and name for the filename to be used for logging memory
errors, etc. If ALogFileName is nil or points to an empty string it will
revert to the default log file name.}

Posts (note that not all of them get their calculations right):

[WayBack] How to get the Memory Used by a Delphi Program – Stack Overflow
[WayBack] delphi – FastMM: Total Allocated Memory – Stack Overflow
[WayBack] delphi – Does calling FastMM4 LogAllocatedBlocksToFile() periodically use up memory space? – Stack Overflow:
I have tracked this down to be a version mismatch of the support library FastMM_FullDebugMode.dll.
An older version of the library works with the newer version compiled into the executable. There seems to be no check that versions do match. However, modules don’t really work together at run-time.

[WayBack] delphi – How to solve memory segmentation and force FastMM to release memory to OS? – Stack Overflow
[WayBack] TURBU Tech » Blog Archive » Wanted: live leak detection for FastMM
[WayBack] Strategy or tools to find “non-leak” memory usage problems in Delphi? – Stack Overflow:

use non-FastMM tools: AQTime, MemProof, SleuthQA
use FastMM methods GetMemoryManagerSTate, GetMemoryManagerUsageSummary, LogMemoryStateToFile

FastMM4991 introduced a new method, LogMemoryManagerStateToFile:
Added the LogMemoryManagerStateToFile call. This call logs a summary of the memory manager state to file: The total allocated memory, overhead, efficiency, and a breakdown of allocated memory by class and string type. This call may be useful to catch objects that do not necessarily leak, but do linger longer than they should.

[WayBack] delphi – How can I find out how much memory is used by a specific component or class? – Stack Overflow
[WayBack] What does GetMemoryManagerState, ReservedAddressSpace do in Delphi? – Stack Overflow

These help you track leaks that do not appear as leaks during shutdown: memory allocations that will be released at the end of your application, but are mostly unused while your application is still alive.
A few things to take away from these:

“Out of Memory” (or exception EOutOfMemor) could mean that the memory manager structures are hosed, but memory is still available.
You can specify the FastMM log file used (for instance to include a PID or run-time ID in them so each run gets a separate filename)
When carefully setting up allocation groups, you are able to zoom in at allocations

A gist with a MemoryManagerUnit showing a few of these calls is below.
An example of its usage is this:

procedure TMyTestClass.TestMethod();
begin
TLogMemoryStatesHelper.DumpMemoryStatesBeforeAndAfter(‘TestMethod’,
TLogging.LogDir,
TFileLogger.GetLogFileName,
procedure (const AFormat: string; const Args: array of const)
begin
TLogging.LogEvent(ltInfoHigh, aFormat, Args);
end,
procedure()
begin
try
// Given
BuildSomeTestScenario();
// When
InitializeTestScenario();
// Then
CheckEquals(0, TestScenarioSummary());
finally
// Cleanup
CleanUpTestScenario();
end;
end
);
end;

–jeroen

.gist table { margin-bottom: 0; }

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

unit MemoryManagerUnit;

// based on ideas in https://stackoverflow.com/questions/437683/how-to-get-the-memory-used-by-a-delphi-program/437749

// and code from https://github.com/pleriche/FastMM4/blob/master/Demos/Usage%20Tracker/FastMMUsageTracker.pas

interface

{$Include FastMM4Options.inc} // So defines like FullDebugMode are handled correctly.

{.define FastMMLogAllocatedBlocks} // Only do this in severe situations, as it will take forever to log the blocks (1 hour or more for a simple compenda run/stop)

uses

{$ifdef FastMM}

FastMM4,

{$endif FastMM}

Winapi.Windows,

System.SysUtils;

type

TMemoryManagerStateHelper = record helper for TMemoryManagerState

function LargeBlockSizeUsageBytes: Cardinal;

function LogicalSmallBlockSizeUsageBytes: Cardinal;

function MediumBlockSizeUsageBytes: Cardinal;

function PysicalSmallBlockSizeUsageBytes: Cardinal;

function ReservedSmallBlockSizeUsageBytes: Cardinal;

function ReservedMemoryUsageBytes: Cardinal;

function TotalBlockSizeUsageBytes: Cardinal;

class function GetMemoryManagerState: TMemoryManagerState; static;

function ToString: string;

end;

TSmallBlockTypeStateHelper = record helper for TSmallBlockTypeState

function LogicalBlockSizeUsageBytes: Cardinal;

function PhysicalBlockSizeUsageBytes: Cardinal;

end;

{$ifndef FastMM}

{ From FastMM4.TMemoryManagerUsageSummary }

TMemoryManagerUsageSummary = record

{The total number of bytes allocated by the application.}

AllocatedBytes: NativeUInt;

{The total number of address space bytes used by control structures, or

lost due to fragmentation and other overhead.}

OverheadBytes: NativeUInt;

{The efficiency of the memory manager expressed as a percentage. This is

100 * AllocatedBytes / (AllocatedBytes + OverheadBytes).}

EfficiencyPercentage: Double;

class function GetMemoryManagerUsageSummary: TMemoryManagerUsageSummary; static;

end;

{$endif FastMM}

TMemoryManagerUsageSummaryHelper = record helper for TMemoryManagerUsageSummary

{$ifdef FastMM}

class function GetMemoryManagerUsageSummary: TMemoryManagerUsageSummary; static;

{$endif FastMM}

function ToString: string;

end;

// Various Windows API call results involving processor and memory state:

TWindowsProcessorAndMemoryStatus = record

public

ProcessorCount: DWORD;

AllocationGranularity: DWORD;

AvailablePhysicalMemory: Int64;

TotalPhysicalMemory: Int64;

AvailableVirtualMemory: Int64;

TotalVirtualMemory: Int64;

TotalVirtualExtendedMemory: Int64;

HaveTotalVirtualExtendedMemory: Boolean;

MaximumIncrement: ULONG;

PageSize: ULONG;

NumberOfPhysicalPages: ULONG;

LowestPhysicalPage: ULONG;

HighestPhysicalPage: ULONG;

HaveMaximumIncrement: Boolean;

HavePageSize: Boolean;

HaveNumberOfPhysicalPages: Boolean;

HaveLowestPhysicalPage: Boolean;

HaveHighestPhysicalPage: Boolean;

PageFaultCount: DWORD;

PeakWorkingSetSize: SIZE_T;

WorkingSetSize: SIZE_T;

QuotaPeakPagedPoolUsage: SIZE_T;

QuotaPagedPoolUsage: SIZE_T;

QuotaPeakNonPagedPoolUsage: SIZE_T;

QuotaNonPagedPoolUsage: SIZE_T;

PagefileUsage: SIZE_T;

PeakPagefileUsage: SIZE_T;

HavePageFaultCount: Boolean;

HavePeakWorkingSetSize: Boolean;

HaveWorkingSetSize: Boolean;

HaveQuotaPeakPagedPoolUsage: Boolean;

HaveQuotaPagedPoolUsage: Boolean;

HaveQuotaPeakNonPagedPoolUsage: Boolean;

HaveQuotaNonPagedPoolUsage: Boolean;

HavePagefileUsage: Boolean;

HavePeakPagefileUsage: Boolean;

CurrentProcessId: DWORD;

MinimumAddress: DWORD;

MaximumVMAddress: DWORD;

PageProtectionAndCommitSize: DWORD;

MinimumQuota: NativeUInt;

MaximumQuota: NativeUInt;

// TotalFree: DWord;

// TotalReserve: DWord;

// TotalCommit: DWord;

class function GetWindowsProcessorAndMemoryStatus: TWindowsProcessorAndMemoryStatus; static;

function ToString: string;

end;

TLogMemoryStates = record

public

MemoryManagerUsageSummary: TMemoryManagerUsageSummary;

MemoryManagerState: TMemoryManagerState;

WindowsProcessorAndMemoryStatus: TWindowsProcessorAndMemoryStatus;

end;

TLogMemoryStatesHelper = record

strict private

const

SBefore = 'before';

SAfter = 'after';

public

type

/// <summary>Decouples actual logging mechanism.</summary>

TLogMethod = reference to procedure(const AFormat: string; const Args: array of const);

/// <summary>Logs before/after states of memory allocator and Windows memory usage to `ALogMethod`, dumps before/after memory alloctor blocks, and calls `AMethod` inbetween.

/// <param name="AState">User defined logged in each `ALogMethod` call.</param>

/// <param name="AGetLogDirectory">To store dump file in.</param>

/// <param name="AGetLogFileName">To generate dump filename.</param>

/// <param name="ALogMethod">Decouples actual logging mechanism.</param>

/// <param name="AMethod">Method to call inbetween before/after substate.</param>

/// <returns>`TLogMemoryStates` instance for potential post processing like performing comparisons.</returns>

/// </summary>

class procedure DumpMemoryStatesBeforeAndAfter(const AState: string; const AGetLogDirectory, AGetLogFileName: TFunc<string>; const ALogMethod: TLogMethod; const AMethod: TProc); overload; static;

/// <summary> Logs current states of memory allocator and Windows memory usage to `ALogMethod`.

/// <param name="AState">User defined logged in each `ALogMethod` call.</param>

/// <param name="ALogMethod">Decouples actual logging mechanism.</param>

/// <returns>`TLogMemoryStates` instance for potential post processing like performing comparisons.</returns>

/// </summary>

class function LogMemoryStates(const AState: string; const ALogMethod: TLogMethod): TLogMemoryStates; overload; static;

/// <summary>Logs before/after states of memory allocator and Windows memory usage to `ALogMethod`, calls `AMethod` inbetween.

/// <param name="AState">User defined logged in each `ALogMethod` call.</param>

/// <param name="ALogMethod">Decouples actual logging mechanism.</param>

/// <param name="AMethod">Method to call inbetween before/after substate.</param>

/// <returns>`TLogMemoryStates` instance for potential post processing like performing comparisons.</returns>

/// </summary>

class procedure LogMemoryStatesBeforeAndAfter(const AState: string; const ALogMethod: TLogMethod; const AMethod: TProc); overload; static;

end;

implementation

uses

Winapi.PsAPI,

{$ifdef FastMM}

{$ifdef FullDebugMode}

FastMM4Messages,

System.DateUtils,

System.IOUtils,

{$endif FullDebugMode}

{$endif FastMM}

REST.Json;

function ToJsonStringAndFree(const InstanceToFree: TObject): string;

begin

try

Result := TJson.ObjectToJsonString(InstanceToFree);

finally

InstanceToFree.Free();

end;

end;

{ Windows API calls from FastMMUsageTracker.pas: }

type

TMemoryStatusEx = packed record

dwLength: DWORD;

dwMemoryLoad: DWORD;

ullTotalPhys: Int64;

ullAvailPhys: Int64;

ullTotalPageFile: Int64;

ullAvailPageFile: Int64;

ullTotalVirtual: Int64;

ullAvailVirtual: Int64;

ullAvailExtendedVirtual: Int64;

end;

PMemoryStatusEx = ^TMemoryStatusEx;

LPMEMORYSTATUSEX = PMemoryStatusEx;

TP_GlobalMemoryStatusEx = function(var PR_MemStatusEx: TMemoryStatusEx): LongBool; stdcall;

TSystem_Basic_Information = packed record

dwUnknown1: DWORD;

uKeMaximumIncrement: ULONG;

uPageSize: ULONG;

uMmNumberOfPhysicalPages: ULONG;

uMmLowestPhysicalPage: ULONG;

uMmHighestPhysicalPage: ULONG;

uAllocationGranularity: ULONG;

pLowestUserAddress: Pointer;

pMmHighestUserAddress: Pointer;

uKeActiveProcessors: ULONG;

bKeNumberProcessors: Byte;

bUnknown2: Byte;

wUnknown3: Word;

end;

TSystem_Performance_Information = packed record

liIdleTime: LARGE_INTEGER;

dwSpare: array[0..75] of DWORD;

end;

TSystem_Time_Information = packed record

liKeBootTime: LARGE_INTEGER;

liKeSystemTime: LARGE_INTEGER;

liExpTimeZoneBias: LARGE_INTEGER;

uCurrentTimeZoneId: ULONG;

dwReserved: DWORD;

end;

TP_NtQuerySystemInformation = function(InfoClass: DWORD; Buffer: Pointer; BufSize: DWORD; ReturnSize: PCardinal): DWORD; stdcall;

var

MP_GlobalMemoryStatusEx: TP_GlobalMemoryStatusEx = nil;

MP_NtQuerySystemInformation: TP_NtQuerySystemInformation = nil;

{ Record helpers: }

function TMemoryManagerStateHelper.LargeBlockSizeUsageBytes: Cardinal;

begin

Result := TotalAllocatedLargeBlockSize * AllocatedLargeBlockCount;

end;

function TMemoryManagerStateHelper.LogicalSmallBlockSizeUsageBytes: Cardinal;

var

SmallBlockTypeState: TSmallBlockTypeState;

begin

Result := 0;

for SmallBlockTypeState in SmallBlockTypeStates do

begin

Inc(Result, SmallBlockTypeState.LogicalBlockSizeUsageBytes);

end;

end;

function TMemoryManagerStateHelper.MediumBlockSizeUsageBytes: Cardinal;

begin

Result := TotalAllocatedMediumBlockSize * AllocatedMediumBlockCount;

end;

function TMemoryManagerStateHelper.PysicalSmallBlockSizeUsageBytes: Cardinal;

var

SmallBlockTypeState: TSmallBlockTypeState;

begin

Result := 0;

for SmallBlockTypeState in SmallBlockTypeStates do

begin

Inc(Result, SmallBlockTypeState.PhysicalBlockSizeUsageBytes);

end;

end;

function TMemoryManagerStateHelper.ReservedSmallBlockSizeUsageBytes: Cardinal;

var

SmallBlockTypeState: TSmallBlockTypeState;

begin

Result := 0;

for SmallBlockTypeState in SmallBlockTypeStates do

begin

Inc(Result, SmallBlockTypeState.ReservedAddressSpace);

end;

end;

function TMemoryManagerStateHelper.ReservedMemoryUsageBytes: Cardinal;

begin

Result := ReservedMediumBlockAddressSpace + ReservedLargeBlockAddressSpace + ReservedSmallBlockSizeUsageBytes;

end;

{ Utility functions from FastMMUsageTracker.pas: }

function CardinalToStringFormatted(const ACardinal: Cardinal): string;

begin

Result := FormatFloat('#,##0', ACardinal);

end;

function Int64ToStringFormatted(const AInt64: Int64): string;

begin

Result := FormatFloat('#,##0', AInt64);

end;

function CardinalToKStringFormatted(const ACardinal: Cardinal): string;

begin

Result := FormatFloat('#,##0', ACardinal div 1024) + 'K';

end;

function Int64ToKStringFormatted(const AInt64: Int64): string;

begin

Result := FormatFloat('#,##0', AInt64 div 1024) + 'K';

end;

// REST.Json does not support converting records to JSON, so introduce an intermediate class

type

TMemoryManagerStateClass = class

LargeBlockSizeUsageBytes: Cardinal;

LogicalSmallBlockSizeUsageBytes: Cardinal;

MediumBlockSizeUsageBytes: Cardinal;

PysicalSmallBlockSizeUsageBytes: Cardinal;

ReservedSmallBlockSizeUsageBytes: Cardinal;

ReservedMemoryUsageBytes: Cardinal;

TotalBlockSizeUsageBytes: Cardinal;

public

constructor Create(const AMemoryManagerState: TMemoryManagerState);

end;

constructor TMemoryManagerStateClass.Create(const AMemoryManagerState: TMemoryManagerState);

begin

inherited Create();

LargeBlockSizeUsageBytes := AMemoryManagerState.LargeBlockSizeUsageBytes;

LogicalSmallBlockSizeUsageBytes := AMemoryManagerState.LogicalSmallBlockSizeUsageBytes;

MediumBlockSizeUsageBytes := AMemoryManagerState.MediumBlockSizeUsageBytes;

PysicalSmallBlockSizeUsageBytes := AMemoryManagerState.PysicalSmallBlockSizeUsageBytes;

ReservedSmallBlockSizeUsageBytes := AMemoryManagerState.ReservedSmallBlockSizeUsageBytes;

ReservedMemoryUsageBytes := AMemoryManagerState.ReservedMemoryUsageBytes;

TotalBlockSizeUsageBytes := AMemoryManagerState.TotalBlockSizeUsageBytes;

end;

class function TMemoryManagerStateHelper.GetMemoryManagerState: TMemoryManagerState;

begin

{$ifdef FastMM}

FastMM4

{$else}

System

{$endif FastMM}

.GetMemoryManagerState(Result);

end;

function TMemoryManagerStateHelper.ToString: string;

begin

Result := ToJsonStringAndFree(TMemoryManagerStateClass.Create(Self));

end;

function TMemoryManagerStateHelper.TotalBlockSizeUsageBytes: Cardinal;

begin

Result := TotalAllocatedMediumBlockSize + TotalAllocatedLargeBlockSize + PysicalSmallBlockSizeUsageBytes;

end;

function TSmallBlockTypeStateHelper.LogicalBlockSizeUsageBytes: Cardinal;

begin

Result := AllocatedBlockCount * InternalBlockSize;

end;

function TSmallBlockTypeStateHelper.PhysicalBlockSizeUsageBytes: Cardinal;

begin

Result := AllocatedBlockCount * UseableBlockSize;

end;

{$ifndef FastMM}

class function TMemoryManagerUsageSummary.GetMemoryManagerUsageSummary: TMemoryManagerUsageSummary;

var

LMMS: TMemoryManagerState;

LAllocatedBytes, LReservedBytes: NativeUInt;

begin

GetMemoryManagerState(LMMS);

LAllocatedBytes := LMMS.TotalBlockSizeUsageBytes;

LReservedBytes := LMMS.ReservedMemoryUsageBytes;

{Set the structure values}

Result.AllocatedBytes := LAllocatedBytes;

Result.OverheadBytes := LReservedBytes – LAllocatedBytes;

if LReservedBytes > 0 then

begin

Result.EfficiencyPercentage := LAllocatedBytes / LReservedBytes * 100;

end

else

Result.EfficiencyPercentage := 100;

end;

{$endif FastMM}

{$ifdef FastMM}

class function TMemoryManagerUsageSummaryHelper.GetMemoryManagerUsageSummary: TMemoryManagerUsageSummary;

begin

FastMM4.GetMemoryManagerUsageSummary(Result);

end;

{$endif FastMM}

// REST.Json does not support converting records to JSON, so introduce an intermediate class

type

TMemoryManagerUsageSummaryClass = class

AllocatedBytes: NativeUInt;

OverheadBytes: NativeUInt;

EfficiencyPercentage: Double;

public

constructor Create(const AMemoryManagerUsageSummary: TMemoryManagerUsageSummary);

end;

constructor TMemoryManagerUsageSummaryClass.Create(const AMemoryManagerUsageSummary: TMemoryManagerUsageSummary);

begin

inherited Create();

AllocatedBytes := AMemoryManagerUsageSummary.AllocatedBytes;

OverheadBytes := AMemoryManagerUsageSummary.OverheadBytes;

EfficiencyPercentage := AMemoryManagerUsageSummary.EfficiencyPercentage;

end;

function TMemoryManagerUsageSummaryHelper.ToString: string;

begin

Result := ToJsonStringAndFree(TMemoryManagerUsageSummaryClass.Create(Self));

end;

procedure ModuleInit;

begin

if Win32Platform = VER_PLATFORM_WIN32_NT then

begin

MP_GlobalMemoryStatusEx := TP_GlobalMemoryStatusEx(GetProcAddress(GetModuleHandle(kernel32), 'GlobalMemoryStatusEx'));

MP_NtQuerySystemInformation := TP_NtQuerySystemInformation(GetProcAddress(GetModuleHandle('ntdll.dll'), 'NtQuerySystemInformation'));

end;

end;

class function TWindowsProcessorAndMemoryStatus.GetWindowsProcessorAndMemoryStatus: TWindowsProcessorAndMemoryStatus;

const

SystemBasicInformation = 0;

var

LR_SystemInfo: TSystemInfo;

LR_GlobalMemoryStatus: TMemoryStatus;

LR_GlobalMemoryStatusEx: TMemoryStatusEx;

LR_ProcessMemoryCounters: TProcessMemoryCounters;

LR_SysBaseInfo: TSystem_Basic_Information;

LU_MinQuota: {$if CompilerVersion >= 23}NativeUInt{$else}Cardinal{$ifend};

LU_MaxQuota: {$if CompilerVersion >= 23}NativeUInt{$else}Cardinal{$ifend};

begin

LU_MinQuota := 0;

LU_MaxQuota := 0;

if Assigned(MP_GlobalMemoryStatusEx) then

begin

ZeroMemory(@LR_GlobalMemoryStatusEx, SizeOf(TMemoryStatusEx));

LR_GlobalMemoryStatusEx.dwLength := SizeOf(TMemoryStatusEx);

if not MP_GlobalMemoryStatusEx(LR_GlobalMemoryStatusEx) then

begin

RaiseLastOSError();

end;

end

else

begin

LR_GlobalMemoryStatus.dwLength := SizeOf(TMemoryStatus);

GlobalMemoryStatus(LR_GlobalMemoryStatus);

end;

GetProcessWorkingSetSize(GetCurrentProcess, LU_MinQuota, LU_MaxQuota);

GetSystemInfo(LR_SystemInfo);

Result.ProcessorCount := LR_SystemInfo.dwNumberOfProcessors;

Result.AllocationGranularity := LR_SystemInfo.dwAllocationGranularity;

Result.MinimumAddress := DWORD(LR_SystemInfo.lpMinimumApplicationAddress);

Result.MaximumVMAddress := DWORD(LR_SystemInfo.lpMaximumApplicationAddress);

Result.PageProtectionAndCommitSize := LR_SystemInfo.dWPageSize;

if Assigned(MP_GlobalMemoryStatusEx) then

begin

with LR_GlobalMemoryStatusEx do

begin

Result.AvailablePhysicalMemory := LR_GlobalMemoryStatusEx.ullAvailPhys;

Result.TotalPhysicalMemory := LR_GlobalMemoryStatusEx.ullTotalPhys;

Result.AvailableVirtualMemory := LR_GlobalMemoryStatusEx.ullAvailVirtual;

Result.TotalVirtualMemory := LR_GlobalMemoryStatusEx.ullTotalVirtual;

Result.TotalVirtualExtendedMemory := LR_GlobalMemoryStatusEx.ullAvailExtendedVirtual;

Result.HaveTotalVirtualExtendedMemory := True;

end;

end

else

begin

with LR_GlobalMemoryStatus do

begin

Result.AvailablePhysicalMemory := LR_GlobalMemoryStatus.dwAvailPhys;

Result.TotalPhysicalMemory := LR_GlobalMemoryStatus.dwTotalPhys;

Result.AvailableVirtualMemory := LR_GlobalMemoryStatus.dwAvailVirtual;

Result.TotalVirtualMemory := LR_GlobalMemoryStatus.dwTotalVirtual;

Result.TotalVirtualExtendedMemory := –1;

Result.HaveTotalVirtualExtendedMemory := False;

end;

end;

if Assigned(MP_NtQuerySystemInformation) and

(0 = MP_NtQuerySystemInformation(SystemBasicInformation, @LR_SysBaseInfo, SizeOf(LR_SysBaseInfo), nil))

then

begin

Result.MaximumIncrement := LR_SysBaseInfo.uKeMaximumIncrement;

Result.PageSize := LR_SysBaseInfo.uPageSize;

Result.NumberOfPhysicalPages := LR_SysBaseInfo.uMmNumberOfPhysicalPages;

Result.LowestPhysicalPage := LR_SysBaseInfo.uMmLowestPhysicalPage;

Result.HighestPhysicalPage := LR_SysBaseInfo.uMmHighestPhysicalPage;

Result.HaveMaximumIncrement := True;

Result.HavePageSize := True;

Result.HaveNumberOfPhysicalPages := True;

Result.HaveLowestPhysicalPage := True;

Result.HaveHighestPhysicalPage := True;

end

else

begin

Result.MaximumIncrement := 0;

Result.PageSize := 0;

Result.NumberOfPhysicalPages := 0;

Result.LowestPhysicalPage := 0;

Result.HighestPhysicalPage := 0;

Result.HaveMaximumIncrement := False;

Result.HavePageSize := False;

Result.HaveNumberOfPhysicalPages := False;

Result.HaveLowestPhysicalPage := False;

Result.HaveHighestPhysicalPage := False;

end;

// same as GetProcessMemoryInfo & NtQuerySystemInformation (SystemBasicInformation

// The working set is the amount of memory physically mapped to the process context at a given

// time. Memory in the paged pool is system memory that can be transferred to the paging file

// on disk (paged) when it is not being used. Memory in the nonpaged pool is system memory

// that cannot be paged to disk as long as the corresponding objects are allocated. The pagefile

// usage represents how much memory is set aside for the process in the system paging file.

// When memory usage is too high, the virtual memory manager pages selected memory to disk.

// When a thread needs a page that is not in memory, the memory manager reloads it from the

// paging file.

if GetProcessMemoryInfo(GetCurrentProcess, @LR_ProcessMemoryCounters, SizeOf(LR_ProcessMemoryCounters)) then

begin

Result.PageFaultCount := LR_ProcessMemoryCounters.PageFaultCount;

Result.PeakWorkingSetSize := LR_ProcessMemoryCounters.PeakWorkingSetSize;

Result.WorkingSetSize := LR_ProcessMemoryCounters.WorkingSetSize;

Result.QuotaPeakPagedPoolUsage := LR_ProcessMemoryCounters.QuotaPeakPagedPoolUsage;

Result.QuotaPagedPoolUsage := LR_ProcessMemoryCounters.QuotaPagedPoolUsage;

Result.QuotaPeakNonPagedPoolUsage := LR_ProcessMemoryCounters.QuotaPeakNonPagedPoolUsage;

Result.QuotaNonPagedPoolUsage := LR_ProcessMemoryCounters.QuotaNonPagedPoolUsage;

Result.PagefileUsage := LR_ProcessMemoryCounters.PagefileUsage;

Result.PeakPagefileUsage := LR_ProcessMemoryCounters.PeakPagefileUsage;

Result.HavePageFaultCount := True;

Result.HavePeakWorkingSetSize := True;

Result.HaveWorkingSetSize := True;

Result.HaveQuotaPeakPagedPoolUsage := True;

Result.HaveQuotaPagedPoolUsage := True;

Result.HaveQuotaPeakNonPagedPoolUsage := True;

Result.HaveQuotaNonPagedPoolUsage := True;

Result.HavePagefileUsage := True;

Result.HavePeakPagefileUsage := True;

end

else

begin

Result.PageFaultCount := 0;

Result.PeakWorkingSetSize := 0;

Result.WorkingSetSize := 0;

Result.QuotaPeakPagedPoolUsage := 0;

Result.QuotaPagedPoolUsage := 0;

Result.QuotaPeakNonPagedPoolUsage := 0;

Result.QuotaNonPagedPoolUsage := 0;

Result.PagefileUsage := 0;

Result.PeakPagefileUsage := 0;

Result.HavePageFaultCount := False;

Result.HavePeakWorkingSetSize := False;

Result.HaveWorkingSetSize := False;

Result.HaveQuotaPeakPagedPoolUsage := False;

Result.HaveQuotaPagedPoolUsage := False;

Result.HaveQuotaPeakNonPagedPoolUsage := False;

Result.HaveQuotaNonPagedPoolUsage := False;

Result.HavePagefileUsage := False;

Result.HavePeakPagefileUsage := False;

end;

Result.CurrentProcessId := GetCurrentProcessId();

Result.MinimumQuota := LU_MinQuota;

Result.MaximumQuota := LU_MaxQuota;

{TODO -oJWP -cEnhancement : Future }

// Result.TotalFree := LU_MEM_FREE;

// Result.TotalReserve := LU_MEM_RESERVE;

// Result.TotalCommit := LU_MEM_COMMIT;

// if LP_FreeVMList.Count > CI_MaxFreeBlocksList then

// LI_Max := CI_MaxFreeBlocksList – 1

// else

// LI_Max := LP_FreeVMList.Count – 1;

//

// for LI_I := 0 to LI_Max do

// begin

// Result.Largest Free Block ' + IntToStr(LI_I + 1) + '. = ' + CardinalToKStringFormatted(Cardinal(LP_Free:= LI_I]);

// end;

// In case we want to add a FastMM4 summary:

// Result.TotalBlocks := LTotalBlocks;

// Result.TotalAllocated := LTotalAllocated;

// Result.TotalReserved := LTotalReserved;

end;

// REST.Json does not support converting records to JSON, so introduce an intermediate class

type

TWindowsProcessorAndMemoryStatusClass = class

ProcessorCount: DWORD;

AllocationGranularity: DWORD;

AvailablePhysicalMemory: Int64;

TotalPhysicalMemory: Int64;

AvailableVirtualMemory: Int64;

TotalVirtualMemory: Int64;

TotalVirtualExtendedMemory: Int64;

HaveTotalVirtualExtendedMemory: Boolean;

MaximumIncrement: ULONG;

PageSize: ULONG;

NumberOfPhysicalPages: ULONG;

LowestPhysicalPage: ULONG;

HighestPhysicalPage: ULONG;

HaveMaximumIncrement: Boolean;

HavePageSize: Boolean;

HaveNumberOfPhysicalPages: Boolean;

HaveLowestPhysicalPage: Boolean;

HaveHighestPhysicalPage: Boolean;

PageFaultCount: DWORD;

PeakWorkingSetSize: SIZE_T;

WorkingSetSize: SIZE_T;

QuotaPeakPagedPoolUsage: SIZE_T;

QuotaPagedPoolUsage: SIZE_T;

QuotaPeakNonPagedPoolUsage: SIZE_T;

QuotaNonPagedPoolUsage: SIZE_T;

PagefileUsage: SIZE_T;

PeakPagefileUsage: SIZE_T;

HavePageFaultCount: Boolean;

HavePeakWorkingSetSize: Boolean;

HaveWorkingSetSize: Boolean;

HaveQuotaPeakPagedPoolUsage: Boolean;

HaveQuotaPagedPoolUsage: Boolean;

HaveQuotaPeakNonPagedPoolUsage: Boolean;

HaveQuotaNonPagedPoolUsage: Boolean;

HavePagefileUsage: Boolean;

HavePeakPagefileUsage: Boolean;

CurrentProcessId: DWORD;

MinimumAddress: DWORD;

MaximumVMAddress: DWORD;

PageProtectionAndCommitSize: DWORD;

MinimumQuota: NativeUInt;

MaximumQuota: NativeUInt;

// TotalFree: DWord;

// TotalReserve: DWord;

// TotalCommit: DWord;

public

constructor Create(const AWindowsProcessorAndMemoryStatus: TWindowsProcessorAndMemoryStatus);

end;

constructor TWindowsProcessorAndMemoryStatusClass.Create(const AWindowsProcessorAndMemoryStatus:

TWindowsProcessorAndMemoryStatus);

begin

inherited Create();

ProcessorCount := AWindowsProcessorAndMemoryStatus.ProcessorCount;

AllocationGranularity := AWindowsProcessorAndMemoryStatus.AllocationGranularity;

AvailablePhysicalMemory := AWindowsProcessorAndMemoryStatus.AvailablePhysicalMemory;

TotalPhysicalMemory := AWindowsProcessorAndMemoryStatus.TotalPhysicalMemory;

AvailableVirtualMemory := AWindowsProcessorAndMemoryStatus.AvailableVirtualMemory;

TotalVirtualMemory := AWindowsProcessorAndMemoryStatus.TotalVirtualMemory;

TotalVirtualExtendedMemory := AWindowsProcessorAndMemoryStatus.TotalVirtualExtendedMemory;

HaveTotalVirtualExtendedMemory := AWindowsProcessorAndMemoryStatus.HaveTotalVirtualExtendedMemory;

MaximumIncrement := AWindowsProcessorAndMemoryStatus.MaximumIncrement;

PageSize := AWindowsProcessorAndMemoryStatus.PageSize;

NumberOfPhysicalPages := AWindowsProcessorAndMemoryStatus.NumberOfPhysicalPages;

LowestPhysicalPage := AWindowsProcessorAndMemoryStatus.LowestPhysicalPage;

HighestPhysicalPage := AWindowsProcessorAndMemoryStatus.HighestPhysicalPage;

HaveMaximumIncrement := AWindowsProcessorAndMemoryStatus.HaveMaximumIncrement;

HavePageSize := AWindowsProcessorAndMemoryStatus.HavePageSize;

HaveNumberOfPhysicalPages := AWindowsProcessorAndMemoryStatus.HaveNumberOfPhysicalPages;

HaveLowestPhysicalPage := AWindowsProcessorAndMemoryStatus.HaveLowestPhysicalPage;

HaveHighestPhysicalPage := AWindowsProcessorAndMemoryStatus.HaveHighestPhysicalPage;

PageFaultCount := AWindowsProcessorAndMemoryStatus.PageFaultCount;

PeakWorkingSetSize := AWindowsProcessorAndMemoryStatus.PeakWorkingSetSize;

WorkingSetSize := AWindowsProcessorAndMemoryStatus.WorkingSetSize;

QuotaPeakPagedPoolUsage := AWindowsProcessorAndMemoryStatus.QuotaPeakPagedPoolUsage;

QuotaPagedPoolUsage := AWindowsProcessorAndMemoryStatus.QuotaPagedPoolUsage;

QuotaPeakNonPagedPoolUsage := AWindowsProcessorAndMemoryStatus.QuotaPeakNonPagedPoolUsage;

QuotaNonPagedPoolUsage := AWindowsProcessorAndMemoryStatus.QuotaNonPagedPoolUsage;

PagefileUsage := AWindowsProcessorAndMemoryStatus.PagefileUsage;

PeakPagefileUsage := AWindowsProcessorAndMemoryStatus.PeakPagefileUsage;

HavePageFaultCount := AWindowsProcessorAndMemoryStatus.HavePageFaultCount;

HavePeakWorkingSetSize := AWindowsProcessorAndMemoryStatus.HavePeakWorkingSetSize;

HaveWorkingSetSize := AWindowsProcessorAndMemoryStatus.HaveWorkingSetSize;

HaveQuotaPeakPagedPoolUsage := AWindowsProcessorAndMemoryStatus.HaveQuotaPeakPagedPoolUsage;

HaveQuotaPagedPoolUsage := AWindowsProcessorAndMemoryStatus.HaveQuotaPagedPoolUsage;

HaveQuotaPeakNonPagedPoolUsage := AWindowsProcessorAndMemoryStatus.HaveQuotaPeakNonPagedPoolUsage;

HaveQuotaNonPagedPoolUsage := AWindowsProcessorAndMemoryStatus.HaveQuotaNonPagedPoolUsage;

HavePagefileUsage := AWindowsProcessorAndMemoryStatus.HavePagefileUsage;

HavePeakPagefileUsage := AWindowsProcessorAndMemoryStatus.HavePeakPagefileUsage;

CurrentProcessId := AWindowsProcessorAndMemoryStatus.CurrentProcessId;

MinimumAddress := AWindowsProcessorAndMemoryStatus.MinimumAddress;

MaximumVMAddress := AWindowsProcessorAndMemoryStatus.MaximumVMAddress;

PageProtectionAndCommitSize := AWindowsProcessorAndMemoryStatus.PageProtectionAndCommitSize;

MinimumQuota := AWindowsProcessorAndMemoryStatus.MinimumQuota;

MaximumQuota := AWindowsProcessorAndMemoryStatus.MaximumQuota;

end;

function TWindowsProcessorAndMemoryStatus.ToString: string;

begin

Result := ToJsonStringAndFree(TWindowsProcessorAndMemoryStatusClass.Create(Self));

end;

class procedure TLogMemoryStatesHelper.DumpMemoryStatesBeforeAndAfter(const AState: string; const AGetLogDirectory, AGetLogFileName: TFunc<string>; const

ALogMethod: TLogMethod; const AMethod: TProc);

{TODO -ojwp -cOptimise : Make all variables non-dynamic and stack based so they do not cause heap allocation differences }

var

AfterState: string;

BeforeState: string;

begin

BeforeState := SBefore + ' ' + AState;

AfterState := SAfter + ' ' + AState;

LogMemoryStatesBeforeAndAfter(AState, ALogMethod,

procedure

// note that the `FastMM` `FullDebugMode` related methods need to be local, as otherwise they cannot be captured into the anonymous method.

{$ifdef FastMM}

{$ifdef FullDebugMode}

/// <summary>Memory dump is in the log directory with an extension so it is recognisable as FastMM related.</summary>

function GetMemoryManagerLogPath(const AStartIso8601: string; const AAllocationGroup: Cardinal; const AState: string; const AWhat: string; const AWhen: string): string;

var

LogDirectory: string;

LogFileExtension: string;

LogFileName: string;

begin

LogDirectory := AGetLogDirectory();

LogFileName := AGetLogFileName();

LogFileExtension := PChar(FastMM4Messages.LogFileExtension); // strip any trailing #0

LogFileExtension := Format('%s_%d_%s_%s_%s%s', // last %s has no underscore, as it is already in FastMM4Messages.LogFileExtension

[AStartIso8601, AAllocationGroup, AWhat, AWhen, AState, LogFileExtension]);

LogFileName := TPath.ChangeExtension(LogFileName, LogFileExtension);

Result := TPath.Combine(LogDirectory, LogFileName);

end;

/// <summary>By default only logs memory manager state; only logs blocks when `FastMMLogAllocatedBlocks` is defined.</summary>

function LogStateAndBlocksAndReturnCurrentAllocationGroup(const AStartIso8601: string; const AState: string; const AWhen: string; const AAdditionalDetails: string): Cardinal;

const

SState = 'state';

{$ifdef FastMMLogAllocatedBlocks}

SBlocks = 'blocks';

{$endif FastMMLogAllocatedBlocks}

var

CurrentAllocationGroup: Cardinal;

MemoryManagerLogPath: string;

{$ifdef FastMMLogAllocatedBlocks}

AnsiMemoryManagerLogPath: AnsiString;

{$endif FastMMLogAllocatedBlocks}

begin

CurrentAllocationGroup := FastMM4.GetCurrentAllocationGroup();

MemoryManagerLogPath := GetMemoryManagerLogPath(AStartIso8601, CurrentAllocationGroup, SState, AWhen, AState);

LogMemoryManagerStateToFile(MemoryManagerLogPath, AAdditionalDetails); // logs to a specific filename

{$ifdef FastMMLogAllocatedBlocks}

if CurrentAllocationGroup <> 0 then

begin

MemoryManagerLogPath := GetMemoryManagerLogPath(AStartIso8601, CurrentAllocationGroup, SBlocks, AWhen, AState);

AnsiMemoryManagerLogPath := AnsiString(MemoryManagerLogPath); // suppress W1058; see https://stackoverflow.com/questions/20402653/how-can-i-convert-a-unicode-string-to-an-ansistring

// Only do this in severe situations, as it will take forever to log the blocks

FastMM4.SetMMLogFileName(PAnsiChar(AnsiMemoryManagerLogPath));

LogAllocatedBlocksToFile(CurrentAllocationGroup, CurrentAllocationGroup); // logs to the current MMLogFileName

end;

{$endif FastMMLogAllocatedBlocks}

Result := CurrentAllocationGroup;

end;

var

CurrentAllocationGroup: Cardinal;

Start: TDateTime;

StartIso8601: string;

{$endif FullDebugMode}

{$endif FastMM}

begin

{$ifdef FastMM}

{$ifdef FullDebugMode}

Start := Now();

StartIso8601 := DateToISO8601(Start, False).Replace('–', '').Replace(':', ''); // https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators

CurrentAllocationGroup := LogStateAndBlocksAndReturnCurrentAllocationGroup(StartIso8601, AState, SBefore, BeforeState);

FastMM4.PushAllocationGroup(CurrentAllocationGroup+1);

{$endif FullDebugMode}

{$endif FastMM}

try

AMethod();

finally

{$ifdef FastMM}

{$ifdef FullDebugMode}

try

LogStateAndBlocksAndReturnCurrentAllocationGroup(StartIso8601, AState, SAfter, AfterState);

finally

FastMM4.PopAllocationGroup();

FastMM4.SetMMLogFileName(nil) // calls SetDefaultMMLogFileName();

end;

{$endif FullDebugMode}

{$endif FastMM}

end;

end);

end;

class function TLogMemoryStatesHelper.LogMemoryStates(const AState: string; const ALogMethod: TLogMethod): TLogMemoryStates;

begin

ALogMethod(AState, []);

Result.MemoryManagerUsageSummary := TMemoryManagerUsageSummary.GetMemoryManagerUsageSummary();

Result.MemoryManagerState := TMemoryManagerState.GetMemoryManagerState();

Result.WindowsProcessorAndMemoryStatus := TWindowsProcessorAndMemoryStatus.GetWindowsProcessorAndMemoryStatus();

ALogMethod('%s %s: %s.', ['Memory manager summary', AState, Result.MemoryManagerUsageSummary.ToString()]);

ALogMethod('%s %s: %s.', ['Memory manager state', AState, Result.MemoryManagerState.ToString()]);

ALogMethod('%s %s: %s.', ['Windows process and memory state', AState, Result.WindowsProcessorAndMemoryStatus.ToString()]);

end;

class procedure TLogMemoryStatesHelper.LogMemoryStatesBeforeAndAfter(const AState: string; const ALogMethod: TLogMethod; const AMethod: TProc);

var

Before: TLogMemoryStates;

After: TLogMemoryStates;

AfterState: string;

BeforeState: string;

begin

BeforeState := SBefore + ' ' + AState;

Before := LogMemoryStates(BeforeState, ALogMethod);

try

AMethod();

finally

AfterState := SAfter + ' ' + AState;

After := LogMemoryStates(AfterState, ALogMethod);

{TODO -ojwp -cFeature : log the diff }

end;

end;

initialization

ModuleInit();

end.

view raw

MemoryManagerUnit.pas

hosted with ❤ by GitHub

Read More

New Delphi Community Edition 10.4 Available

  

As announced yesterday with the blog post Delphi & C++Builder FREE Community Editions Updated to Version 10.4.2 Are Now Available, Embarcadero has moved the free CE license of its two IDEs, Delphi and C++Builder, to the last version of the product, 10.4 Sydney. The Community Edition is a free edition for non-professional developers (read the FAQ or the EULA for more information) and anyone who wants to start using one of the 2 products to learn them.
Being a complete product aligned to the Professional Edition of Delphi or C++Builder, it allows building applications for desktop (Windows and also macOS for Delphi) and mobile (Android and iOS). The CE edition includes both the VCL and the FMX UI libraries, but also the core RTL and database libraries, web technologies and more.
If you are interested, you can download 10.4 CE and also sign up for the “summer camp” to learn more about the technology of these CE editions and other Embarcadero tools:

Download Delphi CE

Download C++Builder CE

Signup for the summer camp

I’ve very happy we are making the 10.4 version of Delphi and C++Builder available as a free Community Edition tools, as I’m certain a lot of people will leverage the opportunity to learn about our technology, both in terms of programming languages and libraries. If you are new to it, Delphi is a lot of fun to learn and a very powerful natively compiled language, worth having a deep look.

Read More

Writing components for TMS WEB Core

Unfortunately, at this time we do not yet have a seminal book like Danny Thorpe’s book “Delphi Component Design” (https://www.amazon.com/Delphi-Component-Design-Danny-Thorpe/dp/0201461366) for component design for TMS WEB Core. It is definitely on our todolist to write a book on TMS WEB Core component design, but until this happened, there are already a couple of resources that we wanted to bundle here that can help you out.

Component types

There are actually different component types in TMS WEB Core and depending on your needs, you might select the best type that fits.

– Components build from HTML elements 

In this component type, the component is rendered from HTML elements, like DIV, BUTTON, INPUT, IMG, SELECT, … To render the component, we basically create such HTML elements in the DOM and attach via Object Pascal code technically JavaScript event handlers to the elements and manipulate the HTML element attributes, content & style properties. 
A good starting point to develop this type of components is the blog article https://www.tmssoftware.com/site/blog.asp?post=449 
As most components included in the TMS WEB Core framework are of this type, the source code of the framework is of course also a great resource to learn from.

– Components similar to VCL controls that are based on painting on a canvas

A VCL custom control is basically a class that will paint itself and that will react to user interface inputs like mouse, keyboard, touch… The painting in a VCL control is done via drawing on the TCanvas. Well, the good news is that this component model also exists in TMS WEB Core. It can provide an easy way to port existing VCL UI’s to the web. We in fact did this with Tetris game original VCL Delphi code we found for this. You can find all the information in this blog article https://www.tmssoftware.com/site/blog.asp?post=446. Delphi developers should be quickly familiar developing this kind of custom control by descending from the TGraphicControl class and write overrides for OnMouseDown/OnMouseMove/OnMouseUp or OnKeyDown/OnKeyPress/OnKeyUp and also override the Paint method. The TCanvas class in TMS WEB Core has the same interface as TCanvas in VCL, so you will be quickly familiar using a Brush/Pen and methods like LineTo(), Rectangle(), Draw(), …

– FNC Components
Use the FNC component architecture and develop the component as FNC component that will also work in a web application.
This is another approach and comes with the additional benefit that your custom control will not only work in a TMS WEB Core web client application but also in a VCL Windows application or a Firemonkey cross platform application. And it will also work in the free Lazarus IDE with the LCL framework. Component development for FNC is quite similar to VCL or FMX component development. One basically also overrides methods for UI inputs such as keyboard, mouse and touch and performs the drawing of the control. A difference for the drawing is that this is done via the TTMSFNCGraphics context (and not the TCanvas like in VCL). The TTMSFNCGraphics is an abstraction layer that works in all supported frameworks with the same code. A good starting point for beginning to develop FNC custom controls is this blog article https://www.tmssoftware.com/site/blog.asp?post=346
Other resources
Of course, TMS WEB Core full versions ships with the full framework source code, so it is a great learning resource for writing components. In addition to this, we have published several open-source components you can directly download and not only use and learn from. You can find these components at https://www.tmssoftware.com/site/webpartners.asp
And of course, last but not least, there is always the TMS team that is available to answer your question when you get started to develop components!

Get started

So, even though there is not yet a book, there is already various information to get you started! We look forward to learn about your component development experiences and if you have interesting components you wish to share with fellow Delphi developers, you can always submit these. Authors with published components under https://www.tmssoftware.com/site/webpartners.asp become recognized partners and get a free TMS WEB Core license that can be used in Delphi, Lazarus or Visual Studio Code.

Read More

Read More

TMS WEB Core v1.8 Sirolo beta available

TMS WEB Core v1.8 beta is here

It was of course highly anticipated and awaited, this new version of TMS WEB Core v1.8 named Sirolo. Sirolo is a small but beautiful town along the Italian coast at the Adriatic sea. After city Ancona (name of TMS WEB Core v1.7 release), it is a nearby little town the Mille Miglia race in 1955 crossed.

Other than the reference to this beautiful place, what does TMS WEB Core v1.8 brings on the table for Delphi developers:

1) Miletus support for macOS and Linux

After Miletus targeted Windows in TMS WEB Core v1.7, our team now accomplished the phenomenal next step to create cross-platform web technology based desktop applications for Windows, macOS and Linux. And all that with a minimal footprint. On Windows and Linux this means single executable file apps and for macOS the typical application folder but also here with a single application executable file. Deployment can be done via ultra simple XCOPY deployment.
In a nutshell, you can create desktop applications from a single source code base from your Delphi IDE on Windows. You do actually not need an Apple mac machine or Linux box to create these applications. The GUI of these applications is rendered in the browser and therefore empowered by HTML5 and CSS3. You can take advantage of existing web application templates to create wonderfull GUIs. 
Not familiar with Miletus? You can learn all about it from this webinar replay

2) Extended Miletus local databases support

In this new version, we added two more possible local databases that can be directly used from a Miletus application and that is Interbase and Firebird. No need here to create a REST API interface, you can directly connect a client dataset to local databases. In v1.8 this is now: MS Access, MS SQL, mySQL, SQLite, Interbase and Firebird. 
            

3) Miletus support to access INI files and registry

Convenient for storing application settings, INI files or registry is what developers frequently use. So, from a Miletus app you can now easily access these INI files and the registry in the same way as you can from a VCL or Firemonkey application. Of course, there is no registry on macOS or Linux and here there is automatic fallback on INI files.

4) Two new UI components

We have added two new controls to TMS WEB Core v1.8. This is a chatbox control and a rating panel. With the TWebChatbox control, you can write web chat applications with little to no code. Drop this component on the form and all you need to do is write some lines of code to send the message to your chat server of choices or receive messages from there. The other new component is the TWebRatingPanel. This is the classic UI pattern of rating via clicking a number of stars (or other icons of choice). The rating panel offers setting rates in units of 1, 0.5 or fully fractional.

5) SHA1 encryption support in the TWebCrypto component

As SHA1 is often needed in communication protocols, we extened the TWebCrypto component that is internally using the browser crypto API to also offer SHA1 support.

6) Lots of smaller improvements and new features

Based on your feedback and that of our team, we did numerous smaller improvements to the components, we further polished the Object Pascal to JavaScript compiler and integrated the latest pas2js RTL.

Availability

TMS WEB Core v1.8 Sirolo beta is now available for all TMS ALL-ACCESS users with an active license. Our team is eager to learn about your experiences with this new version as well as about how work is going on your projects based on TMS WEB Core. If you have interesting projects that near completion, don’t hesitate to get in touch for creating together a nice story about your experiences and results with TMS WEB Core!

What’s next

First goals are to release TMS WEB Core v1.8 and at the same time bringing TMS WEB Core for Visual Studio Code on par with its Delphi companion by also integrating Miletus support. After that, our team is already researching key v1.9 new features. We can’t reveal much yet as there are several still uncertain technical challenges to tackle. We will of course keep you in the loop as soon as we feel sufficiently confident about these! 

Learn

Holidays are a good period to learn and get your feet wet with TMS WEB Core.
Didn’t have the time yet to explore the wonderful world of web development? The upcoming holiday period is the ideal time to broaden your horizons and start learning about TMS WEB Core. To help you, we can, while our stock lasts, offer  for European customers with a TMS WEB Core license purchase a free TMS WEB Core book written by our evangelist Holger Flick (books will be shipped from our main European office). If you want this book free, make sure to reserve your free copy via email and we’ll confirm availability and ship your book upon receipt of the license order. Hurry, our stock is limited!

Meanwhile, enjoy a great holiday period, why not at the gorgeous beaches of Sirolo in Italy, and with this, this blog article is full circle.

Read More

Read More

Delphi interface generic function – Is there a work around? – Stack Overflow

  

The documentation simply states “no”, without explaining why.
Luckily, there is [WayBack] David Heffernan at [WayBack] Delphi interface generic function – Is there a work around? – Stack Overflow
Interfaces do not support generic parameterized methods, as the compiler says.
There is no workaround because it’s a fundamental limitation. Parameterized methods in classes are implemented by adding one method per instantiation to the class. That works for classes since they are concrete, but is not viable for interfaces. That’s because interfaces are a table of functions and the size of that table cannot vary depending on which generic method instantiations happen to be present elsewhere in the code. For similar reasons, generic methods cannot be virtual or dynamic.

In any case, it’s not possible. One option is to use a class instead. I agree that this is a bind.
If you want to do it in an interface, the best you can do is:
function CastAs(const IID: TGUID): IInterface;
But you’d have to call it like this:
MyIntf := ProxyIntf.CastAs(IMyIntf) as IMyIntf;
which feels somewhat foul.
Choose your poison!
In the background, for every distinct use of generic method parameters, the compiler generates an overloaded concrete version of the method. That is possible for classes and records, but not for interfaces.
In practice, I either go for the last option, or have the interface expose a record or class type that can have the required generic method (so the compiler can generate the overloads at run-time).
All options feel somewhat ugly, so I like the comment by [WayBack] Jason:
Wish I could “Program into Your Language, Not in it” programmers.stackexchange.com/questions/2777/…
Which leads to Code Complete
[WayBack] What are the key points of Code Complete? – Software Engineering Stack Exchange answer by [WayBack] limist:
Code Complete is about software craftsmanship; it is an advanced-beginner/intermediate-level book, written for the working programmer, but it would still be very useful to someone who’s been programming for at least a year.
Thus the key points of Code Complete (2nd ed.) are nicely summarized in its Chapter 34, Themes in Software Craftsmanship. As paraphrased from my notes:

Conquer Complexity: reduce the cognitive load on your mind via discipline, conventions, and abstraction.
Pick Your Process: be conscious of quality from start (requirements) to finish (deployment) and beyond (maintenance).
Write Programs for People First, Computers Second: code readability is hugely important for comprehensibility, review-ability, error-rate, error-correction, modifiability, and the consequent development time and quality.
Program into Your Language, Not in it: think of the What? and Why? before the How?
Focus Your Attention with the Help of Conventions: conventions manage complexity by providing structure where it’s needed, so that the ultimate resource – your attention – can be effectively used.
Program in Terms of the Problem Domain: work at the highest level of abstraction possible; top-level code should describe the problem being solved. Distinguish OS level, programming language level, low-level implementation structures, low-level problem domain terms, and finally, high-level problem-domain terms that would make total sense to the (non-coder) user.
Watch for Falling Rocks: as programming merges art and science, good judgement is vital, including heeding warning signs.
Iterate, Repeatedly, Again and Again: iterate requirements, design, estimates, code, code tuning.
Thou Shalt Render Software and Religion Asunder: be eclectic and willing to experiment. Don’t be an inflexible zealot, it precludes curiosity and learning. Go beyond having just a hammer in your toolbox.

But the most important take-aways are in Chapter 33, Personal Character: once you consciously seek to improve as a coder, you can and will. The fastest way to do so is to take on the the attitudes of master coders(humility, curiosity, intellectual honesty, discipline, creativity), while also practicing their habits (many good habits are listed in the book, e.g. choosing good variable/value names).
Also, the book makes clear that the gap between average and excellent in software is immense; that fact alone should drive the conscientious coder to better himself.
That’s the short of it; the long version is in the book. 🙂 I can also send you my not-so-long, not-so-short notes if you want more details. But the book is certainly money and time well-spent, even if the writing style is tiresome at times.
Beyond Code Complete, I’d highly recommend The Pragmatic Programmer. It’s for intermediate-level programmers, nicely-written and a great mix of high, medium, and low-level advice.
There are more limitations on generics in Delphi
The documentation on limitations has not changed much since Delphi 2009.
Delphi 2009
[WayBack] Overview of Generics: Platform Requirements and Differences

Generics are supported by the Delphi for Win32 compiler.
Runtime type identification (RTTI)
In Win32, generics and methods do not have RTTI, but instantiated types do have RTTI. An instantiated type is the combination of a generic with a set of parameters.
Interface GUID
In Win32, an instantiated interface type does not have an interface GUID.
Parameterized method in interface
A parameterized method (method declared with type parameters) cannot be declared in an interface.
Instantiation timing
Instantiation is processed by the compiler. All instantiated objects are emitted into .obj files.
Dynamic instantiation
Dynamic instantiation at runtime is not supported.
Interface constraints
The Win32 interface is not a “light” interface. This means all type parameters with interface constraints always support the COM IUnknown methods _AddRef_Release and QueryInterface or inherit from TInterfacedObject. Record types cannot specify an interface constraint parameter.

Delphi 10.3 Rio
[WayBack] Overview of Generics – RAD Studio: Platform Requirements and Differences
Generics are supported by the Delphi compilers.
Run-time type identification
In Win32, generics and methods do not have run-time type information (RTTI), but instantiated types do have RTTI. An instantiated type is the combination of a generic with a set of parameters. The RTTI for methods of a class is a subset of the RTTI for that class as a whole. If a non-generic class has a generic method, that method will not be included in the RTTI generated for the class because generics are instantiated at compile time, not at run time.
Interface GUID
In Win32, an instantiated interface type does not have an interface GUID.
Parameterized method in interface
A parameterized method (method declared with type parameters) cannot be declared in an interface.
Instantiation timing
Generic types are instantiated at compile time and emitted into executables and relocatable files. Instance variables of a generic type are instantiated at run time for classes and at compile time for generic records. The RTTI for generic classes is only generated when the classes are instantiated. RTTI for instantiated classes follows just as for non-generic classes. If the generic class has a generic method, then the instantiated generic class will not have RTTI for that generic method.
Dynamic instantiation
Dynamic instantiation at run time is not supported.
Interface constraints
The Win32 interface is not a “light” interface. This means all type parameters with interface constraints always support the COM IUnknown methods _AddRef_Release, and QueryInterface or inherit from TInterfacedObject. Record types cannot specify an interface constraint parameter.
–jeroen

Read More

“No mapping for the Unicode character exists in the target multi-byte code page”

  

Usually when I see this error [Wayback] “No mapping for the Unicode character exists in the target multi-byte code page” – Google Search, it is in legacy code that uses string buffers where decoding or decompressing data into.
This is almost always wrong no matter what kind of data you use, as it will depend in your string encoding.
I have seen it happen especially in these cases:

base64 decoding from string to string (solution: decode from a string stream into a binary stream, then post-process from there)
zip or zlib decompress from binary stream to string stream, then reading the string stream (solution: decompress from binary stream to binary stream, then post-process from there)

Most cases I encountered were in Delphi and C code, but surprisingly I also bumped into C# exhibiting this behaviour.
I’m not alone, just see these examples from the above Google search:

[WayBack] C# metro No mapping for the Unicode character exists in the target multi-byte code page – Stack Overflow
[WayBack] No mapping for the Unicode character exists in the target multi-byte code page – The file must be in UTF-8 or UTF-16 encoding. :: WinSCP
[WayBack] file io – WinRT No mapping for the Unicode character exists in the target multi-byte code page – Stack Overflow
[WayBack] No mapping for the Unicode character exists in the target multi-byte code page · Issue #110 · VBA-tools/VBA-Web · GitHub
Fixed issues:

[WayBack] Error while import file · Issue #566 · HeidiSQL/HeidiSQL · GitHub
[WayBack] EEncodingError: No mapping for the Unicode character in target multi-byte code page · Issue #515 · HeidiSQL/HeidiSQL · GitHub

[WayBack] No mapping for the Unicode character exists in the target multi-byte code page ? (Delphi XE6) – Stack Overflow (tries to decompress a string)
[WayBack] android – delphi xe6 No mapping for the Unicode character exists in the target multi-byte code page – Stack Overflow (tries to always download HTTP with the same encoding, despite it can have any encoding)
[WayBack] delphi – What could cause “No mapping for the Unicode character exists in the target multi-byte code page”? – Stack Overflow (which was a bug in the Delphi RTL method TFile.AppendAllBytes which is still present in Delphi 10.2 Tokyo, and likely newer versions)
Two artifacts of the same resolved bug in the DevExpress ExpressRichEdit Control (they do fix their bugs and release fixed versions soon):

[WayBack] T663543 – Saving document as UTF-8 plain text doesn’t write the preamble | DevExpress Support Center
[WayBack] T664497 – The “No mapping for the Unicode character exists in the target multi-byte code page” error occurs on loading a TXT file that doesn’t have an encoding preamble | DevExpress Support Center

–jeroen

Read More