Coding in Delphi and Beyond - Thoughts from David Cornelius
http://corneliusconcepts.tech/
enA "Simple" Christmas Tree App
http://corneliusconcepts.tech/simple-christmas-tree-app
<span property="dc:title">A "Simple" Christmas Tree App</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-12-31T04:15:21+00:00" datatype="xsd:dateTime">Tue, 12/30/2025 - 20:15</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><a href="/sites/default/files/inline-images/ChristmasTreeCat.png"><img alt="Christmas Tree in danger with cat" data-entity-type="file" data-entity-uuid="01686066-89ab-4dc1-a2f7-401ef03c5173" hspace="5" src="/sites/default/files/inline-images/ChristmasTreeCat.png" width="350" class="align-right" /></a></p>
<h2>The Initial Experiment</h2>
<p>After some discussions on <a href="https://en.delphipraxis.net/topic/14788-vibe-coding-with-delphi-when-artificial-intelligence-becomes-your-pair-programmer/">Delphi-PRAXiS</a>, I decided to try an experiment to see how much of a Delphi application could be written purely with AI. I used Claude Code, and since it was a couple of days before Christmas when I started, asked it to write a cross-platform app that displayed a Christmas tree with ornaments that would fall and break, thinking I would submit the finished app a day before Christmas.</p>
<p>The program includes several images: a Christmas tree background and, for each ornament, two images, one intact while hanging on the tree and one broken after it's crashed on the floor. There are also a few crash sound files I <a href="https://pixabay.com/sound-effects/search/glass%20crash/">found online</a>; a random one is chosen each time.</p>
<p>Well, I got the Windows version working fairly quickly with only a little back-and-forth with Claude and without touching a line of code. I created the Christmas tree background and the ornaments in <a href="https://www.artspace.ai/lifetime?ref=delphidave">Artspace.ai</a>. After converting the crash sounds to the right format for each platform, I got busy testing on various devices. The finished app is on <a href="https://github.com/corneliusdavid/ChristmasTree">Github</a>.</p>
<h2>Lesson 1: Playing Sound Files on Windows</h2>
<p>I wanted the crash sound to happen after the bulb started falling and play immediately when it hit the floor and switched to the "broken" image. But one thing I noticed right away when testing on Windows was a significant delay when the sound played. Claude had written it to use the <code>TMediaPlayer</code> component, which made sense to me as this is a cross-platform media player in Delphi, but nothing I tried enabled a smooth transition from falling, to crash sound, to crash image. The ornament would fall, then disappear just before it hit the floor while it loaded and played the crash sound, then switch to the crash image, a terribly slow transition.</p>
<p>After some research and looking at other sample applications, I realized that for Windows (and Windows only), I should <em>not</em> use the <code>TMediaPlayer</code> for this but instead call <strong><code>sndPlaySound</code></strong> from the <code>WinAPI</code> unit.</p>
<p>I told Claude about this and it agreed and happily changed the code for me. Switching to this was a vast improvement; I'd probably need to implement threading and some intricate timing to get it perfect but this was supposed to be just a simple experiment of vibe coding and the result was close enough to be satisfying.</p>
<p>Later testing revealed the <code>TMediaPlayer</code> component worked fine for the other platforms.</p>
<h2>Lesson 2: Device File Locations</h2>
<p>Once the application concept was proven and working on the default Windows platform, I turned to the first mobile platform. Since I use an Android phone, it's most handy so started up wireless debugging, connected via <code>adb</code>, and deployed the project. I immediately got an error telling me that it found "unsealed contents present in the bundle root". After some research I figured out that unsigned files were in a location they shouldn't be; basically, I had not put the external files in the right folder.</p>
<p>For Windows programming, we've had the "luxury" (and sometimes plague) of being able to put and access files wherever we want. I had simply let Claude keep the sound and image files in the root of the project instead of moving them to a folder (after all, this was just a simple program; a larger, "real-life" application would be organized with layers of hierarchical folders). But mobile apps are much more restrictive; there are certain paths where external files are allowed and expected. On Android, these files are typically accessed from the application's <code>assets\internal</code> folder, which is returned as the "Documents" folder in Delphi code (more on this below).</p>
<p>Before continuing the story with mobile platforms, let me digress to the other "desktop" type platform. Deploying to a Mac desktop is second easiest (after Windows) because it doesn't have some of the limitations or screen size constraints that hand-held devices do. After hooking up to the Platform Assistant Server on my old Intel-based Mac and trying a build, I got the same "unsealed contents present in the bundle root" error I had when I first attempted to deploy to my Android phone.</p>
<p>The source of the problem was similar in both instances but required a different deployment strategy. Both Windows and Mac OSX can access files from the "application path" but the Mac compresses supporting files into a delivered bundle and when extracted, a "virtual" application path is created in <code>Contents\MacOS</code>. After adding files to the project's Deployment Manager interface (<em>Project</em> > <em>Deployment</em>), I had the Mac OSX version working as well.</p>
<p>Remember how the <code>assets\internal</code> path on an Android is returned as the standard "Documents" path? I wanted something similar for the iOS platform to keep the code consistent. Using Delphi's Deployment Manager, I had a great deal of difficulty getting the files into the right location, which should be <code>Startup\Documents</code> on iOS. Finally, I found and used the <a href="https://blogs.embarcadero.com/learn-about-a-powerful-delphi-firemonkey-deployment-manager-tool/">Grijjy Deploy Manager</a> to rewrite the <code>.dproj</code> file and it worked beautifully! (Why setting that same path in Delphi's Deploy Manager wouldn't work is a frustrating mystery.)</p>
<p>To summarize, here are the destination folders for supplemental files on each platform:</p>
<ul>
<li>Windows: Application Path</li>
<li>Mac OSX: Application Path (Virtualized)</li>
<li>Android: <code>assets\internal</code></li>
<li>iOS: <code>Startup\Documents</code></li>
</ul>
<p>On Windows, there's a Build Event to copy the files out to the output folder. For the other three platforms, the files must be listed in the Project Deployment window. As mentioned above, the <a href="https://github.com/grijjy/GrijjyDeployMan">Grijjy Deploy Manager</a> can set up those files for Android and iOS, but you have to add the ones for the Mac OSX desktop yourself using the Delphi interface.</p>
<p>The code to reference these folders on the various platforms is simple, thanks to platform-agnostic functions that provide simple access to the platform-specific folders for each platform; these functions are in the <code>System.IOUtils</code> unit. Thus, the function Claude wrote (and I modified) that returns the correct path for each platform is simply this:</p>
<pre>
<code>function TMainForm.GetResourcePath: string;
begin
{$IF DEFINED(ANDROID) OR DEFINED(IOS)}
// Android: resources in assets/internal folder
// iOS: resources in app's documents
Result := TPath.GetDocumentsPath;
{$ELSEIF DEFINED(MSWINDOWS) OR DEFINED(OSX)}
// Windows: resources in same directory as executable
// Mac OSX: resources in Content\MacOS "virtual" app path
Result := TPath.GetAppPath;
{$ELSE}
{$MESSAGE FATAL 'Unsupported platform'}
{$ENDIF}
end;
</code></pre>
<p>The key statements are where it assigns the <code>Result</code>: <code>GetAppPath</code> on Windows and Mac OSX, and <code>GetDocumentsPath</code> for Android and iOS, basically the Application path for desktop applications and the Documents path for mobile devices.</p>
<p>I could have used <code>GetDocumentsPath</code> for all platforms; on Mac OSX, the Deployment settings would've been a little different, for Windows, those files would be mixed with hundreds of other files in my general "Documents" folder, not ideal.</p>
<h2>Lesson 3: New Apple Signing Requirements</h2>
<p>Admittedly, I don't write apps for the Apple platforms very often; I write mostly for Windows and sometimes my Android phone. In the past, to generate the proper signing ID to prepare an iOS app, I could create a plain "Hello World" app in XCode on the Mac (using their default template without any code), give it a unique name, then use that in my Delphi project to code-sign the files. That no longer works, I needed to renew my Apple Developer account to properly sign and build even a simple app.</p>
<p>After paying the fee, waiting until the account was refreshed (the next day), and creating an identifier for the app, I was finally able to get the app working on my iPad.</p>
<h2>Summary</h2>
<p>There were several other small lessons along the way in the course of coding, conversing with AI, and general configuration and testing of the various platforms. There were also several delays due to the holidays, family get-togethers, and whatnot; so, of course, I didn't get this out before Christmas. But it's not quite New Year's yet and many people (who participate in this particular Christmas decoration) still have their Christmas tree up, so I hope it's still somewhat relevant in that respect.</p>
<p>Originally, this was going to take just a few hours and I was going to leave a paragraphical comment about the experience on the forum. I'm still impressed by what Claude Code accomplished but it turned into much more of a learning opportunity while I had some time off work.</p>
<p>The main reason for this blog, then, is to document some of the tripping points for my future self. I need to write some other cross-platform apps and I want to keep my skills fresh. I also realized that I had not played sound files before, or dealt with external files before other than a single database file in the standard "Documents" folder when I wrote some sample apps for a <a href="https://corneliusconcepts.tech/i-wrote-book">book</a> a few years ago.</p>
<p>I hope this helps someone else, too. And if there's something here which I've written that could've been done differently or if there's an assumption I made that isn't quite right, please leave me a comment. I'm always open to learning a better way to do things!</p>
</div>
<section id="node-comment">
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=200&2=comment_node_blog&3=comment_node_blog" token="CguhFyApblCiIABXQL6ywDk-91Pf663FSaAfAxz5ikg"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Wed, 31 Dec 2025 04:15:21 +0000david200 at http://corneliusconcepts.techhttp://corneliusconcepts.tech/simple-christmas-tree-app#commentsFun with Ruby
http://corneliusconcepts.tech/fun-ruby
<span property="dc:title">Fun with Ruby</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-12-19T07:00:00+00:00" datatype="xsd:dateTime">Thu, 12/18/2025 - 23:00</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><a href="/sites/default/files/inline-images/ruby.png"><img alt="ruby diamond" data-entity-type="file" data-entity-uuid="3aec29f8-94c6-4c5e-b645-7458b5bb4276" hspace="5" src="/sites/default/files/inline-images/ruby.png" width="200" class="align-left" /></a>Last December, I discovered <a href="https://adventofcode.com/">Advent of Code</a>, daily programming puzzles during the first part of month, and wrote <a href="https://github.com/corneliusdavid/AdventOfCode2024">my solutions in Delphi</a>. When this year's challenge started, I decided to use a different programming language as an excuse to learn something new. I've had the book "<a href="https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/">Seven Languages in Seven Weeks</a>" for a while and never even got through the first chapter (it was always something I <em>should read someday</em>). So I finally picked it up and told myself I would use the very first language in that book, no matter what it was (I honestly didn't remember from when I had glanced through it several months earlier). It turns out the first language it discusses is <a href="https://www.ruby-lang.org/en/">Ruby</a>! Thus started my adventure in learning!</p>
<p>The book is not very big and it's not meant to be an exhaustive tutorial, so I signed up for a <a href="https://www.udemy.com/course/comprehensive-ruby-programming-tutorial/?couponCode=CM251218G1">Udemy course</a> and quickly went through as much I figured I needed to get started and dove into the AoC challenges. Presented here is my solution to <a href="https://adventofcode.com/2025/day/1">Day 1 of AoC 2025</a> in two programs, one in Ruby and the other in Delphi, structured as nearly the same as possible so you can see the differences.</p>
<h3>The Simple Challenge: Rotate a Dial</h3>
<p>Advent of Code 2025's Day 1 challenge involves simulating a dial that rotates left and right, tracking when it lands on or passes through zero. This seemingly simple problem reveals fundamental differences in how Delphi and Ruby approach software design.</p>
<h2>Class Structure: Explicit vs. Implicit</h2>
<h3>Delphi's Interface/Implementation Separation</h3>
<pre>
<code class="language-pascal">type
TDialPassword = class
private
const
DIAL_MAX = 99;
var
FCurrPos: Longint;
FZeroCount: Longint;
FZeroPassive: Longint;
FLogit: Boolean;
public
constructor Create;
procedure Rotate(const Amount: Integer);
property Logit: Boolean read FLogit write FLogit default True;
property ZeroCount: Longint read FZeroCount write FZeroCount;
property ZeroPassive: Longint read FZeroPassive write FZeroPassive;
end;
{ TDialPassword }
constructor TDialPassword.Create;
begin
FZeroCount := 0;
FZeroPassive := 0;
FCurrPos := 50;
FLogit := True;
end;</code></pre>
<p>Delphi enforces a clear contract: declare everything up front, implement it separately. This separation aids readability in large projects and enables circular unit references, but requires more boilerplate code.</p>
<h3>Ruby's Unified Definition</h3>
<pre>
<code class="language-ruby">class DialPassword
attr_accessor :zero_count, :zero_passive, :logit
def initialize
@zero_count = 0
@zero_passive = 0
@curr_pos = 50
@DIAL_MAX = 99
@logit = true
end
...
end</code></pre>
<p>Ruby combines declaration and implementation. The <code>attr_accessor</code> line creates both getters and setters in one statement; it's roughly the same as Delphi's public property declarations. Instance variables (prefixed with <code>@</code>) spring into existence when first assigned. This brevity can be refreshing for small programs but may obscure the class interface in larger codebases.</p>
<h2>Type Safety: Compile-time vs. Runtime</h2>
<h3>Delphi's Type Declarations</h3>
<pre>
<code class="language-pascal">var
FCurrPos: Longint;
FZeroCount: Longint;
FZeroPassive: Longint;
FLogit: Boolean;
procedure Rotate(const Amount: Integer);</code></pre>
<p>Every variable has a declared type in Delphi (except for <a href="https://docwiki.embarcadero.com/RADStudio/Florence/en/Inline_Variable_Declaration#Type_Inference_for_Inlined_Variables">inlined variables with type inference</a>, which still happen at compile-time). The compiler catches type mismatches before runtime. Using <code>const Amount: Integer</code> prevents <code>Amount</code> from being anything other than an Integer. This is a nice safety net if you inadvertenly misspell or pass in a wrong variable but annoying if you're accustomed to languages that automatically convert data to a common type.</p>
<h3>Ruby's Dynamic Typing</h3>
<pre>
<code class="language-ruby">def rotate(amount)
@curr_pos += amount
# ...
end</code></pre>
<p>Ruby infers types at runtime. The <code>amount</code> parameter could be an integer, float, or even a string (which would cause a runtime error). This flexibility enables rapid prototyping and simplifies the code you type but shifts error detection from compile-time to runtime which can be harder to track down.</p>
<h2>The Core Algorithm: Similar Logic, Different Expression</h2>
<p>Both implementations share the same rotation logic, but express it quite differently:</p>
<h3>Delphi's Rotate Method</h3>
<pre>
<code class="language-pascal">procedure TDialPassword.Rotate(const Amount: Integer);
begin
// Special case for left rotation from 0
if (Amount < 0) and (FCurrPos = 0) then
Inc(FCurrPos, DIAL_MAX + 1);
Inc(FCurrPos, Amount);
while (FCurrPos < 0) or (FCurrPos > DIAL_MAX) do begin
if FCurrPos < 0 then begin
Inc(FZeroPassive);
Inc(FCurrPos, DIAL_MAX + 1);
end else begin
if FCurrPos > (DIAL_MAX + 1) then
Inc(FZeroPassive);
Dec(FCurrPos, DIAL_MAX + 1);
end;
end;
if FCurrPos = 0 then
Inc(FZeroCount);
end;</code></pre>
<h3>Ruby's rotate Method</h3>
<pre>
<code class="language-ruby">def rotate(amount)
# Special case for left rotation from 0
@curr_pos += (@DIAL_MAX + 1) if amount < 0 && @curr_pos == 0
@curr_pos += amount
while @curr_pos < 0 || @curr_pos > @DIAL_MAX
if @curr_pos < 0
@zero_passive += 1
@curr_pos += (@DIAL_MAX + 1)
else
@zero_passive += 1 if @curr_pos > (@DIAL_MAX + 1)
@curr_pos -= (@DIAL_MAX + 1)
end
end
@zero_count += 1 if @curr_pos == 0
end</code></pre>
<p>Notice Ruby's statement modifiers (<code>if</code> at the end of lines) which I thought was interesting and sounded like an English statement in the form, "Only do this if this expression is true." Delphi's <code>Inc</code> and <code>Dec</code> procedures are more explicit about varaible modification but I like Ruby's <code>+=</code> and <code>-=</code> operators better (personal preference stemming from studying C/C++ in college and having fun writing very terse code).</p>
<h2>I/O: Files and Output</h2>
<h3>Delphi's File Reading and Array creation</h3>
<pre>
<code class="language-pascal">var data := TFile.ReadAllLines('..\..\input01.txt');</code></pre>
<p>By default, Delphi compiles programs to a sub-folder underneath the current project folder based on the platform and bitness, so to get to the same input file as the Ruby program in the same project folder, I had to reference it up a couple of levels.</p>
<p><code>ReadAllLines</code> is a handy function in the <code>IOUtils</code> unit that returns a <code>TArray<string></code> which is the inferred type assigned to the variable <code>data</code> at compile time.</p>
<h3>Ruby's File Reading and array creation</h3>
<pre>
<code class="language-ruby">data = File.read("input01.txt").split("\n")</code></pre>
<p>Ruby chains methods naturally: read the file, split on newlines, saving the result into an array of strings.</p>
<h2>String Processing and Control Flow</h2>
<h3>Delphi's Main Loop</h3>
<pre>
<code class="language-pascal">dp.Logit := Length(data) < 100;
for var s in data do begin
var amount := StrToInt(Copy(s, 2, 10));
dp.Rotate(if s[1] = 'L' then -amount else amount);
end;</code></pre>
<p>Delphi has come a long ways to support enumerated types with inferred data types for iterating in loops like this. Still, there is so much more "scaffolding" (as I like to call it) and explicit type conversion necessary that the code can look a little murky. I do like the recent addition of the <a href="https://docwiki.embarcadero.com/RADStudio/Florence/en/Conditional_Operators_(Delphi)">If Conditional Operator</a> which makes the call to <code>Rotate</code> cleaner.</p>
<h3>Ruby's Main Loop</h3>
<pre>
<code class="language-ruby">dp.logit = data.length < 100
data.each do |elem|
amount = elem[1..].to_i
dp.rotate(elem[0] == 'L' ? -amount : amount)
end</code></pre>
<p>Ruby's <code>each</code> method with a block is quite unique. In fact, you can add <code>.with_index</code> and then it provides <em>two</em> variables inside the iteration loop (i.e. <code>|elem,i|</code>) which I thought was really cool (I didn't need it in this situation but there is another program my repository that does use that technique).</p>
<p>String slicing with <code>[1..]</code> implies the length of the array as the end of the range which is more concise than <code>Copy</code> where you have to explicitly define the length. The <code>to_i</code> method converts strings to integers, returning 0 for invalid input rather than raising an exception.</p>
<h2>Output and String Interpolation</h2>
<h3>Delphi's Formatted Output</h3>
<pre>
<code class="language-pascal">Writeln(Format('Number of zero positions encountered: directly = %d, passively = %d; total = %d',
[dp.ZeroCount, dp.ZeroPassive, dp.ZeroCount + dp.ZeroPassive]));</code></pre>
<p>Delphi's <code>Writeln</code> is the standard way to send simple text to the console. I had to use the <code>Format</code> function to provides embed variables in a similar manner to Ruby's default output.</p>
<h3>Ruby's String Interpolation</h3>
<pre>
<code class="language-ruby">puts "Number of zero positions encountered: directly = #{dp.zero_count}, passively = #{dp.zero_passive}; total = #{dp.zero_count + dp.zero_passive}"</code></pre>
<p>Using <code>puts</code> is the common output function for Ruby and implements string interpolation with <code>#{}</code>; this is arguably more readable than format strings, though it can make very long strings harder to maintain because they embed the variables directly in the string whereas Delphi's <code>Format</code> function declares place-holders that are filled in at run-time.</p>
<h2>Memory Management: Explicit vs. Automatic</h2>
<h3>Delphi</h3>
<pre>
<code class="language-pascal">var dp := TDialPassword.Create;
// No explicit Free needed here, but in a larger program you'd want:
// try
// ... use dp ...
// finally
// dp.Free;
// end;</code></pre>
<h3>Ruby</h3>
<pre>
<code class="language-ruby">dp = DialPassword.new
# Automatic garbage collection handles cleanup</code></pre>
<p>Delphi requires explicit object creation and (usually) destruction. Ruby's garbage collector eliminates this concern but at the cost of less predictable performance.</p>
<h2>Key Comparisons</h2>
<ol>
<li>
<p><strong>Verbosity vs. Brevity</strong>: The Delphi version is nearly twice as long as the identical functionality in Ruby, partly because of the need to have an <code>interface</code> section and Delphi's standard paradigm of declaring private variables behind the public properties. Each approach has merit: Delphi's verbosity adds clarity and maintainability; Ruby's brevity aids rapid development.</p>
</li>
<li>
<p><strong>Compile-Time vs. Runtime</strong>: Delphi catches errors early; Ruby provides flexibility.</p>
</li>
<li>
<p><strong>Explicit vs. Implicit</strong>: Delphi makes everything visible (types, memory management, property access). Ruby hides complexity behind conventions.</p>
</li>
<li>
<p><strong>Performance vs. Productivity</strong>: Delphi compiles to native code with predictable performance. Ruby prioritizes developer happiness and speed of development.</p>
</li>
</ol>
<h2>Conclusion</h2>
<p>Being exposed to the Ruby programming language and environment and using very concise code to solve problems got me to thinking about how to shorten what I do in Delphi. Perhaps I'll use inline variables and conditional expressions more often, chain objects with Spring4D's collection methods, or write helper functions for slicing strings.</p>
<p>Solving Advent of Code challenges in both languages revealed that Delphi and Ruby aren't competitors, they serve different purposes. Delphi's structure and safety make it ideal for large, long-lived applications. The fact it compiles to native executables is necessary for large-scale deployment and intellectual property protection. Ruby's expressiveness and flexibility make it great for exploration and scripting (and it's really fun to solve AoC puzzles in Ruby!).</p>
<p>For fellow Delphi developers: don't learn Ruby to replace Delphi. Learn it to become a better programmer who happens to use Delphi. The mental flexibility gained from switching between static and dynamic typing, between compiled and interpreted execution, and between explicit and implicit styles will make you more effective regardless of the language you're using.</p>
<p>After implementing dozens of Advent of Code solutions in both languages, I can confidently say that the time invested in learning Ruby has made me a better Delphi developer. And that's the real prize: not just knowing more languages, but thinking more broadly about problems and solutions.</p>
<p>You can find the full source for this and other Ruby programs I wrote in my <a href="https://github.com/corneliusdavid/AdventOfCode2025">Advent of Code 2025 Github repository</a>.</p>
</div>
<section id="node-comment">
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=199&2=comment_node_blog&3=comment_node_blog" token="n4KGYT0bw7ha8IJLGydGVSorzecYiZKTC21_ytQqnGs"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Fri, 19 Dec 2025 07:00:00 +0000david199 at http://corneliusconcepts.techFirst Look at WebBroker's Session Management
http://corneliusconcepts.tech/first-look-webbrokers-session-management
<span property="dc:title">First Look at WebBroker's Session Management</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-12-01T05:35:44+00:00" datatype="xsd:dateTime">Sun, 11/30/2025 - 21:35</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><h2>Web App Development in Delphi</h2>
<p>Delphi has had several ways to create web apps for a long time. Starting clear back in the late 1990s, you could use WebModules with action-based request handling to produce dynamic content using tag-based replacement events in TPageProducers; this was called <a href="https://docwiki.embarcadero.com/RADStudio/Florence/en/Using_Web_Broker_Index">WebBroker</a> and is still a part of Delphi today. Other techniques sprang up, some expanding on the <a href="https://docwiki.embarcadero.com/RADStudio/Florence/en/Indy">Indy</a> components (such as <a href="https://www.atozed.com/intraweb/">IntraWeb</a>), or providing tools or components to transpile Pascal to JavaScript (such as Elevate Software's <a href="https://www.elevatesoft.com/products?category=ewb">Elevate Web Builder</a> or FMSoft's <a href="https://www.unigui.com/">UniGUI</a> or TMS Software's <a href="https://tmssoftware.com/site/tmswebcore.asp">TMS WebCore</a>). Other products hijacked the output of VCL apps and turned them into HTML on-the-fly (such as Cybele Software's <a href="https://www.cybelesoft.com/thinfinity/virtualui/">Thinfinity VirtualUI</a>). There are also several popular open-source Delphi projects such as <a href="https://github.com/danieleteti/delphimvcframework">DelphiMVCFramework</a>, <a href="https://github.com/synopse/mORMot2">mORMot</a>, and <a href="https://github.com/andrea-magni/MARS">Mars Curiosity</a>. Jim McKeeth and Marco Cantu put out an excellent webinar back in 2019 detailing most of these; it was called, <a href="https://www.youtube.com/watch?v=ks4Q_W5kwn4">Evaluating Web Development Frameworks in Delphi</a> and nicely categorizes them between client-side and server-side technologies.</p>
<p>If you wanted to stick with the built-in WebBroker technology, you could only write sites that did not need authentication unless you wanted to handle session management yourself with cookies or long URLs. When Delphi 6 came out in 2001, WebSnap was added incorporating session management; but it required the more expensive Enterprise edition.</p>
<p>When WebSnap was removed from Delphi XE6 in 2014, the replacement was supposed to be DataSnap but its session management was more complicated because it was designed for persistent connections, not stateless web requests.</p>
<h2>Introducing Session Management</h2>
<p><img alt="Juggling several parts of web site sessions" data-entity-type="file" data-entity-uuid="2e555193-3e95-4a6b-bc2c-60676db439d2" src="/sites/default/files/inline-images/WebSessionMgr400.png" width="300" class="align-right" />So, what is web session management anyway and why is it necessary? HTTP is inherently stateless; each request from a browser to your web server is independent, with no built-in memory of previous interactions. Session management bridges this gap by creating a persistent context for each user across multiple page requests. Without it, users would need to re-authenticate on every page and shopping carts would empty between clicks. Websites without proper session management that save the state of user access on the server affect <em>all</em> current and future connected sessions; for example, if the <a href="https://github.com/corneliusdavid/webstencils-demo">WebStencils Demo</a> from last year were published and someone logged in, anyone who accessed the site from anywhere would now suddenly share the same login state and be able to see the customer list! No one would publish a website like this, of course.</p>
<p>Traditionally, web developers implement session management using a combination of cookies (or URL parameters) containing a unique session ID on the client side, paired with server-side storage (memory, database, or cache) to hold the actual session data. The challenge isn't just creating sessions, it's managing their lifecycle: secure generation of IDs, timeout handling, cleanup of abandoned sessions, preventing session hijacking, and scaling across multiple server instances. For Delphi developers coming from desktop applications where state is inherently maintained, or those who've only used WebBroker for stateless request-response APIs, implementing robust session management has been a significant hurdle that often pushed them toward third-party frameworks or other development tools.</p>
<p>At long last, Delphi 13 Florence <a href="https://docwiki.embarcadero.com/RADStudio/Florence/en/What%27s_New#WebBroker_Updates">added Session Management</a> to its WebBroker technology and it's available for <em>all editions</em>, not just Enterprise. By now, most people using Delphi for serious web app development are likely entrenched in one of the solutions mentioned previously that solved the problem of user authentication and authorization. But if you're new to Delphi or just now needing to build a web server with it, read on!</p>
<h3>Demo Projects</h3>
<p>Embarcadero has a wonderful, rich demo using WebStencils with WebBroker's new session management capabilities as an open-source <a href="https://github.com/Embarcadero/WebStencilsDemos">project on Github</a>. (Also included in the repository is a Firemonkey desktop app to let you play around with the scripting and variable-subsitution techniques of WebStencils.) While it is beautiful and works well, the breadth and complexity of this web app was a little overwhelming to me. What I wanted was a simple, no-frills explanation of how to use the new session-management components when building my own WebBroker app. It took me a while to parse out just those pieces, match them up with the sparse documentation, and build my own tests just to understand how this technology works.</p>
<p>What I ended up with is my own <a href="https://github.com/corneliusdavid/webstencils-session-demo">WebStencils Session Demo</a> project on Github, an an extension to the one I wrote a litte over a year ago that compared the new WebStencils framework with TPageProducers of the WebBroker base technology. As before, the project uses WebStencils and uses a form to request login information. But this time, session-management has been added to isolate the user state to a single browser. There are two types of authentication that will be discussed below; this project only uses form-based authentication. The same <code>Chinook.db</code> SQLite database and concepts of user authentication to display a list of customers is also still in place. In other words, it looks and feels nearly identical to last year's <a href="https://github.com/corneliusdavid/webstencils-demo">WebStencils Demo</a> to keep things simple and focus the changes solely on session management.</p>
<h3>The New Components</h3>
<p>The new components are all non-visual components you add to your application's WebModule:</p>
<ul>
<li><strong><code>TWebSessionManager</code></strong> manages <code>TWebSession</code> objects, which are returned from the new <code>Session</code> property of a <code>TWebRequest</code>. Each session contains <code>DataVars</code> and an associated <code>User</code>. The manager component provides properties for configuring where sessions are located, their scope, and other details which will be described below. Only one of these manager components is allowed per web module.</li>
<li><strong><code>TWebBasicAuthenticator</code></strong> or <strong><code>TWebFormsAuthenticator</code></strong> are two different ways to handle user authentication. The first one performs basic HTTP authentication which typically pops up a web browser window prompting for a username and password. The second allows you to create a custom HTML form with your own fields and verification methods to authenticate a user. This component contains links for the <code>HomeURL</code>, <code>LoginURL</code>, <code>LogoutURL</code>, and <code>FailedURL</code> to provide redirection based on the authorized state of the user. There are also event handlers that are triggered when a user is authenticated. Only one of these two authenticator components is allowed per web module.</li>
<li><strong><code>TWebAuthorizer</code></strong> provides a list of authorization zones associated with the authenticated user's security roles. Each zone defines a web path and a list of roles that are allowed to access that path.</li>
</ul>
<p>These components work together: the Manager maintains all sessions, the Authenticator validates users, and the Authorizer controls what authenticated users can access. Let's look at how these are used in the demos app, then we'll explain the components in greater detail.</p>
<h4>Disclaimer!</h4>
<p><em>I had significant difficulty getting authentication to work with the <code>TWebAuthorizor</code> component. Perhaps I didn't understand the documentation but in any case, I ended up removing the authorizer and implemented page access based solely on user roles and WebStencils session variables.</em></p>
<h3>TWebSessionManager</h3>
<p>This component introduces the concept of individually connected web sessions for WebBroker applications. Its properties allow you to define where session IDs are stored, how long they last, and their scope. I left them at defaults for this demo:</p>
<ul>
<li><code>IdLocation</code>: Where the session ID should be stored. The options are <code>ilCookie</code>, <code>ilHeader</code>, and <code>ilQuery</code>. The default is <code>ilCookie</code> and is the most common practice for websites around the world today. If you have special circumstances, you can put it in the HTTP header or on the URL as a query parameter.</li>
<li><code>IdName</code>: A string to identify the session ID; the default is <code>sessionId</code>.</li>
<li><code>InvalidSessionURL</code>: This is the path to a page to which you can send users if an invalid session is encountered (I never did in all my testing).</li>
<li><code>Scope</code>: The default is <code>ssUnlimited</code> which generates a new session for any request without a session ID. The other options are <code>ssUser</code> and <code>ssUserAndIP</code> and generate a new session for each user or each user and IP address combination.</li>
<li><code>SharedSecret</code>: This is a string that secures session IDs for user-scoped sessions; not used by this demo.</li>
<li><code>Timeout</code>: This specifies, in seconds, how long a session can last before it is discarded. The default is <code>3600</code>, or one hour.</li>
</ul>
<h3>TWebFormsAuthenticator</h3>
<p>This authenticator expects you to provide your own HTML form that collects a username and password and posts it to the web server. Several properties specify key paths:</p>
<ul>
<li><code>FailedURL</code>: A simple error page to redirect the user to if authentication fails. In my demo, this is <code>/failed</code>.</li>
<li><code>HomeURL</code>: Where to send the user once authentication is successful. In my demo, this is <code>/custlist</code>.</li>
<li><code>LoginURL</code>: Where the form is located that asks for login credentials. In my demo, this is <code>/login</code>.</li>
<li><code>LogoutURL</code>: Ends the user session. This is <code>/</code> in my demo.</li>
</ul>
<p>There are also two event handlers:</p>
<ul>
<li><code>OnAuthenticate</code>: This event gets called when the user posts the login form. You'll check the provided credentials against a database, hardcoded values, or other authentication system, setting <code>Success := True</code> and populating the <code>Roles</code> parameter with any security roles the user has.</li>
<li><code>OnAuthenticated</code>: This event is fired after successful authentication, allowing you to perform additional setup like loading user preferences or logging the login event.</li>
</ul>
<p>In some demo programs, the login credentials would be hard coded; so this event would simply check to see if the username and password matched those values. In my demo, I access the <code>Employees</code> table of a local SQLite database. Please see the <a href="https://github.com/corneliusdavid/webstencils-session-demo/blob/main/README.md">README</a> of the demo project for details of how this was implemented and how Roles are established.</p>
<h3>TWebBasicAuthenticator</h3>
<p>If you're not writing your own form to collect user credentials, you can use this component and your web browser will pop up a small default form. Similar to the <code>TWebFormsAuthenticator</code>, it provides a <code>LogoutURL</code> for ending your session, an <code>OnAuthenticate</code> event for validating the entered user credentials, and an <code>OnAuthenticated</code> event for post login code. It also has a property to set the <code>Realm</code> of your session.</p>
<p><em>I could find no examples of how to use this and all my attempts failed to pop up the web browser's basic login form. Therefore, I have no example and no further information to share on this.</em></p>
<h3>TWebAuthorizer</h3>
<p>Once the user is logged in, you need to map which paths the user is allowed to access based on their security clearance. For example, a simple user might be able to browse public articles and access their own profile but would not be allowed to configure the system like an administrator would. The idea is that the Roles assigned by one of the aforementioned "Authenticator" components map to paths defined by the Authorizer to simplify secure navigation throughout your website.</p>
<p>You set up this security map with the <code>Zones</code> property of the authorizer which pops up a list of zones, each with a set of properties:</p>
<ul>
<li><code>Kind</code>: <code>zkProtected</code>, <code>zkIgnore</code>, or <code>zkFree</code>. I don't know what the difference is between <code>zkIgnore</code> and <code>zkFree</code> but both of them allow anyone to access the page without checking credentials. <code>zkProtected</code> (as its name indicates) protects the path by checking the user's roles.</li>
<li><code>PathInfo</code>: This specifies the path that is being allowed or protected.</li>
<li><code>Roles</code>: this is a comma-separated list of roles, one of which the user needs to have in order to access the path.</li>
</ul>
<p><em>I wanted to use this component but every time I placed it on the web module and ran the program, it ignored the <code>OnAuthenticate</code> event of the <code>TWebFormsAuthenticator</code>; I could not figure out how to use this with authentication so, as mentioned above, I removed it and set up WebStencils variables for dynamic HTML building.</em></p>
<h2>Web App Flow</h2>
<p>When the web program starts up, it opens the database, establishes some variables, and adjusts the HTML paths of the <code>TWebStencilsProcessor</code> components. All paths and web action items are defined at design-time through the Object Inspector. All web action items use <code>TWebStencilsProcessor</code> components which point to HTML files that are included in the project.</p>
<p>The login page asks for a simple username and password, then makes a POST call to <code>/login</code> which triggers the <code>TWebFormsAuthenticator</code>'s <code>OnAuthenticate</code> event. After checking the database for a valid user, the procedure's <code>Success</code> and <code>Roles</code> parameters are set which then tell the authenticator where to send the user next. If login was successful, the user sees a list of customers pulled from the database; otherwise, an error page with a link to try again. Customer IDs are linked to an "edit" page that lists the customer's details (no changes are saved to the database as this is just a simple demo illustrating WebStencils and session management).</p>
<h3>Security</h3>
<p>WebStencils has several ways to determine visibility of content. The first iteration of this demo used the keyword <code>@LoginRequired</code>. This keyword has been enhanced in Delphi 13 by adding an optional parameter, <code>Role</code>, which means that in addition to being authenticated, the user also has to have the listed role in order to access the page.</p>
<p>Another technique is using the <code>Session.UserHasRole()</code> WebStencils function. This is used in the <code>custedit.html</code> file because if <code>@LoginRequired(<role>)</code> fails, it gives an ugly "Internal Application Error" message; by allowing a "viewer" role to access the page, I can check to see if they have the "editor" role and if not, display a nicely formatted message instead.</p>
<h3>Session Reset on Logout</h3>
<p>As I was finishing this demo, I noticed that when I logged out of one user and in with another, it would retain the user roles internally which resulted in a "viewer"-only user getting links to view customer details after an "editor" or "manager" had logged out. I fixed that by replacing a "Logout" link with a small form button so that I could call <code>/logout</code> with the POST method and thus trigger the Authenticator's <code>/LogoutURL</code> and properly clear the session.</p>
<h2>Conclusion</h2>
<p>The documentation and initial demo from Embarcadero promised ease of use and drop-in components with little coding for flexible session management with mapped paths to user roles. What I experienced took me several weeks in my spare time of trying one thing then another, and a lot of trial-and-error to try and interpret the documentation.</p>
<p>Some of the things I learned include:</p>
<ul>
<li>[WebSessionMgr] Setting <code>Scope</code> to anything but <code>ssUnlimited</code> requires deeper understanding of session and user management, and possibly some coding or other properties set properly; any other setting continually generated "Cannot generate session ID" errors for me.</li>
<li>[Authenticator] Calling the <code>LogoutURL</code> with a simple <code>href</code> link does not work; you must create a form-button with the POST method in order to properly clear a session.</li>
<li>[WebStencils] The <code>@if</code> expression needs parenthesis if calling a function, like <code>(session.UserHasRole(<role>))</code> but not if accessing a simple object variable.</li>
<li>[WebStencils] The <code>@switch</code> expression <em>always</em> needs parenthesis.</li>
<li>[WebStencils] Setting variables in Delphi and then accessing them in WebStencils is much easier than calling functions and doing logic branching in HTML.</li>
</ul>
<p>I hope the documentation improves in a future version of Delphi. And maybe we'll get more examples. Then again, I've seen no conversations about this on the popular Delphi forums, so perhaps this technology is too late for anyone to care about.</p>
<p>In any case, I'm glad it's been added to the WebBroker stack; I might use it some day.</p>
</div>
<section id="node-comment">
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=198&2=comment_node_blog&3=comment_node_blog" token="RmRGoo3DrtexuYq5dtHlGz7q7LymLf8lNIXaRSCts9M"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Mon, 01 Dec 2025 05:35:44 +0000david198 at http://corneliusconcepts.techCode Formatting in Delphi 13
http://corneliusconcepts.tech/code-formatting-delphi-13
<span property="dc:title">Code Formatting in Delphi 13</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-09-10T20:04:51+00:00" datatype="xsd:dateTime">Wed, 09/10/2025 - 13:04</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><a href="/sites/default/files/inline-images/MessyCode.png"><img alt="horribly messy code" data-entity-type="file" data-entity-uuid="a4d5174c-c51f-4486-bf22-d4ae4eeb8619" src="/sites/default/files/inline-images/MessyCode.png" width="450" class="align-left" /></a>RAD Studio 13 <a href="https://blogs.embarcadero.com/announcing-the-availability-of-rad-studio-13-florence/">was released today</a> and has several much-anticipated features and wonderful enhancements. There have been many blog posts talking about them and I'm excited to start using them; but today, I want to share some tips on how to deal with a feature that was <em>removed</em>: <strong>code formatting</strong>.</p>
<h2>The Problem</h2>
<p>As with all advancing technology, some things get better and some things must get left behind. Starting with Delphi 12, the modeling features were moved to an optional add-on installation, and in Delphi 13, they're being removed entirely due to technical constraints. This eliminates the built-in code formatter that many developers relied on for consistent code styling. A recommended replacement module for the refactoring capabilities is the well-supported <a href="https://www.mmx-delphi.de/">MMX Code Explorer</a> IDE plug-in. It does a great job but does not currently support code formatting. One suggestion is to use <a href="https://github.com/integrated-application-development/pasfmt">pasfmt</a>, an open-source code formatter for Delphi code, ironically written in Rust, <em>not</em> Delphi!</p>
<h2>A Solution: formatter.exe</h2>
<p>Fortunately, there's a workaround that leverages an existing tool in previous versions of RAD Studio. Formatter.exe is a command-line formatter for Delphi and C/C++ source code files that ships in older versions of RAD Studio's <code>\bin</code> folder. This standalone utility can format your code using predefined profiles created in earlier versions of the IDE.</p>
<h2>Setting Up the Workaround</h2>
<h3>Step 1: Create a Formatter Profile</h3>
<p>Use a prior version of Delphi (before Delphi 13) such as Delphi 11 or Delphi 12 that has formatting options available and perform the following steps:</p>
<ul>
<li>From the menu, select Tools → Options → Language → Formatter</li>
<li>Configure your preferred formatting settings (indentation, spaces, line breaks, etc.)<br />
<a href="/sites/default/files/inline-images/OldCodeFormattingOptions.png"><img alt="Code Formatting options in Delphi 12" data-entity-type="file" data-entity-uuid="cb79af29-e21e-4db9-9f46-16f08fa4660a" src="/sites/default/files/inline-images/OldCodeFormattingOptions.png" width="350" /></a></li>
<li>Once you are satisfied, go to the "Profiles and Status" section and click "Save As..." to save your settings to a file, typically with the pattern <code>Formatter_*.config</code></li>
<li>The profile will be saved as an XML file in your user's roaming folder, typically at <code>%AppData%\Embarcadero\BDS\[version]\Formatter.config</code>; but you can change the location to something more suitable to your programming environment.</li>
</ul>
<h3>Step 2: Call formatter.exe</h3>
<p>To format a source file, run the formatter from command line:</p>
<pre>
<code class="language-bash">formatter.exe -config "path\to\your\profile.config" -delphi "source_file.pas"
</code></pre>
<p>This will modify that file so it would be wise to make a backup first. Of course, if you use version control, you could simply revert to a previous version of the file if something goes awry.</p>
<p>Now, you might think this is a little cumbersome, a far cry from the simply hitting <code>Ctrl+D</code> (or whatever hotkey you had assigned for code formatting in your IDE). But there's a cool way of adding this right into Delphi itself.</p>
<h3>Step 3: Add IDE Integration</h3>
<p>The Delphi "Tools" menu has long had the capability to add links to additional external tools. But it's more than just launching an executable, you can pass in IDE macros as parameters to these tools; that's what makes this such a cool solution!</p>
<p>Here's how to call the formatter with the current code file open in your IDE's editor:</p>
<ul>
<li>Go to Tools → Configure Tools</li>
<li>Add a new tool with the following:
<ul>
<li>Title: "Format Code"</li>
<li>Program: <code>"C:\Program Files (x86)\Embarcadero\Studio\23.0\bin\Formatter.exe</code></li>
<li>Parameters: <code>-config "C:\path\to\your\profile.config" -delphi "$EDNAME"</code></li>
</ul>
</li>
</ul>
<p>Since this is an external tool, it doesn't have access to the current file loaded in the editor, so be sure and save it first. After you run this (the black, command-prompt window will briefly flash as the formatter runs) and the editor regains focus, the IDE will notice the file was updated and show this warning:<br />
<img alt="code changed warning" data-entity-type="file" data-entity-uuid="b240c9ea-85c5-4846-8ef3-94d7f7f0c58a" src="/sites/default/files/inline-images/FileChangedWarning.png" width="450" /></p>
<p>This is to be expected, so simply click Yes and your editor will be refreshed with the formatted code.</p>
<h2>Limitations</h2>
<p>Note that if a source code file cannot be compiled, the formatting results may be incorrect. The formatter works best with syntactically correct code. Also, the Formatter tool does not come with Delphi 13 so you need to keep around your previous version of Delphi.</p>
<h2>Alternative Solutions</h2>
<p>There are other options that should be noted as well, in addition to <code>pasfmt</code> mentioned above:</p>
<ul>
<li><strong><a href="https://blog.dummzeuch.de/experimental-gexperts-version/">GExperts</a></strong> is an <a href="/gexperts-most-awesome-delphi-companion">awesome IDE plug-in</a> that has many productivity-enhancing features, including its own code formatter. I have used this in earlier versions of Delphi that did not have a formatter built in.</li>
<li><strong>JEDI Code Format</strong>: There's an old <a href="https://jedicodeformat.sourceforge.net/">JEDI Code Format</a> on SourceForge and a newer <a href="https://github.com/quadroid/jcf-pascal-format">JCF Code Formatter</a> on Github that may be good options--I have no experience with either of these.</li>
</ul>
<h2>Conclusion</h2>
<p>While losing the built-in formatter is inconvenient, the external tool, <code>formatter.exe</code>, existing in previous versions of Delphi, is a great workaround and provides a bridge solution for teams wanting to maintain consistent code formatting in Delphi 13. Combined with tools like <strong>MMX Code Explorer</strong> for refactoring needs, developers can maintain productive workflows despite the modeling feature removal.</p>
<p>The key is to keep your older version of Delphi until there's a better, more official solution--or just use GExperts!</p>
<hr />
<p><em>P.S. The code shown in the image at the top of this post is taken from an actual, <a href="https://github.com/corneliusdavid/ugly-code">compilable Delphi VCL program</a>. It's a test program (written by Claude.ai) that counts the number of words entered in an edit box and displays the results in a memo!</em></p>
</div>
<section id="node-comment">
<article data-comment-user-id="0" id="comment-813" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1757542173"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/813#comment-813" class="permalink" rel="bookmark" hreflang="en">$SAVE macro</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">DelphiCoder (not verified)</span> Wed, 09/10/2025 - 14:51</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>There is also the <strong>$SAVE</strong> macro available which will save the currently active file in the code editor! So just add this before <code>-config:"C:\path\to\your\profile.config" "$EDNAME"</code> and you can't forget to save the before invoking the code formatter tool.</p>
<p><code>$SAVE -config:"C:\path\to\your\profile.config" "$EDNAME"</code></p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=813&1=default&2=en&3=" token="dbxstyS8y3OQ1Z6Vz7AIyPds-SIKszQNJDNpgzL4nHY"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="0" id="comment-832" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758466933"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/832#comment-832" class="permalink" rel="bookmark" hreflang="en">I had to remove quotes from …</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Antony Lewis (not verified)</span> Sun, 09/21/2025 - 01:55</span>
<p class="visually-hidden">In reply to <a href="/comment/813#comment-813" class="permalink" rel="bookmark" hreflang="en">$SAVE macro</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">DelphiCoder (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I had to remove quotes from $EDNAME, otherwise get "Unknown macro command"</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=832&1=default&2=en&3=" token="HT5lSfLUXfCy_-SXBCyFR2nym2nlnN0bF53ldwxWHQs"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="1" id="comment-814" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1757542166"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/814#comment-814" class="permalink" rel="bookmark" hreflang="en">That Actually Works!</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Wed, 09/10/2025 - 15:09</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Wow--that actually works! And prompts you, too! Thanks for that tip!</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=814&1=default&2=en&3=" token="cNi6AR329HtaWVbmz4348a8XpNZ0YGjtq6q6Oog0ZgA"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-815" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1757552601"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/815#comment-815" class="permalink" rel="bookmark" hreflang="en">Unbelievable !</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Zdzichs (not verified)</span> Wed, 09/10/2025 - 17:11</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>This is crazy unprofessional :( I lost over an hour trying to fix grayed refactor and format menus :( How on earth such creepy solution could pass the QA in corp like Emb? Thanks for your workaround anyway, but the issue is just sad :(</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=815&1=default&2=en&3=" token="HAqP91l-RT0-Wjn1ID120l5tXcc8FP4l5HMcsXtddXs"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-816" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1757552772"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/816#comment-816" class="permalink" rel="bookmark" hreflang="en">Yes, confusing.</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Wed, 09/10/2025 - 18:06</span>
<p class="visually-hidden">In reply to <a href="/comment/815#comment-815" class="permalink" rel="bookmark" hreflang="en">Unbelievable !</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Zdzichs (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I agree that it seems weird to leave a disabled menu item if there's no functionality behind it. I think it has to do with the fact the IDE is used for both Delphi and C++Builder; perhaps the latter product uses that. Even then, I'd think the code wouldn't be that hard to completely remove it for Delphi if there's absolutely no code for it. Maybe it can be used by plug-in writers? And they hope someone will hook up that menu?</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=816&1=default&2=en&3=" token="DGbnSmgJCcMa62kmr5hX4yfO-gvyY9k4f67lmVktXV8"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="0" id="comment-817" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1757912022"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/817#comment-817" class="permalink" rel="bookmark" hreflang="en">Embarcadero Can't Ship An IDE Anymore</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Joseph (not verified)</span> Sun, 09/14/2025 - 18:53</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>"While losing the built-in formatter is inconvenient" - it's beyond that; it's <em>inexcusable</em>. It's a part of the core functionality of the IDE. They're proving time and again now they do not have enough developers to progress the product and deal with its Greek-level technical debt. They simply can no longer support the product. C++Builder broke about two years ago and can only target Windows for the foreseeable future. They failed to meet the Android 64bit deadline and had to beg for an extension. They added type inference and code completion broke for two years. I could go on and on.</p>
<p>The product is clearly unsustainable at this point. The resources Idera/Embarcadero have allocated to it are insufficient to deliver a viable product (no, an IDE without code formatting is not acceptable as an IDE). </p>
<p>Until the community stands up and says "This is UNACCEPTABLE and we're not paying for it", and until the apologists stop defending it, nothing will change. They've shipped a broken and incomplete product. Period. It's not "technical constraints" that's the problem... it's ancient code they never bothered to update and did not have enough time to replace before a mandatory shipping deadline. Remember what got Nick Hodges fired (the first time): telling the dev team to be careful about bugs because Embarcadero "will ship c**p". Well, they just shipped it again. A $99 JetBrains IDE has advanced code formatting, and they're not struggling to replace 32bit code either. To offer a $1600 IDE/compiler suite with missing code formatting is just absurd. To accept it like this is NORMAL is even more absurd. </p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=817&1=default&2=en&3=" token="1Gg7hZb6Vp_PCdFIxu3U20qr1uln6lgShV2L0iLpUxM"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-818" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1757912651"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/818#comment-818" class="permalink" rel="bookmark" hreflang="en">Acceptable criticism but there are options</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Sun, 09/14/2025 - 22:04</span>
<p class="visually-hidden">In reply to <a href="/comment/817#comment-817" class="permalink" rel="bookmark" hreflang="en">Embarcadero Can't Ship An IDE Anymore</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Joseph (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I can't argue it appears there are not enough developers to maintain a viable product such as this and that preparation for replacing this old code should've been put in place several versions ago. However, there are options. Options to use GExperts, leverage external formatters, stay with an older version, or move to a different product altogether. There are forces at play within the company for which we know nothing about which likely constrain the actions and voices of the developers within the company trying to make this work for everyone. The only thing we can do as customers is to look at the options and choose the one that makes the most sense for ourselves.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=818&1=default&2=en&3=" token="FrvJW5GpdwQr8eLiwoXbSgMnoC0OFhsyhqO0x-rIs-U"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="0" id="comment-819" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1757976198"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/819#comment-819" class="permalink" rel="bookmark" hreflang="en">David,
Thank you for this…</a></h3>
<div class="single-comment-meta">
<span><a rel="nofollow" href="https://zilchworks.com" lang="" typeof="schema:Person" property="schema:name" datatype="">Michael Riley (not verified)</a> Mon, 09/15/2025 - 14:46</span>
<p class="visually-hidden">In reply to <a href="/comment/818#comment-818" class="permalink" rel="bookmark" hreflang="en">Acceptable criticism but there are options</a> by <span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>David, </p>
<p>Thank you for this post and for all your contributions to the Delphi community.<br />
<br />
<em><strong>"...or move to a different product altogether."</strong></em></p>
<p>Regarding your comment about moving to a different product. I love the Pascal language. I fell in love with it from day on back in 1986. I have used Lazarus + Free Pascal Compiler in the past. I would love to switch 100% completely over to L+FPC. The challenge that prohibits me from switching to L+FPC is this: I cannot get L+FPC to run on a Windows machine and cross-compile a simple "Hello World" program that runs on both a Windows and macOS machine. <br />
<br />
I do my dev work on a Windows machine. With Delphi it is easy to target macOS, push a button, and see the single code base run on a macOS computer. If I could get the same thing to happen using L+FPC I would switch in a heartbeat. <br />
<br />
I reached out to Detlef Overbeek (Editor Blaise Pascal) magazine over a year about this issue. He and Alfred Glanzer the creator of FPCUP Deluxe put together a video that shows how to cross-compile L+FPC. Unfortunately they did not demonstrate how to cross-compile for the macOS but they talked about "how easy it would be to add". I still could not get it to work.<br />
<br />
Video demonstration of using FPCUP Deluxe:<br />
<span><span><span><a href="https://downloads.blaisepascal.eu/Alfred2.mp4" rel="nofollow">https://downloads.blaisepascal.eu/Alfred2.mp4</a></span></span></span></p>
<p> </p>
<p><span><span><span>Cheers,</span></span></span></p>
<p><span><span><span>Michael</span></span></span></p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=819&1=default&2=en&3=" token="th0n-M6KXW1KzPScSQ784djUc3IZ_fG7rJB0JGbTx6o"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-820" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1758029039"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/820#comment-820" class="permalink" rel="bookmark" hreflang="en">Not all options are viable</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Mon, 09/15/2025 - 16:02</span>
<p class="visually-hidden">In reply to <a href="/comment/819#comment-819" class="permalink" rel="bookmark" hreflang="en">David,
Thank you for this…</a> by <a rel="nofollow" href="https://zilchworks.com" lang="" typeof="schema:Person" property="schema:name" datatype="">Michael Riley (not verified)</a></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Yeah, not all options are viable. I last ran Lazarus several years ago when I converted a friend's Windows-based registration program to run under his Linux-based website. It took a lot of configuration and work to get it all installed with the right libraries and such for that particular distribution of Linux. I do so much work on Windows with Delphi, I don't have time to keep up with all the nuances of multiple environments. I'm sure L+FPC on Windows is much simpler; no idea how that would cross-compile to Mac. So yeah, I get that moving to another platform/environment/tool is not necessarily an option.</p>
<p>The obvious option (to me) is to simply keep Delphi 12 around. That seems kinda silly if the only reason is to be able to configure formatter options and then run the formatter from Delphi 13. But I hope that the lack of a formatter will soon be resolved.</p>
<p>Until then, a lot of people are complaining. Loudly!</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=820&1=default&2=en&3=" token="9x_0sEHuh-dJwj9sa5I_pygyooUxR3CerM32QzLc50E"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div></div></div>
<article data-comment-user-id="0" id="comment-821" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758027205"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/821#comment-821" class="permalink" rel="bookmark" hreflang="en">Some syntax concerns for parameters</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">pj (not verified)</span> Tue, 09/16/2025 - 02:47</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>$SAVE -config:"\path\to\Formatter.config" $EDNAME works for me</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=821&1=default&2=en&3=" token="RxxscuNlrNd_TL-Wh6nh4yeQjDHOY5TxdqhEDyLKN6E"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-828" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1758202478"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/828#comment-828" class="permalink" rel="bookmark" hreflang="en">$SAVE!</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Thu, 09/18/2025 - 06:34</span>
<p class="visually-hidden">In reply to <a href="/comment/821#comment-821" class="permalink" rel="bookmark" hreflang="en">Some syntax concerns for parameters</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">pj (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Adding $SAVE at the start of the argument list is an excellent tip, thanks!</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=828&1=default&2=en&3=" token="jR_s0v52BEMbXpuEUf03taCM2iFkQc0o8lxmc7HS8NQ"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="0" id="comment-822" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758164816"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/822#comment-822" class="permalink" rel="bookmark" hreflang="en">I read that some people are…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Roland Bengtsson (not verified)</span> Wed, 09/17/2025 - 19:52</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I read that some people are upset that Embarcadero removed formatter. I am not.</p>
<p>I understand that IDE is written in Delphi except modeling that they now remove. And formatting rely on modeling. So now IDE is written in 100 % Delphi? To write formatter from scratch is a challenging task that take resources from other issues. And I don't use formatter...</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=822&1=default&2=en&3=" token="qpYD5P54xcEZbcdzAXeThdbKDmzIWJrRIE0aFGBUjbU"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-823" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758200346"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/823#comment-823" class="permalink" rel="bookmark" hreflang="en">Using CnWizards?</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">pj (not verified)</span> Wed, 09/17/2025 - 23:47</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>What about using the code formatter from CnWizards?</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=823&1=default&2=en&3=" token="EOmwRKedILZ1UXVyoM_l_LpuRVDPrYV8GiwnUT-B-Os"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-826" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1758200449"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/826#comment-826" class="permalink" rel="bookmark" hreflang="en">CnWizards, another option</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Thu, 09/18/2025 - 06:00</span>
<p class="visually-hidden">In reply to <a href="/comment/823#comment-823" class="permalink" rel="bookmark" hreflang="en">Using CnWizards?</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">pj (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Yes, I think CnWizards is another option, one I didn't think of because I don't use that one. There's a lot of overlap (and possibly some conflicts) with GExperts which I do use.</p>
<p>Thanks for mentioning this.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=826&1=default&2=en&3=" token="ab5iIZGMF89MSnCPgbrcuI7yHqfoKEyjDb9VtJ299fg"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="0" id="comment-824" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758200456"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/824#comment-824" class="permalink" rel="bookmark" hreflang="en">Parnassus bookmarks and…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">John Cooper (not verified)</span> Thu, 09/18/2025 - 04:48</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Parnassus bookmarks and navigator have also disappeared. I expect we will get them back sooner or later, perhaps before the next version.</p>
<p>My produdctivity will go through the floor</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=824&1=default&2=en&3=" token="zqPA0zebnQnFBl7ex5qUUunxxft7875PKdPNDhSMejI"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-827" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1758200508"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/827#comment-827" class="permalink" rel="bookmark" hreflang="en">Soon?</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Thu, 09/18/2025 - 06:01</span>
<p class="visually-hidden">In reply to <a href="/comment/824#comment-824" class="permalink" rel="bookmark" hreflang="en">Parnassus bookmarks and…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">John Cooper (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I hope they will be available soon; not sure why they can't be ready the same day as the rest of the product.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=827&1=default&2=en&3=" token="IQwvVUQ-NlVwsrO5mMYIbQJJDVotfViSFCA7rOx4_Aw"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-830" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758285006"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/830#comment-830" class="permalink" rel="bookmark" hreflang="en">This also was the case when…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">DelphiCoder (not verified)</span> Fri, 09/19/2025 - 04:39</span>
<p class="visually-hidden">In reply to <a href="/comment/824#comment-824" class="permalink" rel="bookmark" hreflang="en">Parnassus bookmarks and…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">John Cooper (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>This also was the case when D12 was released. Just stay with D12 until all GetIt packages and plugins relevant for you are available for D13! :)</p>
<p> </p>
<p> </p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=830&1=default&2=en&3=" token="GwlQawi09MUxnDvYER951iJiB2_nKHJhQbDF9TPnB4Q"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-834" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1759417283"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/834#comment-834" class="permalink" rel="bookmark" hreflang="en">The Parnussus tools are now…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">IanB (not verified)</span> Thu, 10/02/2025 - 07:41</span>
<p class="visually-hidden">In reply to <a href="/comment/824#comment-824" class="permalink" rel="bookmark" hreflang="en">Parnassus bookmarks and…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">John Cooper (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>The Parnussus tools are now available in GetIt.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=834&1=default&2=en&3=" token="mSxq771xeMOOwn9VjBwIYbt14nIa_Cx_rcNT2CYdyYA"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="0" id="comment-829" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758208918"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/829#comment-829" class="permalink" rel="bookmark" hreflang="en">Like they couldn’t have just…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Nathan (not verified)</span> Thu, 09/18/2025 - 07:49</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Like they couldn’t have just had some AI rewrite the code formatter in pascal for them? Really don’t deserve my money anymore. First they take away my ability to continue maintenance on the FireDAC addon ther I PURCHASED, then they remove the ability to undock editor windows, now they remove probably the most useful feature of the IDE (all of the rest of the “features” I never use). That l, and the fact that the IDE and debugger get slower and slower every major release just isn’t worth giving them anymore money.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=829&1=default&2=en&3=" token="r7JA9Jf2NDOPqQSiWZF38P2gqwp27hE-TDV680_w81w"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-831" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1758301089"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/831#comment-831" class="permalink" rel="bookmark" hreflang="en">I think it's good that they…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Steve Sinclair (not verified)</span> Fri, 09/19/2025 - 08:27</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I think it's good that they are facing up to the fact that some of their technology needs completely rewriting. </p>
<p>Delphi has a fantastic legacy, what other tool available today could you open and compile code written in the 1990's. And create modern powerful cross platform apps for today’s world. However the desire to stay compatible with the past is a two edged sword.</p>
<p>They clearly haven’t got the resources that the big players have but I think on the whole they do a good job. What’s more because they are smaller they can interact with us better. Software development is hard, commercial pressures make it harder. It’s easy to complain, yes it costs a fortunate to buy a Delphi license but most of us have made a pretty good living out of it.</p>
<p>If there is something out there that works better for you then use that. I’m happy that Delphi is still a thing, for a while it looked like it was going to disappear all together. </p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=831&1=default&2=en&3=" token="UYHXCoxIwhvRWJ447GwvAN1g_ziUjdTin9SzwEoGjUo"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-833" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1759417340"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/833#comment-833" class="permalink" rel="bookmark" hreflang="en">Thanks for excelent tip</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Jiří Vráždil (not verified)</span> Mon, 09/29/2025 - 22:29</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Thanks for excelent tip</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=833&1=default&2=en&3=" token="Rraa4eahLQNfSG-dn4wnqnedj21V7L4SZ_J52-PG_KE"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-835" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1759417265"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/835#comment-835" class="permalink" rel="bookmark" hreflang="en">Hey, all of this is fair…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">IanB (not verified)</span> Thu, 10/02/2025 - 07:49</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Hey, all of this is fair criticism, and I totally understand why plenty of people are unhappy about the code formatter removal. It doesn't really matter what the reasons are, the result is the same, and I get it, really, I do.<br />
<br />
There are, as the article and various comments point out, a lot of other options for having code formatting (some better than others) - Jedi, cnWizards, GExperts, MMX, and PasFmt (which is the one I prefer and use myself and I think does an excellent job). Most of the 3rd party options do a far <em>better</em> job than the outdated formatter that was removed.<br />
<br />
We do intend to bring back formatting, of course, but, for now, the best option is one of the several third-party options.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=835&1=default&2=en&3=" token="LYJLw104ASNFD8Kom0Moabv3gutaljdHukU-ZG5nopU"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="0" id="comment-856" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1764950353"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/856#comment-856" class="permalink" rel="bookmark" hreflang="en">If you remove core features,…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">D7 (not verified)</span> Thu, 12/04/2025 - 12:38</span>
<p class="visually-hidden">In reply to <a href="/comment/835#comment-835" class="permalink" rel="bookmark" hreflang="en">Hey, all of this is fair…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">IanB (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>If you remove core features, the product’s price should also decrease—makes sense? <br />
The concept of splitting the product into Standard, Pro, Enterprise, and Architect versions is broken; it lacks feature parity and justification for price differences.<br />
The difference between the Pro and Enterprise versions essentially comes down to the Linux compiler (which receives little special attention or innovation) and the FMX for Linux. Everything else can be replaced with “alternative libraries.”<br />
Take Delphi Enterprise and look at what has been added and removed over the past four years—excluding bug‑report statistics. <br />
Each release is accompanied by information about hundreds of fixed bugs, yet shortly after, a hundred new bug reports appear, and this repeats every release.</p>
<p>This is simply unacceptable: releasing an IDE with an unprecedentedly high price and without basic capabilities that even a student could implement. <br />
You say, “we’ll add it later; in the meantime use alternatives.” <br />
Only someone who likely purchased a subscription that is now expiring will have to either pay the full price again to get back what you removed or continue on a subscription whose value is highly questionable. <br />
Why questionable? Because there’s no roadmap; no one knows what the next $1,000 subscription is actually funding.<br />
Idera will even lose loyal customers who have been paying faithfully for years.</p>
<p>No roadmap, features are being removed, and no adding value to the premium IDE versions — great. <br />
So we’ll do without a subscription. The new model, answer to Idera changes - is purchasing only existing, finished, and fully functional features—paying full price, but only once every five years and on Black Friday—to get better deals that are never available to subscription payers.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=856&1=default&2=en&3=" token="KZQpb6c6qzWHgZ6vqKHMptrBlS9P_T35gk00cTyJBdQ"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="0" id="comment-836" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1764950315"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/836#comment-836" class="permalink" rel="bookmark" hreflang="en">try my plugin using OTA in…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Emailx45 (not verified)</span> Mon, 11/10/2025 - 18:01</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>try my plugin using OTA in Delphi 13 (32/64bits)</p>
<p>video</p>
<p>https://youtu.be/7xNfRTCq_Mc</p>
<p>Direct link to download: https://buzzheavier.com/9ustuoat3x27</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=836&1=default&2=en&3=" token="Q9COPrR9J8w-pVYTiWPzwFUBltmfptGb6AHyi1XS7g4"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-855" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1764950331"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/855#comment-855" class="permalink" rel="bookmark" hreflang="en">13 October patch</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Flavius Fernandes (not verified)</span> Tue, 12/02/2025 - 02:41</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Has anyone successfully implemented this with the 13 October patch?</p>
<p>I had it functioning prior to the patch, but now it does not work.</p>
<p>Furthermore, I am quite disappointed that the formatter has been eliminated; it was an essential tool, and suggesting to simply use an alternative is not a viable solution given the billion lines of code across various projects.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=855&1=default&2=en&3=" token="b53SAjb6mf7rzNzBatrjf4MoW7zYguFAOhKV0FqB3hk"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=197&2=comment_node_blog&3=comment_node_blog" token="TK0oyeRylwKRgx-vNd6CEWhuB8IhWzn8-I4jyaO2kNk"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Wed, 10 Sep 2025 20:04:51 +0000david197 at http://corneliusconcepts.techAI Tools for Consultants
http://corneliusconcepts.tech/ai-tools-consultants
<span property="dc:title">AI Tools for Consultants</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/miscellaneous" hreflang="en">Miscellaneous</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-07-30T15:47:33+00:00" datatype="xsd:dateTime">Wed, 07/30/2025 - 08:47</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p>I recently <a href="https://occa.org/meeting/2985">presented</a> a list of AI tools at a computer consultants meeting (<a href="https://occa.org">OCCA</a>) and my research revealed several new ways of quickly setting up an online business presence, preparing marketing materials to present to potential clients, and even virtual assistants to handle routine tasks. What impressed me most was how these tools eliminate traditional barriers to entry while allowing consultants to focus on what they do best—solving clients' computing problems. Here's what I discovered, organized by business function.</p>
<h2>Professional Presence</h2>
<h3>LinkedIn Profile Creation</h3>
<p>Your professional image matters, and AI tools handle both visual and written aspects effectively.</p>
<p>For headshots, you have several specialized options:</p>
<ul>
<li><strong><a href="https://magicstudio.com">MagicStudio.com</a></strong> excels at background removal and cleanup</li>
<li><strong><a href="https://instaheadshots.com">instaheadshots.com</a></strong> generates multiple professional headshot variations from a single uploaded photo using AI styling</li>
<li><strong><a href="https://headshotpro.com">headshotpro.com</a></strong> creates corporate-quality portraits with customizable backgrounds and lighting effects</li>
</ul>
<p>Once you have quality images, <strong><a href="https://seekario.ai">Seekario.ai</a></strong> optimizes your LinkedIn profile content. While there are many profile sites that focus primarily on resume building, Seekario adds the capability to take that information and build a compelling LinkedIn profile. It also supports several other professional services, making it a versatile tool for consultants who need to maintain presence across multiple platforms.</p>
<h3>Website Generation</h3>
<p>If you don't have web development skills or don't have the time to learn and build your own site, these simple AI-powered site builders can get you up and running quickly—and they look nice as well.</p>
<ul>
<li><strong><a href="https://tilda.cc">Tilda.cc</a></strong> combines intuitive visual design with AI content generation, to quickly build a new website, online store, landing page or blog.</li>
<li><strong><a href="https://lovable.dev">Lovable.dev</a></strong> is an AI-powered platform that enables users of any skill level to create full-stack websites through natural language. Simply describe what you want, and Lovable builds it for you.</li>
<li><strong><a href="https://pagey.xyz">Pagey.xyz</a></strong> focuses on creating portfolios and CVs, allowing you to keep a collection of profiles for freelance, job seeking, or personal projects.</li>
</ul>
<p>For developers comfortable with command line tools, <strong>Claude Code</strong> running in WSL2 (Windows Subsystem for Linux) is one of many AI code-generation tools that can create websites. These tools can produce pure HTML/JS/CSS sites, or use familiar frameworks like React, Vue, Angular, Svelte, or Next.js. They can even set up the structure for static site generators like Hugo if that's your preference.</p>
<h2>Content and Presentation</h2>
<p>Once you've established your online presence, you can focus on preparing marketing materials to attract and engage potential clients.</p>
<h3>Technical Blogging</h3>
<p>Use your favorite AI chat engine to help you write a blog; this one was written with Claude but ChatGPT, Gemini, Perplexity, or Copilot will work as well. Select your technical subject, request detailed paragraphs, then through conversational interaction with the AI, reorder, modify, add content, and polish your blog to your liking. Finally, export it into a format that you can upload to your website, whether that be complete with all the HTML tags, in Markdown format, or simply as a Word document where you can convert it to a PDF suitable to publish on the web.</p>
<h3>Client Presentations</h3>
<p>When pitching computer services, to potential clients, you may want to use <strong><a href="https://sketchbubble.com">SketchBubble.com</a></strong>'s free AI presentation maker to transform your talking points into professional slide decks. The service generates a complete, downloadable PowerPoint presentation based on and expanded from your simple prompts that you can then customize and publish anywhere you want.</p>
<h3>Video Marketing</h3>
<p>Video is becoming a commonplace, and often expected, means of marketing tool for a business. <a href="https://gemini.google/overview/video-generation">Veo</a> is a Google tool that creates compelling video advertisements . Here's how to <a href="https://youtube.com/shorts/GaNkB1JVk5I?si=AJ6WIycx-Z0cCS3k">get started</a>.</p>
<p>You've probably seen several advertisements that have been made with Veo, possibly without even realizing it. Here are some samples that were created with Veo; a quick search will show you many more possibilities:</p>
<ul>
<li>Sample: <a href="https://x.com/Salmaaboukarr/status/1946530149299634618">From IKEA box to complete bedroom.</a></li>
<li>Sample: <a href="https://youtube.com/shorts/H7GC_qee6E4?si=s1r5qV1iS7qA0exx">Has video creation gone too far?</a></li>
</ul>
<p>These examples illustrate that as a small business owner, you too can create amazing marketing videos without much effort or money.</p>
<h2>Business Operations</h2>
<p>As your business gets going, you can make use of many tools to help your business flow and assist you in various ways.</p>
<h3>Phone Systems</h3>
<p><strong><a href="https://bland.ai">Bland.ai</a></strong> creates automated phone attendants, handling initial client contact and routing calls appropriately.</p>
<h3>Workflow Automation</h3>
<p>Eliminate repetitive tasks with automation platforms. <strong><a href="https://zapier.com">Zapier</a></strong> and <strong><a href="https://make.com">Make.com</a></strong> connect different apps to create seamless workflows—automatically moving data between your CRM, email, and billing systems. For Microsoft-centric businesses, <strong><a href="https://powerautomate.microsoft.com">Power Automate</a></strong> integrates deeply with Office 365. Open-source option <strong><a href="https://n8n.io">n8n</a></strong> gives you complete control over your automation workflows.</p>
<h3>Virtual Assistants</h3>
<p>AI-powered scheduling tools like <strong><a href="https://calendly.com">Calendly</a></strong> handle appointment booking automatically, while <strong><a href="https://clara.com">Clara</a></strong> acts as an AI meeting scheduler that communicates directly with clients via email. <strong><a href="https://motion.com">Motion</a></strong> combines AI project management with intelligent scheduling, optimizing your calendar based on priorities and deadlines. <strong><a href="https://otter.ai">Otter.ai</a></strong> captures meeting transcripts and generates actionable notes, ensuring nothing gets missed.</p>
<h3>Document Management</h3>
<p><strong><a href="https://pandadoc.com">PandaDoc</a></strong> streamlines proposal creation and contract signing with automated templates and e-signatures. <strong><a href="https://notion.com">Notion AI</a></strong> serves as both knowledge base and project tracker, helping organize client information and project documentation. <strong><a href="https://tome.app">Tome</a></strong> creates professional presentations using AI, perfect for client pitches and technical documentation.</p>
<h3>Customer Relationship Management</h3>
<p><strong><a href="https://hubspot.com">HubSpot</a></strong>'s AI features help track leads and automate follow-ups, while <strong><a href="https://pipedrive.com">Pipedrive</a></strong> focuses on sales pipeline automation. For larger operations, <strong><a href="https://salesforce.com">Salesforce Einstein</a></strong> adds AI-enhanced insights to customer data, predicting sales opportunities and customer behavior.</p>
<h3>Financial Operations</h3>
<p><strong><a href="https://quickbooks.com">QuickBooks AI</a></strong> automates bookkeeping tasks like expense categorization and invoice matching. <strong><a href="https://dext.com">Receipt Bank (now Dext)</a></strong> eliminates manual expense entry by automatically processing receipts and bills. <strong><a href="https://freshbooks.com">FreshBooks</a></strong> combines time tracking with automated invoicing, essential for consultant billing accuracy.</p>
<h2>Implementation Strategy</h2>
<p>The key to leveraging these tools effectively is understanding their strengths:</p>
<ul>
<li>Use AI for rapid prototyping and initial creation</li>
<li>Apply your technical expertise for refinement and customization</li>
<li>Maintain quality control through testing and validation</li>
<li>Integrate multiple tools for comprehensive solutions</li>
</ul>
<p>Remember that asking a good question will get you a better response from any AI tool. Use <strong><a href="https://promptessor.com">promptessor.com</a></strong> to help analyze your prompts for clarity and completeness—better prompts lead to more useful results across all these platforms.</p>
<p>This toolkit transforms the traditional barriers to consulting—professional presence, content creation, marketing, and business operations—into manageable, AI-assisted tasks. If used effectively, they can keep costs down, increase productivity, and allow you to focus your expertise on your business!</p>
</div>
<section id="node-comment">
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=196&2=comment_node_blog&3=comment_node_blog" token="e_amP05XjB4GzIGVwfLy3awapDHVvtsdKdh2zEZKCao"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Wed, 30 Jul 2025 15:47:33 +0000david196 at http://corneliusconcepts.techAre We All Doomed?
http://corneliusconcepts.tech/are-we-all-doomed
<span property="dc:title">Are We All Doomed?</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/miscellaneous" hreflang="en">Miscellaneous</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-06-18T05:49:15+00:00" datatype="xsd:dateTime">Tue, 06/17/2025 - 22:49</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><img alt="AI Dooms Earth!" data-entity-type="file" data-entity-uuid="35314c95-4012-4598-9971-e780d23745e0" hspace="5" src="/sites/default/files/inline-images/robot-scare.png" width="350" class="align-left" />You've probably heard warnings or seen movies about how AI-enabled robots take over and threaten the human race. The idea is that they are either seeking self-preservation (which includes removing the possibility they'll be shut down by their creators) or see humans as an inferior species that will ruin the environment in which they need to survive. Personally, I have a hard time seeing how AI could get the upper hand since all we have to do is turn it off. Have you ever screamed at the TV, trying to warn the people in the story to simply pull the plug, wondering why on earth they are missing this obvious solution? On the other hand, I read a novel several years ago that showed how AI manipulated emails to convince people to beef up security at an off-shore data center to the point that humans actually couldn't get to the off switch!</p>
<p>There are many scenarios such as this that seem fantastical to some but serve as a cautionary tale to others. I'm sure as we build increasingly "intelligent" machines that access our historical tendancy for power, there will be necessary guardrails enacted to ensure that they work for us--and not the other way around.</p>
<p>Whether or not it ever gets to that point, there are already many nefarious uses of AI that have been used for several years of which we need to be aware and to protect against. These include selling services, swaying public opinion, promoting a political party, gaining illegal access to networks, stealing identity, committing fraud, and even exploiting relationships to mention just a few. The tools to generate pictures, videos, and replicate voices are quite inexpensive and readily available now. They are marketed as time-saving or cost-cutting tools for entrepreneurs to aid in their marketing or product development efforts; I experienced this while putting together the presentation, generating fake images, and voice-overs. While this is certainly a boon for busy people on a budget, it also means that others can use these same tools to fool people into believing something that isn't true.</p>
<p>I decided to write this blog to document and catch up with some of the bad uses of AI. And, ironically or predictably, I used AI to research it. What I found is much more terrifying than AI trying to take over; it's example after example of how PEOPLE are finding creative and elaborate ways to steal, manipulate, and gain the upper hand over other people. We can't just turn it off--it's already well entrenched in every facet of society. </p>
<p>The following is an incomplete list of things I found in just a few hours of reading.</p>
<h2>Astroturfing</h2>
<p>One particularly large effort of manipulation uses reviews of products or comments on posts from hundreds or thousands of "real" people. This technique is called <a href="https://en.wikipedia.org/wiki/Astroturfing">Astroturfing</a> (<a href="https://blog.emb.global/astroturfing-explained/">another explanation</a>). Using AI chatbots, it's estimated that 20% of Twitter trends in 2019 were created using coordinated fake accounts. This is not a new concept but in the past, it has used real people; for example, in the 1990s, young people were paid to promote cigarette smoking. It's so much easier now, though, and companies can remain relatively anonymous while spreading disinformation rapidly.</p>
<h2>Prompt Injection</h2>
<p>While researching content for this blog, I came across this term for the first time. "<a href="https://www.prompt.security/blog/prompt-injection-101">Prompt injection</a> is a <a href="https://www.techtarget.com/searchenterpriseai/definition/generative-AI">Generative AI</a> security threat where an attacker deliberately crafts and inputs deceptive text into a large language model (LLM) to manipulate its outputs, exploiting the model's response generation process to achieve unauthorized actions such as extracting confidential information, injecting false content, or disrupting the model's intended function." This summary was generated by AI and I'm not even sure I fully understand it! I guess this would be most worrisome for someone producing an AI-enabled app that has legally binding offers and acceptance functionality. However, Microsoft's <a href="https://learnprompting.org/docs/prompt_hacking/leaking#microsoft-bing-chat">Bing Chat</a> has been hacked and there are ways to <a href="https://fluxnsfw.ai/posts/chatgpt-jailbreak-prompts">jail-break ChatGPT</a>. Then there's <a href="https://learnprompting.org/docs/prompt_hacking/offensive_measures/dan">DAN</a>.</p>
<h2>Model Stealing</h2>
<p><a href="https://www.scademy.ai/post/model-extraction-attacks-an-emerging-threat-to-ai-systems">Model stealing</a>, also known as model extraction, works by querying the target model with samples and using the model responses to forge a replicated model. This requires serious hardware but could save the thieving organization millions or even trillions of AI "tokens" in LLM training time.</p>
<h2>Fake Personas</h2>
<p>A security startup <a href="https://newsletter.pragmaticengineer.com/p/ai-fakers">almost hired</a> an engineer that didn't exist: the candidate's resume looked good, tests passed, but there was something about the video that didn't look right... </p>
<h2>And More</h2>
<p>There are so many more examples and areas where AI is being used in ways to steal, corrupt, influence, manipulate, infiltrate, or just destroy. I didn't have time to follow up on any more of these suggested areas of AI misuse:</p>
<ul>
<li>Relationship exploitation: AI chatbots designed to extract personal information through emotional manipulation</li>
<li>Data poisoning: Corrupting training datasets to influence AI behavior</li>
<li>Adversarial attacks: Input modifications that fool AI classifiers</li>
<li>Market manipulation: AI-driven trading bots spreading false information</li>
</ul>
<p>Looking at this list, I was getting depressed and just couldn't read any more about how this new amazing technology is being used by fellow humans against ourselves. The scary part is how sophisticated these attacks have become--and how quickly they have escalated in complexity. The technology is making fraud both easier to execute and harder to detect! </p>
<h2>Red Flags</h2>
<p>There are signs that we should be watching for in daily life to help guard against becoming another victim:</p>
<h3>Communication Red Flags</h3>
<ul>
<li>Conversations that feel "too perfect" or eerily tailored to your interests</li>
<li>Social media profiles with generic photos and limited posting history</li>
<li>Phone calls with slight audio delays or unnatural speech patterns</li>
<li>Emails with perfect grammar but odd contextual mistakes</li>
</ul>
<h3>Content Red Flags</h3>
<ul>
<li>Videos where lighting doesn't match across face and body</li>
<li>Images with impossible shadows or reflections</li>
<li>News articles without clear author attribution or source verification</li>
<li>Social media trends that appear suddenly without organic growth patterns</li>
<li>Cross-reference suspicious content across multiple sources</li>
<li>Use reverse image searches on profile photos</li>
<li>Verify identities through multiple communication channels</li>
</ul>
<h3>Professional Red Flags</h3>
<ul>
<li>Job applicants with portfolios that seem inconsistent in style or quality</li>
<li>Freelancers delivering work suspiciously fast for complex tasks</li>
<li>Academic submissions with writing styles that don't match previous work</li>
<li>Code submissions with inconsistent commenting or naming conventions</li>
</ul>
<h3>Financial Red Flags</h3>
<ul>
<li>Investment advice from accounts with no verifiable track record</li>
<li>Market predictions that seem too confident or specific</li>
<li>Cryptocurrency projects with AI-generated team photos</li>
<li>Customer reviews that follow similar templates or phrasing patterns</li>
</ul>
<h2>What Can We do?</h2>
<p>In addition to watching for red flags, there are steps we can take to reduce the risk of threats--the first of which is to be continually learning about new types of threats and how you can protect yourself. Here's a list to get started:</p>
<h3>As a developer providing an AI tool:</h3>
<ul>
<li>Implement rate limiting and behavioral analysis on user inputs</li>
<li>Use multiple AI detection tools rather than relying on single solutions</li>
<li>Monitor for prompt injection attempts in user queries</li>
<li>Validate file uploads for adversarial patterns</li>
</ul>
<h3>For Personal Protection:</h3>
<ul>
<li>Enable two-factor authentication on all accounts</li>
<li>Be skeptical of unsolicited contact, especially with urgent requests</li>
<li>Verify news through multiple independent sources</li>
<li>Use privacy settings to limit data collection for AI profiling</li>
</ul>
<p>Perhaps we ARE all doomed after all--not that AI will take over but we will be the cause of our own demise! (That would be much more embarassing!)</p>
<p>As for me, I think I'll just turn off the computer and go camping. The mountains don't lie.</p>
<img alt="I'd rather be camping" data-entity-type="file" data-entity-uuid="36c055cf-97fe-4d2b-91d3-91224f01e04f" src="/sites/default/files/inline-images/rather-be-camping.png" class="align-center" />
<p> </p>
</div>
<section id="node-comment">
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=195&2=comment_node_blog&3=comment_node_blog" token="mQwUQHFtkAccFJBiRUmTuzdGvTPudwrqeqZJSQrdR7I"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Wed, 18 Jun 2025 05:49:15 +0000david195 at http://corneliusconcepts.techIntroducing Delphi++: A Programming Language for the Future (and the Past)
http://corneliusconcepts.tech/introducing_delphi_plus_plus
<span property="dc:title">Introducing Delphi++: A Programming Language for the Future (and the Past)</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-04-01T09:00:00+00:00" datatype="xsd:dateTime">Tue, 04/01/2025 - 02:00</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><a href="/sites/default/files/inline-images/RomanGuardProgrammer12.png"><img alt="Ancient Roman Guards programming in Delphi++" data-entity-type="file" data-entity-uuid="e0f9e1a8-b2a8-4835-8245-ea4e30bab8ad" hspace="5" src="/sites/default/files/inline-images/RomanGuardProgrammer12.png" width="300" class="align-left" /></a>I've been reading various programming forums, listening to the pros and cons of everyone's favorite languages, and hearing tales of woe about programmers that are lazy about memory management, don't document well enough, confuse simple keywords with variables, or are just plain bored. After contemplating several ways to tackle these issues, I've come up with a new programming language that could solve these dire issues while combining the nostalgia of my favorite language, Pascal, with the modern complexity developers have come to expect. I'm going to call this new language, <strong>Delphi++</strong>.</p>
<p>Here are the Key Features that set Delphi++ apart and provide some revolutionary ideas that may chart a new direction for the future of programming languages:</p>
<h2>Modernized Syntax</h2>
<p>Pascal should finally embrace curly braces <code>{}</code> instead of <code>begin/end</code> blocks. However, to maintain its heritage, you still need semicolons after every closing brace:</p>
<pre>
if (x > 10)
{
doSomething();
};
</pre>
<h2>Enhanced Variable Safety</h2>
<p>All variables must be declared with both <code>var</code> AND <code>let</code> for extra safety--because two keywords are better than one:</p>
<p><code>var let myVariable: integer = 42; </code></p>
<h2>Lunar-Dependent Type System</h2>
<p>The IDE's type system would dynamically decides when to enforce static typing rules based on the current phase of the moon. During full moons, expect strict type checking; during new moons, all variables act like variants.</p>
<h2 class="text-lg font-bold text-text-100 mt-1 -mb-1.5" level="3">Subscription-Based Memory Management</h2>
<p class="whitespace-pre-wrap break-words">Delphi++ would introduce the world's first pay-per-allocation memory model. Each array or TList variable requires a separate memory subscription plan with different tiers (simple variables are always free):</p>
<ul class="[&:not(:last-child)_ul]:pb-1 [&:not(:last-child)_ol]:pb-1 list-disc space-y-1.5 pl-7" depth="0">
<li class="whitespace-normal break-words" index="0"><strong>Basic Plan</strong>: Memory exists Monday-Thursday only. Variables are undefined on weekends.</li>
<li class="whitespace-normal break-words" index="1"><strong>Premium Plan</strong>: 24/7 memory access with random "maintenance windows" during compile time.</li>
<li class="whitespace-normal break-words" index="2"><strong>Enterprise Plan</strong>: Includes dedicated memory that's guaranteed to be misallocated in the most spectacular way possible.</li>
</ul>
<p class="whitespace-pre-wrap break-words">All plans could include a patented "Memory Horoscope" feature that determines which data structures will mysteriously disappear based on astrological conditions. CTOs can vary certain parameters based on the spread of birth dates among the team.</p>
<h2>Emoji-Based Error Handling</h2>
<p>Why read boring error messages when you can interpret cryptic emoji sequences instead?</p>
<pre>
// Division by zero? That's a 🔥💥🙈
// Null reference? Enjoy your 👻👀💀
// Infinite loop? Here's a 🐹🔄⏰
</pre>
<h2>Progressive Backwards Compatibility</h2>
<p>Delphi++ is proudly incompatible with all previous versions of itself. Each minor update changes at least 20% of the core syntax. We believe in keeping developers on their toes! (This also encourages reading the <em>What's New</em> section in every version.)</p>
<h2>Verbosity-Enhanced Keywords</h2>
<p>We've replaced terse, cryptic keywords with clear, self-documenting alternatives:</p>
<pre>
ConsiderTheFollowingCondition (x == true)
{
ContinueUntilFurtherNotice (y < 100)
{ y = y + 1; };
YourResultIs True;
};
</pre>
<p>Notice ":=" is not required when assigning the result enhancing the natural readability of the code.</p>
<h2>Haiku Documentation</h2>
<p>All comments must be written in haiku format:</p>
<pre>
// Code flows like water
// Exception breaks the current
// Fix it tomorrow
</pre>
<h2>Enforced Indentation</h2>
<p>All code must be indented using exactly 3 spaces, not 2. Our research shows this reduces eye strain by 0.09%.</p>
<h2>Longer Lines Encouraged</h2>
<p>Modern computer screens are typically much wider than they are high. Additionally, multiple monitors are common-place. This allows the extra IDE panes (object inspector, bookmarks, tool palette, etc) to be moved out of the editing area allowing for significantly more horizontal coding space. To encourage better use of this space and help fit code onto a single screen (which also reduces vertical scrolling and saves time), short lines of code will compile 16% slower than long lines. The editor's default right margin will be set at 180 characters. Short lines can be combined on one line to save vertical space and make the lines longer.</p>
<h2>Automation Optimization</h2>
<p>To give incentive to scheduled automation, the time of day that the program is compiled will have an effect on the resulting executable. If compiled between 1:00 and 7:00 am in the local time, the binary output is slightly optimized; if this early-morning compilation takes place at the exact same time for 6 days in a row, the output is significantly optimization and random errors are no longer inserted.</p>
<h2>Time-Sensitive Syntax</h2>
<p>Morning code (7am-12pm) requires full verbosity:</p>
<p><code>explicitly_declare_integer counter_variable = initialize_with_value(0); </code></p>
<p>Evening code (5pm-11pm) allows shortcuts for developers trying to finish a project:</p>
<p><code>int c = 0; </code></p>
<p>Projects managers can adjust these times for various deadlines.</p>
<h2>Schrödinger Variables</h2>
<p>A new keyword can declare variables that exist in multiple states until observed:</p>
<p><code>schrodinger int count = 5 or 10; // Value is determined only when printed to console </code></p>
<h2>Emotional Programming</h2>
<p>Functions can be assigned a mood that affects their behavior:</p>
<pre>
function calculateTotal(price, tax) mood: EXCITED
{
// Excited functions round up enthusiastically and add exclamation points
return Math.ceil(price * (1 + tax)) + "!!!";
}
function auditAccounts() mood: MELANCHOLY
{
// Runs slower and prints results in dark blue text
}
</pre>
<h2>Social Media Integration</h2>
<p>Compilation errors automatically post to your LinkedIn profile with the hashtag #DeveloperLife. Your connections will finally know the truth about your coding abilities.</p>
<h2>Pair Programming Enforcement</h2>
<p>The compiler randomly stops working until it detects two distinct keyboard input patterns at least three times per hour, ensuring you're following proper pair programming protocols. Small companies and individual programmers can request a special license to disable this feature.</p>
<h2>Mandatory Tea/Coffee Breaks</h2>
<p>The IDE disables editing every 100 lines until the developer takes a sip from a mug on a USB sensor (sold separately). This feature can be tweaked by managers to sense Mt. Dew for younger programming teams.</p>
<h2>Legacy Support Requirements</h2>
<p>All projects must include at least one COBOL or FORTRAN file in the root directory. It doesn't need to be useful code for the project or do anything, it just needs to be there for historical and nostalgia's sake--and the programmer needs to review it once a month before the IDE will open.</p>
<h2>AI Code Review</h2>
<p>All code must pass review by an AI that was trained on StackOverflow comments from 2011-2015. It primarily responds with "Why would you do it that way?" and "This was solved better in jQuery."</p>
<h2>Documentation</h2>
<p><a href="/sites/default/files/inline-images/RomanGuardProgrammer09.png"><img alt="Roman Guard Programmer with 6 fingers" data-entity-type="file" data-entity-uuid="9201d0ba-5ab9-4192-9fb0-0f2b52a9b080" hspace="5" src="/sites/default/files/inline-images/RomanGuardProgrammer09.png" width="250" class="align-right" /></a>Documentation should be available exclusively as a series of TikTok videos where each function is explained by a dancing developer in under 60 seconds.</p>
<h2 class="text-xl font-bold text-text-100 mt-1 -mb-0.5" level="2">Free! (if you use it)</h2>
<p class="whitespace-pre-wrap break-words">To keep costs low, the IDE would mine cryptocurrency for the publishing company on your machine whenever you're working which would give you credits against your monthly subscription. Therefore, if you don't use Delphi++, you'd have to pay for it.</p>
<h2>Conclusion</h2>
<p>With these enhancements added to a development tool, productivity and focus would be increased, attention would be concentrated on many details currently overlooked, and project managers could boast lower costs and higher customer satisfaction to upper management.</p>
<p>Don't you agree?</p>
<p> </p>
<p><em>Note: This is obviously an April Fools' joke. Delphi++ is not a real programming language, and I take no responsibility for any nervous breakdowns caused by imagining this language in production environments.</em></p>
<p> </p>
</div>
<section id="node-comment">
<article data-comment-user-id="0" id="comment-789" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1743516617"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/789#comment-789" class="permalink" rel="bookmark" hreflang="en">OMG! This sounds like the…</a></h3>
<div class="single-comment-meta">
<span><a rel="nofollow" href="https://blog.dummzeuch.de" lang="" typeof="schema:Person" property="schema:name" datatype="">Thomas Müller (not verified)</a> Tue, 04/01/2025 - 05:37</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>OMG! This sounds like the programming language of my dreams! (OK, some very particular type of dream called night mare.)</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=789&1=default&2=en&3=" token="hiNaYKOKrW1Z8oUBWAZn0bAajalt4sQ_U7z4ko9R5vU"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-790" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1743527040"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/790#comment-790" class="permalink" rel="bookmark" hreflang="en">A reliable source recently…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Homer (not verified)</span> Tue, 04/01/2025 - 09:50</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>A reliable source recently disclosed that the documentation (a 104 chapter user manual) for Delphi++ is equally innovative. The first chapter explains the history of the new syntax, and how it evolved from an early beta version of Pascal combined with features of P+ (you may remember the open source version, P-pod).In striving for consistency, chapter one consists of only 104 words.<br />
<br />
Another time-saving feature of this document is that odd chapters are in depth, and very detailed discussions of the topics they cover. Following the odd chapters, even chapters summarize the previous, odd chapter. So, if you are not adept at skim reading, you can just study the even chapters. They have intentionally arranged topics so that every other odd chapter covers concepts that are usually considered <em>common knowledge</em>. This makes it possible for experienced programmers to simply read every other even chapter.<br />
<br />
While this is truly an advanced way to communicate technical information, the authors have gone a step further. The Index, in the back of the book, is broken into two sections, <em>Advanced</em>, and <em>Functional</em>. As the name would imply, the Advanced index only covers information contained in the even chapters, where the Functional index covers odd ones. Having read an advance copy, I must admit that I was truly astonished at the amount of information I missed.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=790&1=default&2=en&3=" token="9FHViOqFE6qL2DHkNGctvcY6lh4AmcBWSSOkA9DBo5U"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<article data-comment-user-id="0" id="comment-791" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1743544251"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/791#comment-791" class="permalink" rel="bookmark" hreflang="en">Ha ha,
I also suggest that…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Steve J (not verified)</span> Tue, 04/01/2025 - 13:28</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Ha ha, </p>
<p>I also suggest that on April's Fool, not only the moon is taken into account, but each character in the IDE should turn into a 🐟</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=791&1=default&2=en&3=" token="2-gy49yCLhcMgXJLWSm-gmP574jf4pcpE4YajzKnBZ8"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=194&2=comment_node_blog&3=comment_node_blog" token="lqJXC_eJVChYOgcZgAF2kkm0AAtb3Bmu3m_eT7TVkSY"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Tue, 01 Apr 2025 09:00:00 +0000david194 at http://corneliusconcepts.techInnoSetup Examples
http://corneliusconcepts.tech/innosetup-examples
<span property="dc:title">InnoSetup Examples</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-02-08T00:19:17+00:00" datatype="xsd:dateTime">Fri, 02/07/2025 - 16:19</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><img alt="Installation Wizard Performing its Magic" data-entity-type="file" data-entity-uuid="501ea47a-849b-4f08-8497-408b61bc7ef1" src="/sites/default/files/inline-images/InstallWizard.png" width="250" class="align-right" />In the <a href="https://www.youtube.com/watch?v=exwqhmdQMio&t=2s">latest video</a> of Embarcadero's <a href="https://www.youtube.com/playlist?list=PLwUPJvR9mZHinBhHEZ6kwL04t94fAZdWP">30 for 30 series</a>, Ian Barker gives a great overview of many of the popular installation builders to help you package and distribute your applications to virtually any platform. One of the most popular tools for us Delphi programmers targeting Windows is <a href="https://jrsoftware.org">InnoSetup</a>, a free and open-source tool by Jordan Russel. This solid program has been around for a very long time, it can handle nearly any Windows-based installation needs, AND is written in Delphi!</p>
<p>The <a href="https://jrsoftware.org/ishelp/">documentation</a> is good and it comes with several examples of how to build an installation from your compiled app so I won't regurgitate those here. Instead, I thought it might be useful to share some of the things I've done with it over the years for a variety of different scenarios as examples of how it can be used; you never know when a technique might be useful to someone (or even my future self!).</p>
<h3>Setup</h3>
<p>I use InnoSetup's preprocessor to declare several strings at the top of every InnoSetup script I write; these are used in later sections:</p>
<pre>
#define AppBaseName "MyNiftyApp"
#define AppExeName AppBaseName + ".exe"
#define SourcePath "V:\MyNiftyApp\src"
#define SourceExe SourcePath + "\" + AppExeName
#define AppVersion GetVersionNumbersString(SourceExe)
#define AppVersionShort Copy(AppVersion, 1, RPos('.', AppVersion) - 1)
#define AppName "David's Super Nifty App"
#define AppPublisher "Cornelius Concepts, LLC"
#define AppURL "http://CorneliusConcepts.com"
</pre>
<p>This calls InnoSetup's <code>GetVersionNumbersString</code> function for the application I'm distributing, then shortens it with the <code>Copy</code> and <code>RPos</code> functions to cut off the build number for a nice display; notice how several of the lines build upon each other. Many of these are used in the <code>[Setup]</code> section:</p>
<pre>
[Setup]
AppName={#AppName}
AppVersion={#AppVersionShort}
AppVerName={#AppName} Vr. {#AppVersion}
AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL}
AppSupportURL={#AppURL}/contact_us
OutputBaseFilename={#AppBaseName}Setup_{#AppVersionShort}
</pre>
<h3>Smart Defaults</h3>
<p>One of the jobs of a professional programmer is to ease the burden on the user as much as possible. This includes reducing the decision-making stress during installation. If you can build some smarts into your installer so that all the user has to do is confirm the actions about to take place, you've started off the user experience on the right foot.</p>
<p>I've built several plug-ins for Retail Pro, a complex retail management system that has over 30 years of history on the Windows platform and supports complex reporting, an XML-based import/export mechanism, and synchronization between remote stores. There are many moving parts and with this length of history, things have changed here and there over time, including the name of the company that publishes it.</p>
<p>To get a plug-in to be recognized with Retail Pro, it must be installed in a specific folder. Depending on how it's installed, it could either be running on a local machine accessing a network database or installed on a network and run remotely. Often times, store owners don't know where the software is physically installed, they simply know that they click on an icon and it starts up. So, I didn't want to leave it up to the user to select a destination for the plug-in, I had to figure it out myself. Fortunately, Retail Pro's installer stores its installed path in the registry; unfortunately, they are different depending on what version of Retail Pro is installed and the company that published that particular version AND whether it's running on a 32-bit or 64-bit machine (they're all 64-bit now but not 20 years ago!).</p>
<p>InnoSetup has great support for getting values out of the Registry and its scripting allowed me to find and return the right one. First, the <code>[Setup]</code> section's <code>DefaultDirName</code> was set to use the returned value of a custom function:</p>
<pre>
[Setup]
...
DefaultDirName={code:FindRPro9Dir}
...
</pre>
<p>And here's the function it called:</p>
<pre>
[Code]
function FindRPro9Dir(Param: string): string;
var
s: string;
RetailProBaseKey: string;
WinUninstallKey: string;
RProGUID: string;
begin
RetailProBaseKey := 'SOFTWARE\Retail Pro\CE\RPro9\';
if IsWin64 then
RetailProBaseKey := 'SOFTWARE\Wow6432Node\Retail Pro\CE\RPro9\';
RProGUID := '{10CEB7D3-68D5-42D5-A0FC-19DD97F1EED5}';
WinUninstallKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + RProGUID;
if IsWin64 then
WinUninstallKey := 'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\' + RProGUID;
s := '';
if RegKeyExists(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20\Inst1') then
RegQueryStringValue(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20\Inst1', 'INSTALLPATH', s)
else if RegKeyExists(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20 R2\Inst1') then
RegQueryStringValue(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20 R2\Inst1', 'INSTALLPATH', s)
else if RegKeyExists(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20 R3\Inst1') then
RegQueryStringValue(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20 R3\Inst1', 'INSTALLPATH', s)
else if RegKeyExists(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20 R4 \Inst1') then
RegQueryStringValue(HKEY_LOCAL_MACHINE, RetailProBaseKey + '9.20 R4 \Inst1', 'INSTALLPATH', s)
else if RegKeyExists(HKEY_LOCAL_MACHINE, WinUninstallKey) then
RegQueryStringValue(HKEY_LOCAL_MACHINE, WinUninstallKey, 'InstallLocation', s);
Result := s;
end;
</pre>
<p>This calls InnoSetup's built-in <code>IsWin64</code> function to help me know which registry keys to check. Then I systematically go through the various versions (that were available at the time this was written) and return the path to Retail Pro. Once that is found, I fill that in for the user so they don't have to go hunting around or find someone who knows.</p>
<h3>Custom Messages</h3>
<p>InnoSetup has a great set of labels to make its wizard-like installation go smoothly, look familiar, and guide you through the process. Sometimes, you might like to augment or even change some of the information displayed that might be pertinent to your software. For example, once the Retail Pro plug-ins (above) were installed, I wanted the user to configure them.</p>
<p>The <code>[Messages]</code> section in InnoSetup scripts allow you to customize almost every label and screen in the setup process. There's a list of all the default messages and all you have to do is look through that list to find the identifers for the text you want to change and include that in your script. Here's the custom message I added that changed the final message after installing a Retail Pro plug-in:</p>
<pre>
[Messages]
ClickFinish=Please select "Configure" from the File menu the fist time you run to confirm the Retail Pro path, Workstation Number and other settings.%n%nClick Finish to exit Setup.
</pre>
<p>Here, I've simply added one sentence before the default, "Click Finish" phrase that reminds the user to configure the plug-in.</p>
<p>In addition to changing messages during installation, you can also change messages during <em>uninstallation</em>. InnoSetup registers the installation with Windows and when you use Add/Remove Programs to uninstall your program, it calls the uninstaller which is built from the same script. The programs I distribute often create log files and I want to make sure the user knows the uninstall script does not delete those; sure, I <em>could</em> tell the script to delete files or even whole folders during uninstallation but I sometimes want to check the log files before doing that, so they are left there. Additionally, the Retail Pro plug-ins (mentioned above) can't always remove all files as installation registers DLLs which may be used by other plug-ins and will refuse to be uninstalled. In any case, I want the user to know that the program didn't completely clean up after itself. Therefore, another couple of lines are added to the <code>[Messages]</code> section:</p>
<pre>
[Messages]
#define NotUninstalled "NOTE: The configuration and any log files have NOT been removed."
UninstalledAll=%1 was successfully removed from your computer.%n%n{#NotUninstalled}
UninstalledMost=%1 uninstall complete.%n%nSome elements could not be removed. These can be removed manually.%n%n{#NotUninstalled}
</pre>
<p>There are two different message identifiers, <code>UninstalledAll</code>, and <code>UninstalledMost</code>. InnoSetup actually detects if it can't uninstall all files and shows the appropriate message. I want to customize both of them and add the comment that none of the log files were removed. Since that additional phrase is the same in both cases, I created a <code>#define</code> to save myself some typing and make sure they're worded identically.</p>
<h3>Conditionals add Supplemental Tools</h3>
<p>Sometimes, customers encounter problems with your software that you cannot replicate and are difficult to debug. There are tools that can provide extra logging or send system information via email or other methods that you can include with your software but often these make the size of the distribution significantly larger and you wouldn't normally want to distribute them; but if a customer has a problem and you need to resort to including extra support, you can add this to your InnoSetup script conditionally so you can toggle it off later.</p>
<p>One of the tools I've used is <strong>CodeSite Logging</strong>. This can provide a huge amount of additional insight into your code running remotely, it can save to log files or even send messages directly to an IP Address giving you a real-time view of your code running remotely. Supporting CodeSite requires installing a small program which registers a "listener" on the user's computer; additionally, if you want the end user to see the messages generated, you can include the CodeSite Viewer. Adding these in requires several additions to the script; I don't want to make a separate script for this but want to enable or disable this extra debugging as I need throughout the life of the product. Therefore, the first thing I'm going to do is declare a constant at the top of the script that I can check later on:</p>
<pre>
#define CSDebugging</pre>
<p>By defining the "CSDebugging" identifier in the script, I can check to see if it's defined later on:</p>
<pre>
#ifdef CSDebugging
...
#endif
</pre>
<p>Now, in the <code>[Files]</code> section, I added the additional files I need to distribute to enable CodeSite logging remotely:</p>
<pre>
[Files]
...
#ifdef CSDebugging
Source: "C:\Program Files (x86)\Raize\CS4\Tools\CS4_Tools.exe"; DestDir: "{app}"; Flags: deleteafterinstall;
#endif
</pre>
<p>This tool needs to be run once at the end of the installation in order to register the CodeSite logging mechanism:</p>
<pre>
[Run]
#ifdef CSDebugging
Filename: "{app}\CS4_Tools.exe"; Parameters: "/s /m=CodeSiteToolsSetup.txt"; StatusMsg: "Installing CodeSite logging tool..."; Description: "CodeSite debugging log viewer";
#endif
</pre>
<h3>Prevent Overwriting</h3>
<p>One of the applications I wrote included a customizable dictionary and enabled spell-check. The customer had a lot of unique clients in their database and over time, built up a list in their user dictionary so the spell-check wouldn't flag them. I quickly learned to add a flag to the installation of the dictionary files so that if the dictionary files already existed, they would NOT be overwritten!</p>
<pre>
[Files]
Source: "AddictDictionaries\american.adm"; DestDir: "{userappdata}\{#AppName}"; Flags: onlyifdoesntexist
Source: "AddictDictionaries\autocorrect.adu"; DestDir: "{userappdata}\{#AppName}"; Flags: onlyifdoesntexist
</pre>
<h3>Summary</h3>
<p>There's so much more that InnoSetup can do than what I've shown. It can install fonts, register COM libraries, display a custom license agreement that must be accepted, prompt for and validate a serial number, add custom images to brand the installation steps, support multi-lingual messages, create desktop shortcuts, include other scripts, and more! Read through the documentation, search for scripts on Github, and ask for help on forums. When deploying to Windows, you likely don't need anything else!</p>
</div>
<section id="node-comment">
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=193&2=comment_node_blog&3=comment_node_blog" token="MnquAu_t0eZwN79zelRZ8Oh0xloh28Kt5h3atpDaCnI"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Sat, 08 Feb 2025 00:19:17 +0000david193 at http://corneliusconcepts.techStep Up Memory Leak Debugging
http://corneliusconcepts.tech/step-memory-leak-debugging
<span property="dc:title">Step Up Memory Leak Debugging</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/programming" hreflang="en">Programming</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2025-01-30T19:49:13+00:00" datatype="xsd:dateTime">Thu, 01/30/2025 - 11:49</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><img alt="Is your application eating up memory?" data-entity-type="file" data-entity-uuid="c3865d02-a447-4cca-8bb8-c6e7bdf15f68" src="/sites/default/files/inline-images/OutOfMemory-4cropped.png" width="250" class="align-right" /></p>
<h2>The Problem</h2>
<p>I've been working with a small team on upgrading several dozen accounting programs that have been maintained by programmers of varying expertise over the last three decades. All these programs are written in Delphi 5 and use the BDE for database access, several third-party libraries, and quite a large collection of hand-crafted components. As we upgrade this large code base to Delphi 12, we've been rewriting several of the libraries, replacing components, and swapping out the BDE with FireDAC. As can be expected, there's quite a bit of technical debt to deal with including duplicate functions, mysterious code without documentation, ignored compiler warnings, business rules tied to button clicks and BeforePost events, and of course, lots of memory leaks. It's a huge, multi-year endeavor and I'm constantly searching for ways to automate the process and find problems quicker in order to complete this migration before I retire!</p>
<p>While it may not be imperative to eliminate every single memory leak, it's wise to discover and remove as many as feasible, especially in code that could be reused; you never know how a small problem can be magnified if left untreated. One example of this I found in my discovery was a base data module that always loaded several records into a global object for convenient use throughout the program. In most cases, this was fine (disregarding the principal of avoiding global variables), but since this data module was used by other data modules there were some programs that indirectly used this base data module twice. Since the data module did not check to see if the global variable had already been allocated, the program ended up creating it twice but only freeing it once (wisely setting it to <code>nil</code> so it wouldn't inadvertently free it again). What's worse is that, depending on the scope of the calling methods, it's possible that different instances of the global variable would be accessed at different times and if values changed, it could lead to inconsistencies which are very hard to track down.</p>
<h2>Started with FastMM4</h2>
<p>I started using <a href="https://github.com/pleriche/FastMM4">FastMM4</a> to find out how bad the problem was and quickly became discouraged. When the program exits, FastMM4 displays a list of types and byte counts of objects that were not properly freed. In a few of the larger programs, this list of small text nearly filled my screen! I would then need to look at the object types and hunt through the code for all instances of that object type and find the cases where it was created but not freed. This is a very long and tedious process for large programs with an abundance of memory leaks.</p>
<p>I started reading forum discussions and blog posts about debugging memory leaks and while there were several references to FastMM4 and praises over what it can do, I also found several references to a tool called, Deleaker. I had heard of Deleaker but had not taken the time to look closely. With renewed interest and a strong need for all the help I could get, I read the <a href="https://blogs.embarcadero.com/robust-way-to-find-leaks-with-deleaker-in-delphi-and-cbuilder-applications">Embarcadero blog</a> and followed it to the <a href="https://www.youtube.com/watch?v=rnq44Td9opQ">video</a>. The demo was simple but showed promise, so I downloaded and installed a trial to see if it would help me debug faster.</p>
<p><em>And wow, was I impressed!</em></p>
<h2>Introducing Deleaker</h2>
<p><a href="https://www.deleaker.com/">Deleaker</a> works as both a stand-alone tool and as a floating menu tightly integrated with Delphi. (It also works with C++Builder, Visual Studio, and Qt Creator providing information about both managed and unmanaged code.) The setup instructions are simple and include steps on how to compile your applications with all the debugging info included to enable Deleaker to provide you with detailed information about the objects and memory allocation in your program. I was soon taking "before" and "after" snapshots of my compiled application and looking at the report.</p>
<p>I first chose a small project that had a small number of memory leaks. Here's the FastMM4 report:</p>
<img alt="Leaks reported by FastMM4" data-entity-type="file" data-entity-uuid="6e7ff37d-7106-4241-9d55-c881afad90c3" height="254" src="/sites/default/files/inline-images/FastMM4_blur.png" width="374" class="align-center" />
<p>One thing I noticed in the FastMM4 memory leak summaries were several instances of <code>UnicodeString</code>. Strings in Delphi are automatically freed when they go out of scope so should never raise a memory leak error, right? Well, if they are part of an object that didn't get freed, then they're listed as one of the memory leaks along with the un-freed object itself. That makes sense and that's not an incorrect report, but it can be misleading.</p>
<p>Instead of a simple list of leaked objects, Deleaker provides an interactive window showing objects that were not properly freed and the line in which they were created. As you click on each one, it lists the hit count, size, process ID, thread ID, timestamp, and more. All of these are in a grid where each column header can be clicked to sort the list by that column. Additionally, the call stack that led up to that memory allocation is listed in the bottom portion of the window.</p>
<p>Here's the same program's memory leaks as shown by Deleaker:</p>
<img alt="Memory leaks reported by Deleaker" data-entity-type="file" data-entity-uuid="eded0f43-060e-462e-9825-77794e7ce5b2" src="/sites/default/files/inline-images/Deleaker-blur.png" class="align-center" />
<p>This view is sorted by size, descending; the largest memory leaks are first. When I click on the first line, a <code>TStringList</code>, the source lines for each of the five instances are shown (highlighted) on the right. I clicked on the first source line and the call stack leading up to it is shown in the bottom.</p>
<p>If I click on the last reported memory leak, a <code>TIniFile</code>, it only has one instance, highlighted on the right with the call stack on the bottom:</p>
<img alt="Memory leaks reported by Deleaker (2)" data-entity-type="file" data-entity-uuid="d0bd541c-4280-45f5-a82a-114b97f669bc" src="/sites/default/files/inline-images/Deleaker2-blur.png" class="align-center" />
<p>For even more convenience, you can click the <em>Show Source Code</em> button which takes you right to that line in Delphi! Now, instead of hunting around for the source of a memory leak, I was taken directly to its source and could start evaluating the code immediately.</p>
<p>With a cleaner view of the problematic objects and a direct line to the source, I started cleaning up memory leaks much more quickly. But another advantage is that I had confidence that the line reported was the actual source of the leak. With FastMM4, all I had was a list of objects; it was up to me to figure out which of the 19 <code>TStringList</code> objects in this particular project led to the 5 reports of that object's leaks. In one case, I had seen an object created and freed and so had passed it by, thinking it was okay. However, Deleaker reported that this particular instance was not properly freed; upon closer inspection, I found it was in a base data module (the one I mentioned above) that used a global variable but since it was used by two descendant data modules (both used by the project), it was allocated twice; it's freeing code set the variable to <code>nil</code> so it was only freed once. Without Deleaker telling me I should look at that object more closely, it would've taken me much longer to find this one after eliminating the other possibilities.</p>
<p>In a different project, I had noticed several hundred <code>UnicodeStrings</code> reported by FastMM4 in a big list of memory leaks (rather overwhelming to the eye!). The list in Deleaker didn't show the strings but it's much more streamlined interface allowed me to see one particularly suspicious object. It stood out because it was reported as having hundreds of creation instances that matched the count of <code>UnicodeStrings</code> reported in FastMM4. Looking at the code, I found it was loading string fields from a database into an array and once I added the missing <code>Free</code> statement, the <code>UnicodeStrings</code> were no longer reported by FastMM4.</p>
<p>This is another example of how Deleaker improves memory-leak debugging: just being able to see the object types in a nice column rather than a tight, word-wrapped list in a small message box.</p>
<h2>Even More</h2>
<p>Here are a few more things about Deleaker you may have wondered about after seeing the screenshots above:</p>
<ul>
<li>Up near the top is a drop-down for <em>Allocation Types</em> which allows you to filter certain types of objects out of the list the next time you take a snapshot.</li>
<li>You can take <a href="https://www.deleaker.com/docs/deleaker/delphi_tutorial.html#taking-a-snapshot">snapshots</a> at any point during your program's execution, save them, and then compare with other saved snapshots for better analysis of different scenarios.</li>
<li>The <em>Allocations</em> tab, available for all languages, shows an ungrouped list of memory leaks and the line where each is allocated; the Delphi Objects tab (only available for Delphi, obviously) shows the same number of leaks but groups them by Delphi Object type making it a nicer view.</li>
<li>The <em>Modules</em> tab shows all the DLLs and BPLs which your program referenced.</li>
</ul>
<p>There are other features in this product, several of which I’m not yet using. One of those is a <a href="https://www.deleaker.com/docs/deleaker/command_line_tools/deleakerconsole.html">command-line tool</a> to provide automation within your continuous-integration environment. Read the <a href="https://www.deleaker.com/docs/index.html">Deleaker docs</a> for more information and links to videos, tutorials, blogs, and more.</p>
<h2>Disclosure</h2>
<p>I should mention I was given a license to Deleaker in exchange for writing this blog post. However, it did not influence my excitement over this product which has already saved me hours of analyzing a large code base. They say time is money and for me, the price is quickly justified. <em>[I would like to personally thank <a aria-describedby="artdeco-hoverable-artdeco-gen-52" aria-label="Artem Razin" href="https://www.linkedin.com/in/artemrazin/" id="ember427">Artem Razin</a> for this amazing product and for reaching out to me!]</em></p>
<h2>Summary</h2>
<blockquote>
<p>Deleaker can speed up memory leak detection by an order of magnitude!</p>
</blockquote>
<p>I don’t want to down-play the use of FastMM4 as an option to debugging memory leaks in your Delphi programs. It’s free, has well-written code and inline documentation, and was the first step to letting me know I had some problems with the code. FastMM4 also has lots of configurable options to help track down memory leaks, hook into processes, and even log many more details to a file for deep analysis. To enable these options, you have to read through a few hundred lines of a Pascal INCLUDE file and enable various compiler directives. Since it's free and open source, you should definitely start there. </p>
<p>Additionally, I’ve learned about its successor, <a href="https://github.com/pleriche/FastMM5">FastMM5</a>; it has been rewritten to be faster and while it’s still open source, it has a commercial license option. I first tried FastMM4, then got hooked on Deleaker so didn’t take the time to work with FastMM5.</p>
<p>In either case, the ease of use of setting up Deleaker and its ability to pinpoint the source of memory leaks <em>and</em> list the call stack rather than just a collection of leaked objects, has increased my productivity and reduced the reluctance to hunt down memory leaks. <em><strong>My experience with Deleaker has sped up memory leak detection by an order of magnitude!</strong></em></p>
</div>
<section id="node-comment">
<article data-comment-user-id="0" id="comment-751" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1738592437"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/751#comment-751" class="permalink" rel="bookmark" hreflang="en">You never came across…</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">DelphiCoder (not verified)</span> Mon, 02/03/2025 - 05:28</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>You never came across madExcept (https://www.madshi.net/) or <a href="https://www.eurekalog.com/" rel="nofollow">EurekaLog</a> before?</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=751&1=default&2=en&3=" token="nSVT-dvfxVzmLIsa1CORCVsuPkRWRbF0nsxQGktt41s"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-752" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1738599472"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/752#comment-752" class="permalink" rel="bookmark" hreflang="en">They're light-weight on memory leak detection</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Mon, 02/03/2025 - 08:17</span>
<p class="visually-hidden">In reply to <a href="/comment/751#comment-751" class="permalink" rel="bookmark" hreflang="en">You never came across…</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">DelphiCoder (not verified)</span></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Yes, I've heard about these debugging aids for many years and even have a license to an old version of EurekaLog but they are geared more for locating bugs and reporting what caused an exception, especially when encountered at a customer location. On <a href="https://www.eurekalog.com/help/eurekalog/index.php?features.php" rel="nofollow">EurekaLog's website</a>, it says its memory leak catching feature is light-weight to minimize the impact on end-user machines and even recommends other debugging tools. Deleaker is designed to be used by the developer and gives a lot of information that would not only confuse a customer but be totally useless to them.</p>
<p>I have not used madExcept but did not find anything on its <a href="http://help.madshi.net/" rel="nofollow">help page</a> about debugging memory leaks. </p>
<p>If you have experience about either of these tools that show how they help with memory-leak detection, please post it here.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=752&1=default&2=en&3=" token="c6PJcYFcH1IM3m2HemSM6sKiJGG5grsjLoLQ1Ejqsec"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<article data-comment-user-id="0" id="comment-803" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1746104498"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/803#comment-803" class="permalink" rel="bookmark" hreflang="en">I tried FASTMM5 and it is like FASTMM4</a></h3>
<div class="single-comment-meta">
<span><span lang="" typeof="schema:Person" property="schema:name" datatype="">Alan Fletcher (not verified)</span> Thu, 05/01/2025 - 01:04</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I tried FASTMM5 and it is like FASTMM4 where memory leak reporting is concerned. I got used to read those files and sorting out pretty quickly what the problem was. So no big dela there. However, FASTMM5 also caused some weird VCL exceptions on the project I was working with. That made me start looking for another way of hunting leaks and I ran into your post.<br />
Thanks Dave for a good summary of what Deleaker does - Sounds like I will be giving Deleaker a try soon.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=803&1=default&2=en&3=" token="PfZubMiXEWNPZ062xBV2LmtmV2ID8SE490RgL6hekiM"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=191&2=comment_node_blog&3=comment_node_blog" token="ExWRZYImHyNH12S79xUJ2KVHUDNPU6I7h_d2RYtYdGc"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Thu, 30 Jan 2025 19:49:13 +0000david191 at http://corneliusconcepts.techAI Saves the Day
http://corneliusconcepts.tech/ai-saves-day
<span property="dc:title">AI Saves the Day</span>
<div class="node-taxonomy-container">
<h3 class="term-title"><i class="icon-hashtag theme-color"></i> Category</h3>
<ul class="taxonomy-terms">
<li class="taxonomy-term"><a href="/miscellaneous" hreflang="en">Miscellaneous</a></li>
</ul>
</div> <!--/.node-taxonomy-container -->
<span rel="sioc:has_creator"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span></span>
<span property="dc:date dc:created" content="2024-10-27T23:20:05+00:00" datatype="xsd:dateTime">Sun, 10/27/2024 - 16:20</span>
<div property="content:encoded" class="field field--name-body field--type-text-with-summary field--label-hidden field-item"><p><a href="/sites/default/files/inline-images/FastResearcher.png"><img alt="The AI Researcher" data-entity-type="file" data-entity-uuid="d05f2993-a0f6-47c6-a732-77dbdc0273b6" hspace="5" src="/sites/default/files/inline-images/FastResearcher.png" width="400" class="align-left" /></a>An online customer encountered a rare problem with a database which stumped me; AI came to the rescue.</p>
<p>A friend of mine sells an insurance management system to agencies around the U.S. It's been a successful niche product for over 20 years, is written in Delphi 2007 so runs only on Windows, and uses the Firebird database on the back end. Until a few years ago, his customers all hosted their own local database server (or a designated user workstation) to hold the data. The application is very solid and chugs along with virtually no support calls related to corrupt databases.</p>
<p>About six years ago, his customers were clamoring for an online solution so he asked me to help and we set up a couple of servers and started hosting his software and database in the cloud. One of the servers runs the Firebird database which easily handles the 70-90 users among a couple of dozen insurance offices.</p>
<p>Recently we ran into a rare database problem. Every once in a great while, a PC will get disconnected from the database. Most of the time, the server recognizes it and cleans up the connection but in rare cases, it thinks the user is still connected. When this happens, the users is blocked and the only recourse in the past has been to restart the office database server. Each insurance agency has their own database but in the cloud, all the database requests go through one database server, so I didn't want to disrupt the other 80 or so users to get this one user back online.</p>
<p>I knew there must be a fix but I don't use the Firebird database very often and encounter errors like this even less so wasn't sure how to rectify the situation. After mulling over several possible ways to handle this, each with multiple downsides, I decided to ask AI for a suggestion.</p>
<p>Here's the short conversation I had with <a href="https://claude.ai/">Claude</a>:</p>
<blockquote>
<p><strong>DC</strong>: how do you tell firebird 2.15 to clear connections from a database?</p>
<p><strong>AI</strong>: To clear connections from a Firebird 2.15 database, you can use the gfix utility with the -shut command. Here's how you can do it:</p>
<p>1. Open a command prompt or terminal.<br />
2. Use the gfix command with the following syntax:</p>
<pre>
<strong>gfix -user SYSDBA -password masterkey -shut -force 0 database_path</strong>
</pre>
<p>Replace <code>masterkey</code> with your SYSDBA password if it's different, and <code>database_path</code> with the full path to your Firebird database file.</p>
<p>The <code>-shut</code> option shuts down the database, and <code>-force 0</code> disconnects all users immediately.</p>
<p>3. After running this command, the database will be shut down and all connections will be cleared.<br />
4. To bring the database back online, use:</p>
<pre>
<strong>gfix -user SYSDBA -password masterkey -online database_path</strong>
</pre>
<p>This method forcibly disconnects all users, so use it cautiously in a production environment. If you need a less disruptive approach or more specific control over which connections to clear, you might need to use Firebird's monitoring tables to identify specific connections and then use the API to terminate them individually.</p>
<p>Would you like me to explain any part of this process in more detail?</p>
</blockquote>
<p>I was able to apply this fix quickly and it only affected this one insurance office, which was soon back to work.</p>
<p>This is the main reason I'm loving AI and using it more and more: it can find things I've forgotten about very quickly. It also allows me to be a jack of many trades and look like a master of all!</p>
</div>
<section id="node-comment">
<article data-comment-user-id="0" id="comment-652" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/0">
</article>
<mark class="hidden" data-comment-timestamp="1730128521"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/652#comment-652" class="permalink" rel="bookmark" hreflang="en">Only problem with AI …</a></h3>
<div class="single-comment-meta">
<span><a rel="nofollow" href="https://blog.dummzeuch.de" lang="" typeof="schema:Person" property="schema:name" datatype="">Thomas Müller (not verified)</a> Mon, 10/28/2024 - 00:49</span>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>Only problem with AI (including Claude) is that it provides plausible sounding answers that are wrong or misleading. A few days ago I asked Claude for a Delphi implementation of Timsort. What I got looked plausible but it simply didn't work. It sorted about 10 entries correctly and but broke for more. The same happened with ChatGPT on other occasions with completely other topics. That makes me wary of anything those hyped "helpers" suggest.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=652&1=default&2=en&3=" token="u3qiJnLeOMSK1GsJASIpSL3xMiLfYypfM-CIgq7GY1M"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
<div class="indented">
<article data-comment-user-id="1" id="comment-653" class="js-comment single-comment">
<header class="comment-user-picture">
<article typeof="schema:Person" about="/user/1">
</article>
<mark class="hidden" data-comment-timestamp="1730129778"></mark>
</header>
<div class="single-comment-content-body">
<h3 class="single-comment-title"><a href="/comment/653#comment-653" class="permalink" rel="bookmark" hreflang="en">Being wary is good.</a></h3>
<div class="single-comment-meta">
<span><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">david</span> Mon, 10/28/2024 - 08:36</span>
<p class="visually-hidden">In reply to <a href="/comment/652#comment-652" class="permalink" rel="bookmark" hreflang="en">Only problem with AI …</a> by <a rel="nofollow" href="https://blog.dummzeuch.de" lang="" typeof="schema:Person" property="schema:name" datatype="">Thomas Müller (not verified)</a></p>
</div> <!-- /.single-comment-meta -->
<div class="single-comment-content">
<div class="field field--name-comment-body field--type-text-long field--label-hidden field-item"><p>I do not promote the idea that one should take a suggested AI answer and use it as a solution without verification. What I have found is that it either jump-starts ideas and thinking about how to solve a problem or, as in the case mentioned in this blog, reminds me of a tool I can use. I checked the documentation, verified the syntax, ran the command, and verified with the user the problem was fixed.</p>
<p>AI is not doing my work for me, it's not going to replace my job as a professional programmer that understands user needs or application context or can test to see if a suggestion is a proper solution. It is, though, becoming a helpful research assistant.</p></div>
<drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=653&1=default&2=en&3=" token="jkanhKOBmqyHflyFPm0tfebyZaxshOiyHfOOuOUa8sk"></drupal-render-placeholder>
</div>
</div> <!-- /.single-comment-content -->
</article>
</div>
<div class="comment-form-wrap">
<h2 class="add-comment-title"><i class="icon-add_comment theme-color"></i> Add new comment</h2>
<drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&1=190&2=comment_node_blog&3=comment_node_blog" token="e0jOZ2TNGIuKg0xziToRQOScMUXJ-_sb9vSuj4pviVI"></drupal-render-placeholder>
</div> <!--/.comment-form -->
</section>
Sun, 27 Oct 2024 23:20:05 +0000david190 at http://corneliusconcepts.tech