<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Object-Pascal on TurboVision</title>
    <link>https://turbovision.in6-addr.net/tags/object-pascal/</link>
    <description>Recent content in Object-Pascal on TurboVision</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Tue, 21 Apr 2026 14:06:12 +0000</lastBuildDate>
    <atom:link href="https://turbovision.in6-addr.net/tags/object-pascal/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    
    <item>
      <title>Turbo Pascal Toolchain, Part 6: Object Pascal, TPW, and the Windows Transition</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-6-object-pascal-tpwin-and-the-windows-transition/</link>
      <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Fri, 13 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-6-object-pascal-tpwin-and-the-windows-transition/</guid>
      <description>&lt;p&gt;Parts 1–5 mapped the DOS-era toolchain: workflow, artifacts, overlays, BGI, and
the compiler/linker boundary from TP6 to TP7. This part crosses the platform
divide. Object Pascal extensions, Turbo Pascal for Windows (TPW), and the move
to message-driven GUIs forced a different kind of toolchain thinking. Same
language family, new mental model.&lt;/p&gt;
&lt;p&gt;This article traces that transition from a practitioner&amp;rsquo;s perspective: what
stayed familiar, what broke, and what had to be relearned. We cover the
historical milestones (TP 5.5 OOP, TPW 1.0, TPW 1.5, BP7), the technical
culprits that bit migrating teams, debugging and build/deploy workflow
differences, and the mental shift from sequential to event-driven execution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version timeline (conservative):&lt;/strong&gt; TP 5.5 (1989) introduced Object Pascal.
TPW 1.0 appeared in the Windows 3.0 era (c. 1991). Borland Pascal 7 (1992)
offered unified DOS and Windows tooling including DLL support. TPW 1.5 followed
TP7 (c. 1993). OWL matured alongside these releases. Exact dates for some
variants vary by region and packaging; the sequence is well established. The
transition spanned roughly four years; many teams maintained both DOS and
Windows targets during that period.&lt;/p&gt;
&lt;h2 id=&#34;structure-map-balanced-chapter-plan&#34;&gt;Structure map (balanced chapter plan)&lt;/h2&gt;
&lt;p&gt;Before drilling into details, this article follows a fixed ten-chapter plan so
the narrative stays balanced rather than front-loaded:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Object Pascal in TP 5.5&lt;/li&gt;
&lt;li&gt;TPW 1.0 and first Windows workflow shock&lt;/li&gt;
&lt;li&gt;TPW 1.5 in the post-TP7 landscape&lt;/li&gt;
&lt;li&gt;BP7 as dual-target toolchain&lt;/li&gt;
&lt;li&gt;OWL and message-driven architecture&lt;/li&gt;
&lt;li&gt;migration culprits and pitfalls&lt;/li&gt;
&lt;li&gt;debugging model changes (DOS vs Windows)&lt;/li&gt;
&lt;li&gt;build/deploy pipeline changes&lt;/li&gt;
&lt;li&gt;team workflow and review-model changes&lt;/li&gt;
&lt;li&gt;synthesis and transfer lessons&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each chapter carries similar depth: technical mechanism, failure mode, and
practical operator/developer workflow.&lt;/p&gt;
&lt;h2 id=&#34;object-pascal-arrives-tp-55-and-the-oop-extensions&#34;&gt;Object Pascal arrives: TP 5.5 and the OOP extensions&lt;/h2&gt;
&lt;p&gt;Turbo Pascal 5.5, released in 1989, introduced Object Pascal: the &lt;code&gt;object&lt;/code&gt; type
with inheritance, virtual methods, and constructors/destructors. The additions
were substantial for the language, but the toolchain remained essentially the
same. Compile, link, run. &lt;code&gt;.TPU&lt;/code&gt; units still carried compiled code; the linker
still produced &lt;code&gt;.EXE&lt;/code&gt;. What changed was what you expressed in those units and
how you structured larger programs.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;object&lt;/code&gt; keyword (distinct from the later &lt;code&gt;class&lt;/code&gt; keyword in Delphi) defined
a type with a hidden pointer to its virtual method table (VMT). Inheritance was
single; you could not inherit from multiple base objects. Virtual methods
required the &lt;code&gt;virtual&lt;/code&gt; directive and had to be overridden with the same
signature. The compiler emitted the VMT layout; if you got the inheritance
hierarchy wrong, the wrong method could be invoked at runtime—a form of bug
that procedural Pascal had never had.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit Shapes;

interface

type
  TShape = object
    X, Y: Integer;
    procedure Move(Dx, Dy: Integer);
    procedure Draw; virtual;
    constructor Init(AX, AY: Integer);
    destructor Done; virtual;
  end;

  TCircle = object(TShape)
    Radius: Integer;
    procedure Draw; virtual;
    constructor Init(AX, AY, ARadius: Integer);
  end;

implementation

constructor TShape.Init(AX, AY: Integer);
begin
  X := AX;
  Y := AY;
end;

destructor TShape.Done;
begin
  { cleanup }
end;

procedure TShape.Move(Dx, Dy: Integer);
begin
  Inc(X, Dx);
  Inc(Y, Dy);
end;

procedure TShape.Draw;
begin
  { base: no-op or default behavior }
end;

constructor TCircle.Init(AX, AY, ARadius: Integer);
begin
  TShape.Init(AX, AY);
  Radius := ARadius;
end;

procedure TCircle.Draw;
begin
  { draw circle at X,Y with Radius }
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For DOS projects, this was still a single-threaded, linear-control-flow world.
The object model improved structure and reuse; it did not yet change the
execution paradigm. Overlays, BGI, and conventional memory limits applied
unchanged. Teams adopting Object Pascal in the late 1980s learned inheritance
and polymorphism while keeping familiar toolchain habits.&lt;/p&gt;
&lt;p&gt;Constructor and destructor discipline mattered. In the early &lt;code&gt;object&lt;/code&gt; model
(pre-class syntax), you called &lt;code&gt;Init&lt;/code&gt; explicitly and &lt;code&gt;Done&lt;/code&gt; before disposal.
Forgetting &lt;code&gt;Done&lt;/code&gt; on objects that held resources (handles, memory) leaked. The
toolchain did not enforce this; it was a coding discipline. Virtual method
tables added a small runtime cost and one more thing to get wrong when mixing
object types—passing a &lt;code&gt;TShape&lt;/code&gt; where a &lt;code&gt;TCircle&lt;/code&gt; was expected could produce
subtle bugs if the receiver assumed the concrete type.&lt;/p&gt;
&lt;p&gt;The important point for the Windows transition: Object Pascal gave developers
the vocabulary (inheritance, virtual dispatch, encapsulation) that OWL and later
frameworks would use. Learning OOP in DOS was preparation for OWL&amp;rsquo;s
message-handler hierarchy.&lt;/p&gt;
&lt;p&gt;Toolchain impact was minimal. TP 5.5 still produced &lt;code&gt;.TPU&lt;/code&gt; units; the compiler
emitted VMT layout for object types; the linker resolved virtual calls at
link time. Debugging object hierarchies required understanding the VMT
structure, but Turbo Debugger could display object instances and their
fields. Migration from procedural to object-based code was incremental: one
unit at a time, starting with leaf modules that had no dependencies. A common
path: introduce a single object type to encapsulate a record and its
operations, compile and test, then add inheritance where it simplified
structure. Big-bang rewrites to &amp;ldquo;full OOP&amp;rdquo; were rare and risky; most teams
evolved their codebases gradually.&lt;/p&gt;
&lt;h2 id=&#34;turbo-pascal-for-windows-10-the-first-wave&#34;&gt;Turbo Pascal for Windows 1.0: the first wave&lt;/h2&gt;
&lt;p&gt;Turbo Pascal for Windows 1.0 arrived in the Windows 3.0 era, commonly cited as
around 1991. The toolchain surface looked familiar: blue IDE, integrated
compiler, linker. Underneath, the target was completely different. Instead of
DOS &lt;code&gt;.EXE&lt;/code&gt; and real-mode segments, you produced Windows &lt;code&gt;.EXE&lt;/code&gt; binaries that
linked against the Windows API, expected a GUI entry point (&lt;code&gt;WinMain&lt;/code&gt;), and
ran inside a message loop.&lt;/p&gt;
&lt;p&gt;First-time TPW users discovered that a &amp;ldquo;Pascal program&amp;rdquo; was no longer a
straight-line script. The main block ran once to register the window class,
create the main window, and enter &lt;code&gt;GetMessage&lt;/code&gt;/&lt;code&gt;DispatchMessage&lt;/code&gt;. After that,
everything happened inside the window procedure (&lt;code&gt;WndProc&lt;/code&gt;) in response to
messages. A typical beginner error: putting &amp;ldquo;real&amp;rdquo; logic in the main block,
wondering why it never ran, and only later realizing the block had already
exited into the message loop. Another: assuming that &lt;code&gt;WndProc&lt;/code&gt; would be
called once per &amp;ldquo;event.&amp;rdquo; In fact, Windows sends many messages—&lt;code&gt;WM_CREATE&lt;/code&gt;,
&lt;code&gt;WM_SIZE&lt;/code&gt;, &lt;code&gt;WM_PAINT&lt;/code&gt;, &lt;code&gt;WM_COMMAND&lt;/code&gt;, and dozens more—and the order and
timing depend on user actions and system behaviour. Learning which messages
mattered for a given task was part of the ramp-up.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program HelloWin;

uses
  WinTypes, WinProcs;

const
  IDC_BUTTON = 100;

function WndProc(Window: HWnd; Message, WParam: Word; LParam: LongInt): LongInt;
  far;
begin
  case Message of
    wm_Command:
      if WParam = IDC_BUTTON then
        MessageBox(Window, &amp;#39;Hello from TPW&amp;#39;, &amp;#39;TPW&amp;#39;, mb_Ok);
    wm_Destroy:
      PostQuitMessage(0);
    else
      WndProc := DefWindowProc(Window, Message, WParam, LParam);
      Exit;
  end;
  WndProc := 0;
end;

var
  Msg: TMsg;
  WndClass: TWndClass;
  hWnd: HWnd;

begin
  WndClass.style := 0;
  WndClass.lpfnWndProc := @WndProc;
  WndClass.cbClsExtra := 0;
  WndClass.cbWndExtra := 0;
  WndClass.hInstance := HInstance;
  WndClass.hIcon := LoadIcon(0, idi_Application);
  WndClass.hCursor := LoadCursor(0, idc_Arrow);
  WndClass.hbrBackground := GetStockObject(white_Brush);
  WndClass.lpszMenuName := nil;
  WndClass.lpszClassName := &amp;#39;HelloWin&amp;#39;;

  RegisterClass(WndClass);
  hWnd := CreateWindow(&amp;#39;HelloWin&amp;#39;, &amp;#39;TPW Hello&amp;#39;, ws_OverlappedWindow,
    cw_UseDefault, 0, cw_UseDefault, 0, 0, 0, HInstance, nil);
  ShowWindow(hWnd, sw_ShowNormal);
  UpdateWindow(hWnd);

  while GetMessage(Msg, 0, 0, 0) do
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The shift was conceptual: instead of &amp;ldquo;run from top to bottom,&amp;rdquo; you &amp;ldquo;register a
window class, create a window, then sit in a message loop.&amp;rdquo; Event handling was
reactive. The toolchain still produced &lt;code&gt;.EXE&lt;/code&gt;, but the runtime contract was
Windows API calls, far procs, and &lt;code&gt;GetMessage&lt;/code&gt;/&lt;code&gt;DispatchMessage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;TPW 1.0 shipped with &lt;code&gt;WinTypes&lt;/code&gt; and &lt;code&gt;WinProcs&lt;/code&gt; units (API bindings) and
optionally &lt;code&gt;WinCrt&lt;/code&gt; for console-style apps. The IDE looked like the DOS Turbo
Pascal IDE but targeted a different runtime. Keyboard shortcuts and menu
structure were familiar, which eased the transition. The debugger, however,
had to handle a different execution model: breakpoints in message handlers
fired when messages arrived, not when you single-stepped through a linear
flow. Setting a breakpoint in &lt;code&gt;WndProc&lt;/code&gt; and running would eventually stop
there—but only when a message was dispatched to that window. First-time TPW users often hit:
wrong library linking (mixing DOS and Windows units), missing &lt;code&gt;far&lt;/code&gt; on
&lt;code&gt;WndProc&lt;/code&gt;, and confusion about when their code actually ran—the main block
sets up and enters the loop; the rest happens inside &lt;code&gt;WndProc&lt;/code&gt; when messages
arrive. That inversion was the core mental break.&lt;/p&gt;
&lt;p&gt;Linker differences mattered. TPW produced Windows executables with a different
header format, different segment layout, and different startup code. You could
not link a DOS object file into a Windows executable or vice versa. Mixed
projects—e.g. a shared algorithm library—had to compile the same source
twice, once for each target, with target-specific &lt;code&gt;uses&lt;/code&gt; and possibly
&lt;code&gt;{$IFDEF}&lt;/code&gt; guards. The idea of &amp;ldquo;one binary runs everywhere&amp;rdquo; did not exist;
you had DOS binaries and Windows binaries.&lt;/p&gt;
&lt;p&gt;Understanding the message loop was essential. &lt;code&gt;GetMessage&lt;/code&gt; blocks until a
message is available; &lt;code&gt;TranslateMessage&lt;/code&gt; converts keystrokes to &lt;code&gt;WM_CHAR&lt;/code&gt; when
needed; &lt;code&gt;DispatchMessage&lt;/code&gt; invokes the window procedure for the target window.
Every GUI action in a Windows app flows through this pipeline. A handler that
did too much work (e.g. a long computation) would block the loop and freeze
the UI. DOS programs could &lt;code&gt;ReadKey&lt;/code&gt; and wait indefinitely; Windows programs
had to return from handlers quickly and defer heavy work (e.g. via timers or
background processing) to avoid stalling the whole application. Developers
coming from DOS often wrote handlers that performed synchronous file I/O or
lengthy calculations, then wondered why the window would not repaint or
respond to input until the operation finished. The fix was to break work
into smaller chunks or use &lt;code&gt;PeekMessage&lt;/code&gt;-based cooperative multitasking—a
technique that required unlearning the &amp;ldquo;run until done&amp;rdquo; habit.&lt;/p&gt;
&lt;h2 id=&#34;tpw-15-and-the-post-tp7-landscape&#34;&gt;TPW 1.5 and the post-TP7 landscape&lt;/h2&gt;
&lt;p&gt;TPW 1.5 followed TP7 and appeared in the early 1990s (often cited around 1993).
It brought the TP7-era language and tooling to the Windows target. Better
integration with Windows APIs, improved resource tooling, and alignment with the
Borland Pascal 7 family. By this point, DOS and Windows were parallel targets
within the same product family, not separate products with different pedigrees.&lt;/p&gt;
&lt;p&gt;Build workflows diversified. A team might maintain both a DOS and a Windows
configuration: different compiler switches, different libraries, different
entry points. Shared units had to stay abstract enough to compile for both.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Conditional compilation for dual-target units }
unit SharedCore;

interface

procedure DoWork(Data: Pointer);

implementation

{$IFDEF MSWINDOWS}
uses WinTypes, WinProcs;
{$ENDIF}
{$IFDEF MSDOS}
uses Dos;
{$ENDIF}

procedure DoWork(Data: Pointer);
begin
  {$IFDEF MSWINDOWS}
  { Windows-specific implementation }
  {$ENDIF}
  {$IFDEF MSDOS}
  { DOS-specific implementation }
  {$ENDIF}
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;{$IFDEF}&lt;/code&gt; pattern became standard for code shared across targets. Not all
logic could be shared; APIs differed. But data structures, algorithms, and
business rules could live in common units with thin platform-specific wrappers.
Teams learned to minimize &lt;code&gt;{$IFDEF}&lt;/code&gt; surface and push platform branches to
dedicated units.&lt;/p&gt;
&lt;p&gt;A common layout: a &lt;code&gt;Core&lt;/code&gt; unit with pure logic (no &lt;code&gt;uses&lt;/code&gt; of platform units),
a &lt;code&gt;CoreDOS&lt;/code&gt; unit that implemented &lt;code&gt;Core&lt;/code&gt; for DOS (overlays, BGI, &lt;code&gt;Dos&lt;/code&gt; unit),
and a &lt;code&gt;CoreWin&lt;/code&gt; unit that implemented &lt;code&gt;Core&lt;/code&gt; for Windows (handles, &lt;code&gt;WinProcs&lt;/code&gt;).
The program or a top-level unit chose which implementation to use. This kept
the conditional compilation at a few strategic points rather than scattered
throughout.&lt;/p&gt;
&lt;p&gt;TPW 1.5 also improved the resource workflow. Earlier TPW had resource support,
but the integration was rougher. By 1.5, the path from dialog design to linked
&lt;code&gt;.EXE&lt;/code&gt; was more streamlined, and teams doing serious Windows development could
rely on it.&lt;/p&gt;
&lt;p&gt;A practical consideration: machine requirements. DOS Turbo Pascal ran on an
8088 with 256 KB of RAM. TPW and Windows 3.x demanded more—typically a 286 or
386, 1 MB or more of RAM, and a graphics display. Teams developing on
higher-end machines had to remember that target users might have minimal
configurations. Testing on a &amp;ldquo;cramped&amp;rdquo; setup (e.g. 1 MB RAM, 640×480) caught
memory pressure and layout bugs that did not appear on development hardware.&lt;/p&gt;
&lt;h2 id=&#34;bp7-unified-dos-and-windows-toolchain&#34;&gt;BP7: unified DOS and Windows toolchain&lt;/h2&gt;
&lt;p&gt;Borland Pascal 7, released in 1992, provided a single box with DOS and Windows
support. You could build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOS executables (with overlays, EMS, real-mode semantics)&lt;/li&gt;
&lt;li&gt;Windows executables&lt;/li&gt;
&lt;li&gt;Windows DLLs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DLL building introduced a new artifact type and a new linkage model.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;library MyLib;

uses
  WinTypes, WinProcs;

exports
  MyExportProc index 1,
  MyExportFunc index 2;

procedure MyExportProc(P: PChar); far;
begin
  { DLL-exported procedure }
end;

function MyExportFunc(I: Integer): Integer; far;
begin
  MyExportFunc := I * 2;
end;

begin
  { DLL entry/exit handling if needed }
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The toolchain produced &lt;code&gt;.DLL&lt;/code&gt; instead of (or in addition to) &lt;code&gt;.EXE&lt;/code&gt;. Callers
used &lt;code&gt;LoadLibrary&lt;/code&gt; and &lt;code&gt;GetProcAddress&lt;/code&gt;. Version coupling and calling
conventions mattered more: a Pascal DLL had to match what the caller expected.
Teams learned to isolate DLL interfaces and treat them as stable ABI boundaries.&lt;/p&gt;
&lt;p&gt;DLL entry and exit ran at load/unload. If a DLL&amp;rsquo;s initialization touched
other DLLs or global state, load order could cause subtle failures. Export by
name vs. by ordinal had tradeoffs: ordinals were smaller and faster to resolve
but fragile if the export table changed. Many teams standardized on name-based
exports for maintainability and reserved ordinals for performance-critical
paths. The &lt;code&gt;exports&lt;/code&gt; section in the &lt;code&gt;library&lt;/code&gt; block was the contract; changing
it broke any caller that relied on it. Adding new exports was usually safe;
removing or reordering required coordinated updates to all clients. Teams
that treated the DLL interface as a stable API and versioned it explicitly
(including in documentation) had fewer integration surprises.&lt;/p&gt;
&lt;p&gt;Calling a Pascal DLL from C or another language required matching conventions:
pascal vs. cdecl, near vs. far, and structure layout. Teams building mixed-
language systems documented the ABI explicitly. A small test program that
called each exported function and verified return values caught many
integration bugs before they reached production.&lt;/p&gt;
&lt;p&gt;BP7&amp;rsquo;s value was consolidation: one purchase, one documentation set, one
support channel for both DOS and Windows. Teams could prototype on DOS (faster
iteration, simpler debugging) and port to Windows when the design stabilised,
or maintain both targets from a shared codebase from the start.&lt;/p&gt;
&lt;p&gt;The DLL workflow itself took time to internalise. A &lt;code&gt;library&lt;/code&gt; program had no
main loop; it exported entry points. Callers loaded it, resolved exports, and
called. The DLL&amp;rsquo;s initialization block ran at load; its finalization (if any)
ran at unload. Thread safety was not a primary concern in 16-bit Windows, but
DLL global state was shared across all callers. A bug in one executable&amp;rsquo;s use
of a DLL could corrupt state for another. Documentation and code review had to
cover &amp;ldquo;who loads this DLL, when, and what do they assume about its state?&amp;rdquo;
DLLs also changed the testing matrix: a fix in a shared DLL required
re-testing every application that used it. Versioning the DLL (e.g. embedding
a version resource) and checking it at load time caught many &amp;ldquo;wrong DLL&amp;rdquo;
deployment bugs before they manifested as mysterious crashes.&lt;/p&gt;
&lt;p&gt;Importing a DLL from Pascal required matching the export signature exactly.
A common pattern:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ In unit that uses the DLL }
procedure MyImportProc(P: PChar); far; external &amp;#39;MYLIB&amp;#39; index 1;
function MyImportFunc(I: Integer): Integer; far; external &amp;#39;MYLIB&amp;#39; index 2;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the DLL used &lt;code&gt;pascal&lt;/code&gt; convention (Borland default) and the caller did too,
calls worked. Mixing &lt;code&gt;cdecl&lt;/code&gt; and &lt;code&gt;pascal&lt;/code&gt; caused stack corruption. Teams
building reusable DLLs often documented the calling convention in the header
or in a separate ABI document.&lt;/p&gt;
&lt;h2 id=&#34;owl-and-message-driven-architecture&#34;&gt;OWL and message-driven architecture&lt;/h2&gt;
&lt;p&gt;Object Windows Library (OWL) and similar frameworks wrapped the raw Windows API
in an object-oriented, message-handler style. Instead of a giant &lt;code&gt;case&lt;/code&gt;
statement in a single &lt;code&gt;WndProc&lt;/code&gt;, you subclassed window types and overrode
message handlers.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit MyWindow;

interface

uses
  Objects, WinTypes, WinProcs, OWindows;

type
  PMyWindow = ^TMyWindow;
  TMyWindow = object(TWindow)
    procedure WMCommand(var Msg: TMessage); virtual wm_First + wm_Command;
    procedure WMPaint(var Msg: TMessage); virtual wm_First + wm_Paint;
  end;

implementation

procedure TMyWindow.WMCommand(var Msg: TMessage);
begin
  if Msg.WParam = 100 then
    MessageBox(HWindow, &amp;#39;Button clicked&amp;#39;, &amp;#39;OWL&amp;#39;, mb_Ok)
  else
    inherited WMCommand(Msg);
end;

procedure TMyWindow.WMPaint(var Msg: TMessage);
var
  PS: TPaintStruct;
  DC: HDC;
begin
  DC := BeginPaint(HWindow, PS);
  { draw using DC }
  EndPaint(HWindow, PS);
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The pattern: each message maps to a virtual method; &lt;code&gt;inherited&lt;/code&gt; propagates to
the default handler. Toolchain-wise, you still compiled units and linked, but
the design idiom was &amp;ldquo;object per window, method per message.&amp;rdquo; This influenced
how teams structured code and how they debugged: failures showed up as wrong
message routing or missing overrides.&lt;/p&gt;
&lt;p&gt;OWL abstracted the raw &lt;code&gt;RegisterClass&lt;/code&gt;/&lt;code&gt;CreateWindow&lt;/code&gt;/message-loop boilerplate.
You derived from &lt;code&gt;TApplication&lt;/code&gt; and &lt;code&gt;TWindow&lt;/code&gt;, filled in handlers, and the
framework dealt with registration and dispatch. The tradeoff: learning OWL&amp;rsquo;s
object graph and lifecycle. Windows created by OWL were owned by the framework;
manual &lt;code&gt;CreateWindow&lt;/code&gt; calls mixed with OWL could bypass that ownership and cause
duplicate destruction or leaked handles. Teams that went &amp;ldquo;all OWL&amp;rdquo; had fewer
ownership bugs than those that mixed raw API and OWL freely.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;virtual wm_First + wm_Command&lt;/code&gt; syntax mapped a Windows message ID to a
method. When a message arrived, OWL&amp;rsquo;s dispatch logic looked up the method and
called it. If you did not override a message, the base class handled it (or
passed to &lt;code&gt;DefWindowProc&lt;/code&gt;). This was a clean separation of concerns: each
window class handled only the messages it cared about.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ OWL: creating a custom control by inheritance }
type
  PMyEdit = ^TMyEdit;
  TMyEdit = object(TEdit)
    procedure WMChar(var Msg: TMessage); virtual wm_First + wm_Char;
  end;

procedure TMyEdit.WMChar(var Msg: TMessage);
begin
  { Filter or transform input before default handling }
  inherited WMChar(Msg);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This pattern—override, do something, call inherited—became the standard for
extending OWL controls. The toolchain compiled and linked the same way; the
design vocabulary had expanded.&lt;/p&gt;
&lt;p&gt;Choosing between raw API and OWL was a real decision. Raw API gave full
control and smaller binaries but required more boilerplate and discipline.
OWL added framework overhead but let teams ship Windows apps faster. Many
TPW projects started with raw API for learning, then switched to OWL once
the team understood the message model. Hybrid approaches existed but demanded
careful ownership rules for window handles and resources.&lt;/p&gt;
&lt;p&gt;OWL also provided standard dialogs, common controls wrappers, and application
lifecycle management. Reinventing these with raw API was possible but time-
consuming. Teams that adopted OWL early often had a working prototype in days
instead of weeks. The tradeoff was dependency on Borland&amp;rsquo;s framework and its
design decisions; customising behaviour sometimes required diving into OWL
source or working around framework limitations. For teams building multiple
Windows applications, OWL&amp;rsquo;s consistency across projects was valuable: once
you learned the patterns, new apps came together faster. The investment in
learning the framework paid off over several products.&lt;/p&gt;
&lt;h2 id=&#34;technical-culprits-and-pitfalls&#34;&gt;Technical culprits and pitfalls&lt;/h2&gt;
&lt;p&gt;Several failure modes were common when moving from DOS to Windows. Experienced
DOS developers often hit these first; the habits that worked in real mode
backfired in Windows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Far-call discipline.&lt;/strong&gt; Windows callback procs (&lt;code&gt;WndProc&lt;/code&gt;, dialogs, hooks) must
be far. The Windows kernel and USER module invoke your code through function
pointers; in the segmented 16-bit model, a near call to a callback caused
immediate corruption when the system tried to return. Missing &lt;code&gt;far&lt;/code&gt; or wrong
declaration led to crashes that were hard to reproduce—sometimes only when a
particular code path was taken. The compiler did not always catch it; runtime
did, and not always with a clear message.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource coupling.&lt;/strong&gt; Windows apps depend on &lt;code&gt;.RC&lt;/code&gt; resources (dialogs,
menus, icons). Wrong paths, missing resources, or mismatched IDs produced
obscure startup failures. The linker or resource compiler had to be in the loop,
and the resulting &lt;code&gt;.RES&lt;/code&gt; had to link into the &lt;code&gt;.EXE&lt;/code&gt;. A dialog defined in &lt;code&gt;.RC&lt;/code&gt;
with control ID 100 had to match the &lt;code&gt;wm_Command&lt;/code&gt; handler that checked for 100.
Typos or reuse of IDs across dialogs caused wrong controls to be identified.
Teams learned to centralize ID constants in a shared include or unit. Some
teams used a naming scheme (e.g. &lt;code&gt;IDC_BUTTON_SAVE&lt;/code&gt;, &lt;code&gt;IDC_EDIT_NAME&lt;/code&gt;) to make
the link between resource and handler obvious during code review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Segment and memory model.&lt;/strong&gt; Windows 3.x used segmented memory. Large
allocations, wrong segment assumptions, or stack overflow in message handlers
could corrupt the heap or cause intermittent faults. DOS habits (assume
sequential execution, small stack) did not translate. In DOS, you often knew
exactly when a procedure returned; in Windows, a message handler could call
&lt;code&gt;SendMessage&lt;/code&gt; and re-enter the same or another handler before returning.
Recursive message handling required care with stack depth and static state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;String interop.&lt;/strong&gt; Pascal &lt;code&gt;String[N]&lt;/code&gt; vs. C null-terminated. Windows API
expects &lt;code&gt;PChar&lt;/code&gt; and length conventions. Conversion bugs caused truncation,
buffer overrun, or wrong display. Teams needed explicit conversion layers and
disciplined use of buffers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DLL load order and initialization.&lt;/strong&gt; DLLs had init/exit sequences. Circular
dependencies or incorrect load order led to startup hangs or access violations.
Build order and &lt;code&gt;uses&lt;/code&gt; discipline mattered.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;String conversion and buffer safety.&lt;/strong&gt; Windows API calls often expect
null-terminated &lt;code&gt;PChar&lt;/code&gt;. Pascal &lt;code&gt;String&lt;/code&gt; is length-prefixed. Passing a raw
&lt;code&gt;String&lt;/code&gt; variable where &lt;code&gt;PChar&lt;/code&gt; was expected could work by accident (many
implementations had a trailing zero) but was undefined. Correct pattern:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Safe Pascal-to-Windows string passing }
procedure ShowText(const S: String);
var
  Buf: array[0..255] of Char;
  I: Integer;
begin
  for I := 0 to Length(S) - 1 do
    Buf[I] := S[I + 1];  { Pascal 1-based indexing }
  Buf[Length(S)] := #0;
  MessageBox(0, Buf, &amp;#39;Title&amp;#39;, mb_Ok);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Teams built small conversion units and used them consistently. Ad-hoc &lt;code&gt;StrPCopy&lt;/code&gt;
calls scattered across codebases were a maintenance hazard. A &lt;code&gt;StrUtils&lt;/code&gt; or
&lt;code&gt;WinStrings&lt;/code&gt; unit with &lt;code&gt;PascalToPChar&lt;/code&gt;, &lt;code&gt;PCharToPascal&lt;/code&gt;, and perhaps
&lt;code&gt;PCharBuf&lt;/code&gt; for temporary buffers reduced copy-paste errors and gave a single
place to fix bugs when a new Windows version changed length semantics.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Common mistake: forgetting far on Windows callbacks }
procedure BadProc(Window: HWnd; Msg: Word; W, L: LongInt);  { WRONG }
procedure GoodProc(Window: HWnd; Msg: Word; W, L: LongInt); far;  { CORRECT }&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;debugging-workflows-dos-vs-windows&#34;&gt;Debugging workflows: DOS vs Windows&lt;/h2&gt;
&lt;p&gt;DOS debugging was relatively direct. Single process, linear execution, predictable
crash locations. Turbo Debugger could single-step, set breakpoints, inspect
memory. Overlay and BGI issues were usually reproducible. If a crash happened
at a fixed address, you set a breakpoint there, ran again, and examined the
call stack. Deterministic replay was the default.&lt;/p&gt;
&lt;p&gt;Windows debugging was harder. Message-driven execution meant control flow jumped
between handlers. A bug might only appear when a specific message arrived in a
specific order. Reproducing required driving the UI in a particular way.
Crashes could occur in system code invoked via callback; the immediate cause
might be bad parameters passed from your handler. Null pointer dereferences,
wrong handle usage, and stack corruption in message handlers produced
intermittent failures that did not correlate with &amp;ldquo;run it again.&amp;rdquo;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Diagnostic: log message flow to understand ordering }
procedure TMyWindow.DefaultHandler(var Msg: TMessage);
begin
  WriteLn(DebugFile, &amp;#39;Msg=&amp;#39;, Msg.Msg, &amp;#39; W=&amp;#39;, Msg.WParam, &amp;#39; L=&amp;#39;, Msg.LParam);
  inherited DefaultHandler(Msg);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Practitioners used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OutputDebugString and a monitor (e.g. Turbo Debugger for Windows or
third-party tools) to capture log output&lt;/li&gt;
&lt;li&gt;Conditional breakpoints in the debugger on message IDs (e.g. break when
&lt;code&gt;Msg.Msg = wm_Paint&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Small harness programs that sent specific messages via &lt;code&gt;SendMessage&lt;/code&gt; to
isolate behavior without manual UI interaction&lt;/li&gt;
&lt;li&gt;Map files to correlate addresses with symbols when analyzing postmortem dumps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mental shift: from &amp;ldquo;re-run until it crashes&amp;rdquo; to &amp;ldquo;instrument and trace
message flow.&amp;rdquo; Debugging became hypothesis-driven: which message, which
window, which order?&lt;/p&gt;
&lt;p&gt;Another technique: build a minimal reproduction. If the bug appeared when
clicking a specific button after resizing the window, create a tiny app with
only that button and that resize logic. Isolating the failure often revealed
that the cause was not where intuition suggested—e.g. a &lt;code&gt;WM_PAINT&lt;/code&gt; handler
that assumed state set up in &lt;code&gt;WM_SIZE&lt;/code&gt;, but &lt;code&gt;WM_PAINT&lt;/code&gt; could arrive before
&lt;code&gt;WM_SIZE&lt;/code&gt; in certain scenarios. Understanding Windows&amp;rsquo; message ordering and
reentrancy was as important as knowing the API. A handler that called
&lt;code&gt;SendMessage&lt;/code&gt; to a child window could find itself re-entered if the child&amp;rsquo;s
handler did something that triggered another message to the parent. Careful
design avoided such cycles; when they occurred, stack overflow or corrupted
state often resulted.&lt;/p&gt;
&lt;h2 id=&#34;build-and-deploy-dos-vs-windows&#34;&gt;Build and deploy: DOS vs Windows&lt;/h2&gt;
&lt;p&gt;DOS deployment was simple: &lt;code&gt;.EXE&lt;/code&gt;, optionally &lt;code&gt;.OVR&lt;/code&gt;, and &lt;code&gt;.BGI&lt;/code&gt;/&lt;code&gt;.CHR&lt;/code&gt; in a
known directory. Batch files or simple install scripts sufficed. A typical
release package: one folder, a few files, run the EXE. Path assumptions (e.g.
&lt;code&gt;.\BGI&lt;/code&gt; for drivers) had to be correct, but the surface was small. Floppy
distribution was common: a single disk for the program, optionally a second
for BGI drivers or overlay files. Users understood &amp;ldquo;copy to C:\MYAPP and run.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Windows deployment added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple DLLs (Windows system DLLs plus any you shipped)&lt;/li&gt;
&lt;li&gt;Resource files (icons, dialogs) embedded or alongside&lt;/li&gt;
&lt;li&gt;INI files or registry for configuration&lt;/li&gt;
&lt;li&gt;Different machine profiles (video drivers, memory)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The resource pipeline was new. You authored &lt;code&gt;.RC&lt;/code&gt; files, compiled them with
&lt;code&gt;BRC.EXE&lt;/code&gt; (Borland Resource Compiler) to &lt;code&gt;.RES&lt;/code&gt;, and linked the &lt;code&gt;.RES&lt;/code&gt; into
the &lt;code&gt;.EXE&lt;/code&gt;. Forgetting the resource step produced a binary that ran but showed
no icon, wrong menu, or broken dialogs. Dialog editor output and hand-written
&lt;code&gt;.RC&lt;/code&gt; had to stay in sync; ID collisions caused mysterious behavior. A small convention helped: define
all resource IDs in a single &lt;code&gt;$I&lt;/code&gt;-included file or a dedicated unit, and
reference them from both &lt;code&gt;.RC&lt;/code&gt; and Pascal. Changing an ID in one place
without the other was a frequent source of &amp;ldquo;the button does nothing&amp;rdquo; bugs
that took hours to track down.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bat&#34; data-lang=&#34;bat&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;REM DOS build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpc -B main.pas
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;copy&lt;/span&gt; main.exe dist\
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;copy&lt;/span&gt; *.ovr dist\ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;copy&lt;/span&gt; bgi\*.bgi dist\bgi\
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;REM Windows build (conceptual)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpw main.pas
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brc main.res main.exe
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;copy&lt;/span&gt; main.exe dist\
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;copy&lt;/span&gt; mylib.dll dist\ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Build scripts had to branch by target. Release builds often required separate
configurations for DOS and Windows, with different linker options and runtime
selection. Teams documented &amp;ldquo;DOS build checklist&amp;rdquo; vs. &amp;ldquo;Windows build checklist&amp;rdquo;
and treated them as separate pipelines. A dual-target product meant two
release builds, two test passes, and two support matrices (e.g. &amp;ldquo;runs on
DOS 5.0+&amp;rdquo; vs. &amp;ldquo;runs on Windows 3.1+&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;Versioning of deliverables also changed. A DOS product might ship &amp;ldquo;v1.2&amp;rdquo;;
a Windows product might need &amp;ldquo;v1.2 for Windows 3.1&amp;rdquo; vs. &amp;ldquo;v1.2 for Windows
3.11&amp;rdquo; if patch-level differences mattered. Installer design entered the
picture: copying files into the right place, registering extensions, and
creating program group icons. Teams that had never needed an &amp;ldquo;install&amp;rdquo; step
had to learn one. Early Windows installers were often batch files or simple
scripts; later, dedicated installer tools (e.g. Borland&amp;rsquo;s own offerings)
became part of the release workflow. The transition from &amp;ldquo;copy to floppy and
run&amp;rdquo; to &amp;ldquo;run setup and follow the wizard&amp;rdquo; was another incremental change that
accumulated over the early 1990s.&lt;/p&gt;
&lt;h2 id=&#34;team-collaboration-and-mental-model-shift&#34;&gt;Team collaboration and mental model shift&lt;/h2&gt;
&lt;p&gt;DOS-era teams had a shared mental model: one process, one flow, predictable
artifacts. Code reviews focused on logic, overlays, and memory. A developer
could read a program from top to bottom and follow execution. Ownership of
&amp;ldquo;the main loop&amp;rdquo; was clear.&lt;/p&gt;
&lt;p&gt;Windows-era teams dealt with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Split expertise: some people owned dialog layout (&lt;code&gt;.RC&lt;/code&gt; and resource
editor), others message handlers, others DLL interfaces. The &amp;ldquo;GUI person&amp;rdquo;
and the &amp;ldquo;engine person&amp;rdquo; became distinct roles.&lt;/li&gt;
&lt;li&gt;Asynchronous feel: events could arrive in varied order; testing had to cover
combinations. &amp;ldquo;Click A then B&amp;rdquo; vs. &amp;ldquo;Click B then A&amp;rdquo; could expose different
bugs.&lt;/li&gt;
&lt;li&gt;Toolchain fragmentation: resource compiler, different linker flags,
different debugger workflows. Build breaks could occur in the resource step,
which DOS-only developers had never seen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Documentation shifted. Instead of &amp;ldquo;run main, then X, then Y,&amp;rdquo; teams wrote &amp;ldquo;on
WM_COMMAND with ID Z, the flow is&amp;hellip;&amp;rdquo;. Architecture diagrams showed window
hierarchies and message flow, not just procedure call graphs. Onboarding
documents included &amp;ldquo;Windows messaging basics&amp;rdquo; and &amp;ldquo;OWL object lifecycle.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;New joiners needed to internalize the event loop and the idea that &amp;ldquo;your code
runs when Windows says so.&amp;rdquo; That was a larger conceptual jump than learning
Object Pascal syntax. Experienced DOS Pascal developers sometimes struggled
more than newcomers—unlearning &amp;ldquo;I control the flow&amp;rdquo; was harder than never
having assumed it.&lt;/p&gt;
&lt;p&gt;Code review practices adapted. DOS reviews often traced &amp;ldquo;what happens when we
run.&amp;rdquo; Windows reviews asked &amp;ldquo;what happens when the user does X, and in what
order do messages arrive?&amp;rdquo; Test plans shifted from &amp;ldquo;run through the menu&amp;rdquo;
to &amp;ldquo;for each dialog, test each control, test tab order, test keyboard
shortcuts.&amp;rdquo; The surface area of &amp;ldquo;things that can go wrong&amp;rdquo; grew
substantially. Senior developers who had debugged DOS programs for years
sometimes needed mentoring from junior developers who had started with
Windows—not because the seniors were less skilled, but because the younger
developers had never internalised the sequential model and adapted to
event-driven design more quickly.&lt;/p&gt;
&lt;p&gt;A practical collaboration upgrade in that period was formal handoff contracts
between UI and engine work. In DOS-only projects, one developer could often own
everything from input parsing to rendering. In TPW projects, that approach
scaled poorly because message handlers, dialog resources, and shared core logic
changed at different speeds. Teams that stayed healthy wrote explicit contracts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which messages a form handled directly versus delegated&lt;/li&gt;
&lt;li&gt;which unit owned validation rules&lt;/li&gt;
&lt;li&gt;which module owned persistence and file I/O&lt;/li&gt;
&lt;li&gt;which callbacks were synchronous, and which were deferred&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without this, &amp;ldquo;small UI tweaks&amp;rdquo; frequently broke core behavior because a
developer moved logic into a handler that now ran under a different timing
context.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Windows handoff note (example)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-----------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Form: CustomerEdit
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Owner: UI team
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Incoming messages of interest:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WM_INITDIALOG      -&amp;gt; initializes control state
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WM_COMMAND(IDOK)   -&amp;gt; calls ValidateCustomer, then SaveCustomer
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WM_CLOSE           -&amp;gt; prompts if dirty flag set
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Engine callbacks:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ValidateCustomer(Data): owned by core unit UCustomerRules
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SaveCustomer(Data): owned by storage unit UCustomerStore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Invariants:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - SaveCustomer must never run before ValidateCustomer success
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Dirty flag set only by control-change events
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Cancel path must not mutate persisted data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This kind of document looked heavy for small teams and saved debugging days.
It made expectations executable in reviews and reduced arguments about &amp;ldquo;who
owns this behavior.&amp;rdquo; It also improved onboarding because a new developer could
read one page and understand the current flow before touching code.&lt;/p&gt;
&lt;p&gt;Another change was review vocabulary. DOS reviews asked, &amp;ldquo;Does this procedure
return the right value?&amp;rdquo; Windows reviews increasingly asked, &amp;ldquo;In what callback
context does this run?&amp;rdquo; and &amp;ldquo;What other message paths can trigger this state
change?&amp;rdquo; That second question caught an entire class of defects: duplicated
state transitions caused by one logic block being reachable through both menu
commands and control notifications.&lt;/p&gt;
&lt;p&gt;Teams that developed this callback-context discipline were already preparing for
Delphi&amp;rsquo;s event model, even before switching products. The names changed (&lt;code&gt;OnClick&lt;/code&gt;
instead of &lt;code&gt;WM_COMMAND&lt;/code&gt; branches), but the design concern stayed the same: keep
state transitions explicit, idempotent where possible, and reviewable under
multiple event paths.&lt;/p&gt;
&lt;h2 id=&#34;synthesis-what-the-toolchain-taught&#34;&gt;Synthesis: what the toolchain taught&lt;/h2&gt;
&lt;p&gt;The transition from DOS Turbo Pascal to Object Pascal and TPW was not a
language change alone. The Pascal syntax, unit system, and compilation model
persisted. What changed was the execution environment, the artifact graph, and
the problem-solving strategies. It was a shift in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control flow:&lt;/strong&gt; from sequential to event-driven. Your code became a set of
handlers invoked by the runtime, not a script you controlled from start to
finish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artifacts:&lt;/strong&gt; from &lt;code&gt;.EXE&lt;/code&gt;+&lt;code&gt;.OVR&lt;/code&gt; to &lt;code&gt;.EXE&lt;/code&gt;+&lt;code&gt;.DLL&lt;/code&gt;+resources. The artifact
graph grew; build and deploy had more moving parts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; from reproducible traces to message-flow analysis. Crashes
became context-dependent; instrumentation and hypothesis replaced simple
replay.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment:&lt;/strong&gt; from single-directory to multi-component, multi-profile.
&amp;ldquo;Works on my machine&amp;rdquo; expanded to &amp;ldquo;works on which video driver, which
memory configuration, which Windows patch level.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The compiler and linker remained recognizable. The surrounding workflow—
resources, callbacks, DLLs, deployment—became the new complexity. Teams that
succeeded treated the Windows toolchain as a different system with different
rules, not &amp;ldquo;Turbo Pascal with a new UI library.&amp;rdquo; The language carried forward;
the problem-solving model had to adapt. Developers who made that mental shift
were well positioned for Delphi and the 32-bit Windows world that followed.
The lessons—event-driven design, resource pipelines, DLL boundaries—carried
forward. Delphi refined the language and tooling, but the conceptual bridge
from DOS to Windows had already been crossed.&lt;/p&gt;
&lt;h2 id=&#34;practical-migration-dos-to-windows-checklist&#34;&gt;Practical migration: DOS to Windows checklist&lt;/h2&gt;
&lt;p&gt;For teams porting an existing DOS application to Windows, a disciplined
sequence reduced risk:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Isolate platform-dependent code.&lt;/strong&gt; Identify all &lt;code&gt;Dos&lt;/code&gt;, &lt;code&gt;Crt&lt;/code&gt;, &lt;code&gt;Graph&lt;/code&gt;,
and overlay usage. Move them behind abstraction layers or &lt;code&gt;{$IFDEF}&lt;/code&gt;-guarded
units.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify string handling.&lt;/strong&gt; Audit every place that touches filenames,
user input, or API parameters. Introduce conversion routines and use them
consistently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add the resource pipeline.&lt;/strong&gt; Create a minimal &lt;code&gt;.RC&lt;/code&gt;, link it, verify the
app still runs. Add dialogs and menus incrementally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replace the main loop.&lt;/strong&gt; The DOS &amp;ldquo;repeat until done&amp;rdquo; loop becomes
&amp;ldquo;register, create, message loop.&amp;rdquo; Ensure no logic assumed it ran &amp;ldquo;at startup&amp;rdquo;
in a single pass.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test on multiple configurations.&lt;/strong&gt; Different video drivers, different
memory, and different Windows versions surfaced bugs that did not appear in
development.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Not every DOS app was worth porting. Those that were tightly coupled to
hardware (TSRs, direct port I/O, mode-X graphics) required substantial redesign
or remained DOS-only. Business logic and data-heavy applications were better
candidates.&lt;/p&gt;
&lt;p&gt;A phased approach often worked: first a Windows shell that displayed data
(perhaps read from a file format shared with the DOS version), then
incremental feature parity. Trying to port everything at once usually led to
long integration branches and merge pain. Teams that shipped a minimal
Windows version early, then iterated, had better feedback and morale.&lt;/p&gt;
&lt;h2 id=&#34;related-reading&#34;&gt;Related reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-overlay-tutorial-build-package-and-debug-an-ovr-application/&#34;&gt;Turbo Pascal Overlay Tutorial: Build, Package, and Debug an OVR Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;full-series-index&#34;&gt;Full series index&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/&#34;&gt;Part 1: Anatomy and Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-2-objects-units-and-binary-investigation/&#34;&gt;Part 2: Objects, Units, and Binary Investigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-3-overlays-memory-models-and-link-strategy/&#34;&gt;Part 3: Overlays, Memory Models, and Link Strategy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-4-graphics-drivers-bgi-and-rendering-integration/&#34;&gt;Part 4: Graphics Drivers, BGI, and Rendering Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 6: Object Pascal, TPW, and the Windows Transition (this article)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-7-from-tpwin-to-delphi-and-the-rad-mindset/&#34;&gt;Part 7: From TPW to Delphi and the RAD Mindset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Toolchain, Part 7: From TPW to Delphi and the RAD Mindset</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-7-from-tpwin-to-delphi-and-the-rad-mindset/</link>
      <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Fri, 13 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-7-from-tpwin-to-delphi-and-the-rad-mindset/</guid>
      <description>&lt;p&gt;The transition from Turbo Pascal for Windows (TPW) and Borland Pascal 7 to Delphi
was not merely a product upgrade. It was a mindset shift: from procedural
resource wrangling and manual message dispatch to a visual, component-based, and
event-driven workflow. Developers who had mastered TPW&amp;rsquo;s message loops and
resource scripts found themselves in a different world—one where the form
designer and object inspector replaced the resource editor, and where component
ownership and event handlers replaced explicit handle management.&lt;/p&gt;
&lt;p&gt;This article traces that transition from the perspective of a practitioner who
lived it. It covers workflow changes, delivery model shifts, debugging
adaptations, and team process evolution. The goal is not nostalgia but practical guidance: what to watch for when
migrating, what patterns hold, and what pitfalls to avoid. The TPW-to-Delphi
path was well-traveled in the mid-to-late 1990s; the lessons learned then
remain applicable to any transition from low-level, imperative UI development
to a higher-level, component-based framework. This article assumes familiarity
with TPW or BP7; readers new to that era may find Part 5 and Part 6 of this
series useful for context.&lt;/p&gt;
&lt;h2 id=&#34;structure-map-balanced-chapter-plan&#34;&gt;Structure map (balanced chapter plan)&lt;/h2&gt;
&lt;p&gt;To keep chapter quality even, the article uses a fixed ten-part structure before
going deep into each topic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;historical grounding and chronology boundaries&lt;/li&gt;
&lt;li&gt;what was at stake in workflow terms&lt;/li&gt;
&lt;li&gt;form/resource workflow changes&lt;/li&gt;
&lt;li&gt;component model and package mechanics&lt;/li&gt;
&lt;li&gt;common migration culprits&lt;/li&gt;
&lt;li&gt;build/release pipeline changes&lt;/li&gt;
&lt;li&gt;testing/debugging mindset shift&lt;/li&gt;
&lt;li&gt;architecture consequences&lt;/li&gt;
&lt;li&gt;team-process and delivery-model changes&lt;/li&gt;
&lt;li&gt;migration pattern playbook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each chapter is intentionally expanded with similar depth: mechanism, pitfalls,
and practical migration guidance.&lt;/p&gt;
&lt;h2 id=&#34;historical-grounding-19931996&#34;&gt;Historical grounding: 1993–1996&lt;/h2&gt;
&lt;p&gt;Delphi development started internally at Borland around 1993. The first public
release shipped in February 1995. That release introduced the Visual Component
Library (VCL), which became the central framework for visual, event-driven
Windows development in Object Pascal. Delphi 2 arrived in 1996 with a strong
focus on 32-bit Windows, consolidating the shift away from 16-bit TPW.&lt;/p&gt;
&lt;p&gt;These dates matter because they bound the technical assumptions. TPW and BP7
targeted 16-bit Windows. Delphi 1 supported 16-bit; Delphi 2 and later targeted
32-bit. Anyone migrating in that window faced both paradigm and platform shifts.&lt;/p&gt;
&lt;p&gt;The competitive landscape also shaped expectations. Visual Basic had established
a visual-design paradigm; Borland&amp;rsquo;s Object Windows Library (OWL) offered an
object-oriented wrapper over the Windows API but remained close to the message
model. Delphi positioned itself between the two: more structured than VB, more
visual than raw OWL. The VCL was the differentiator—a single framework that
unified visual design, component reuse, and compiled performance. Delphi 1
supported 16-bit Windows; migration from TPW could proceed without an immediate
32-bit requirement. Delphi 2&amp;rsquo;s 32-bit focus, arriving in 1996, aligned with
Windows 95&amp;rsquo;s dominance and made the 16-bit path a legacy concern for most new
development. The choice of Object Pascal rather than C++ for the VCL reflected Borland&amp;rsquo;s
heritage and the language&amp;rsquo;s suitability for rapid development: a simpler
object model, predictable destruction, and strong typing reduced certain
classes of bugs. The trade-off was less low-level control than C++; for most
business applications, that trade-off was acceptable. The result was a tool
that appealed to both former TPW developers and newcomers from VB or other
environments.&lt;/p&gt;
&lt;h2 id=&#34;what-was-at-stake-from-resource-wrangler-to-form-designer&#34;&gt;What was at stake: from resource wrangler to form designer&lt;/h2&gt;
&lt;p&gt;In TPW, you typically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hand-authored or used resource editors to produce &lt;code&gt;.RC&lt;/code&gt; and &lt;code&gt;.RES&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;wrote &lt;code&gt;WndProc&lt;/code&gt; handlers and message-case logic&lt;/li&gt;
&lt;li&gt;managed child window placement and styling via API calls&lt;/li&gt;
&lt;li&gt;linked and loaded resources explicitly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mental model was &lt;em&gt;imperative&lt;/em&gt;: you told Windows what to do, step by step.
Delphi replaced that with a &lt;em&gt;declarative&lt;/em&gt; model: you placed components on forms,
set properties, and responded to events. The form became the primary unit of
design, not the resource file.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// TPW-era: manual dialog creation and message handling
function DlgProc(Dlg: HWND; Msg: Word; WParam: Word; LParam: LongInt): Bool;
begin
  Result := False;
  case Msg of
    WM_INITDIALOG: begin
      SetWindowText(GetDlgItem(Dlg, ID_EDIT), &amp;#39;&amp;#39;);
      Result := True;
    end;
    WM_COMMAND:
      if LoWord(WParam) = IDOK then begin
        GetDlgItemText(Dlg, ID_EDIT, Buffer, SizeOf(Buffer));
        EndDialog(Dlg, IDOK);
        Result := True;
      end;
  end;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Delphi, the same interaction is expressed as component events:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure TMainForm btnOKClick(Sender: TObject);
begin
  // Edit1.Text is directly available; no GetDlgItemText
  ProcessInput(Edit1.Text);
  ModalResult := mrOK;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The shift is not cosmetic. Ownership, lifecycle, and coupling all change. In
TPW, you were responsible for ensuring that every control you created was
eventually destroyed and that no dangling handles survived. In Delphi, the
component tree and ownership model handle that—provided you used &lt;code&gt;Create&lt;/code&gt; with
the correct owner. The mental load shifted from &amp;ldquo;did I free everything?&amp;rdquo; to
&amp;ldquo;did I wire the right events and set the right properties?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;A TPW developer who had internalized the message loop could predict exactly
when &lt;code&gt;WM_PAINT&lt;/code&gt; would fire and in what order. Delphi&amp;rsquo;s &lt;code&gt;OnPaint&lt;/code&gt; and &lt;code&gt;Invalidate&lt;/code&gt;
abstracted that; the framework decided when to paint. That abstraction was
liberating for routine UI work but could be frustrating when squeezing out
performance or debugging flicker. Knowing when to drop to &lt;code&gt;WndProc&lt;/code&gt; or
&lt;code&gt;CreateParams&lt;/code&gt; for low-level control became a mark of seniority. Double-buffering,
which reduced flicker in TPW by managing &lt;code&gt;WM_ERASEBKGND&lt;/code&gt; and paint regions, had
VCL analogs (&lt;code&gt;DoubleBuffered&lt;/code&gt;, &lt;code&gt;TBitmap&lt;/code&gt; offscreen drawing), but the control
points were different. Migration often required re-learning where the levers were.
Developers who had tuned TPW apps for smooth animation or rapid repaints often
needed to re-profile in Delphi: the VCL&amp;rsquo;s paint sequence and invalidation
semantics were not identical to raw &lt;code&gt;WM_PAINT&lt;/code&gt; handling. In most cases the
default behavior was sufficient; for performance-critical paths, measuring
before optimizing remained the rule.&lt;/p&gt;
&lt;h2 id=&#34;form-and-resource-workflow-changes&#34;&gt;Form and resource workflow changes&lt;/h2&gt;
&lt;p&gt;TPW projects combined Pascal sources with resource scripts. A typical layout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MAIN.RC&lt;/code&gt; defined menus, dialogs, string tables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BRCC.EXE&lt;/code&gt; produced &lt;code&gt;MAIN.RES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$R MAIN.RES&lt;/code&gt; pulled resources into the executable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Form layout was encoded in dialog templates. Moving a button meant editing
coordinates in the &lt;code&gt;.RC&lt;/code&gt; file or using a separate resource editor. Visual
feedback was indirect. A typical TPW session might involve: edit &lt;code&gt;.RC&lt;/code&gt;, run
&lt;code&gt;BRCC&lt;/code&gt;, recompile, run, discover the button was two pixels off, repeat. The
compile-run cycle was fast, but the layout iteration was tedious.&lt;/p&gt;
&lt;p&gt;Delphi introduced the &lt;code&gt;.DFM&lt;/code&gt; (Delphi Form) file: a textual or binary
representation of the form&amp;rsquo;s component tree and properties. The form designer
and the form&amp;rsquo;s object inspector became the primary interface for layout and
configuration. The &lt;code&gt;.DFM&lt;/code&gt; is paired with a &lt;code&gt;.PAS&lt;/code&gt; file that defines the
component event handlers.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Delphi unit: MainForm.pas (conceptual)
unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TMainForm = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.Button1Click(Sender: TObject);
begin
  ShowMessage(&amp;#39;Value: &amp;#39; + Edit1.Text);
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;{$R *.DFM}&lt;/code&gt; directive embeds the form&amp;rsquo;s binary resource. No separate &lt;code&gt;.RC&lt;/code&gt;
file is needed for the form itself. Dialogs, menus, and layout live in the form
file; the Pascal unit owns the behavior.&lt;/p&gt;
&lt;p&gt;Early Delphi used binary &lt;code&gt;.DFM&lt;/code&gt; by default. The format was compact but opaque;
merging conflicts in version control were difficult. Later versions offered
text-based &lt;code&gt;.DFM&lt;/code&gt;, which improved diffability. Teams doing collaborative form
work learned to prefer textual form storage where possible.&lt;/p&gt;
&lt;p&gt;The form designer also changed the workflow for alignment and layout. Delphi
provided alignment tools, snap-to-grid, and the ability to select multiple
controls and align them as a group. This reduced the tedium of pixel-perfect
placement and made iteration faster.&lt;/p&gt;
&lt;h3 id=&#34;the-object-inspector-and-design-time-behavior&#34;&gt;The object inspector and design-time behavior&lt;/h3&gt;
&lt;p&gt;A TPW developer edited resources in one tool and wrote Pascal in another.
Delphi unified these: selecting a control in the form designer populated the
object inspector with that control&amp;rsquo;s properties and events. Changing &lt;code&gt;Caption&lt;/code&gt;
or &lt;code&gt;Enabled&lt;/code&gt; took effect immediately in the designer. Double-clicking an event
slot (e.g. &lt;code&gt;OnClick&lt;/code&gt;) created a stub handler and jumped to the code. This tight
loop—design, set property, wire event, run—defined the RAD experience.&lt;/p&gt;
&lt;p&gt;Design-time behavior rested on the same component instances that would run at
runtime. A form loaded in the designer was a real &lt;code&gt;TForm&lt;/code&gt; descendant with real
children. Code that assumed a full application context (e.g. &lt;code&gt;Application.MainForm&lt;/code&gt;)
could fail in the designer. The &lt;code&gt;csDesigning in ComponentState&lt;/code&gt; check became a
standard guard for code that should run only at runtime. Custom components that
performed I/O, showed dialogs, or accessed the network in their constructor
needed such guards—otherwise the designer would hang or error when the
component was dropped on a form.&lt;/p&gt;
&lt;h2 id=&#34;the-component-model-and-packages&#34;&gt;The component model and packages&lt;/h2&gt;
&lt;p&gt;VCL is built on &lt;code&gt;TComponent&lt;/code&gt;, which extends &lt;code&gt;TPersistent&lt;/code&gt; and introduces
ownership, naming, and streaming. Components can contain other components; they
participate in design-time and runtime property streaming.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Minimal custom component skeleton
unit MyButton;

interface

uses
  Classes, Controls, StdCtrls;

type
  TMyButton = class(TButton)
  private
    FClickCount: Integer;
  protected
    procedure Click; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property ClickCount: Integer read FClickCount;
  end;

procedure Register;

implementation

constructor TMyButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FClickCount := 0;
end;

procedure TMyButton.Click;
begin
  Inc(FClickCount);
  inherited Click;
end;

procedure Register;
begin
  RegisterComponents(&amp;#39;Samples&amp;#39;, [TMyButton]);
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Packages (&lt;code&gt;.DPK&lt;/code&gt;) emerged as the unit of distribution for components and
optional runtime modules. A package lists units and required packages; it can
be design-time only, runtime only, or both. This allowed teams to ship
component libraries without recompiling the main application. Design-time
packages extended the IDE with new components and editors; runtime packages
shipped as &lt;code&gt;.BPL&lt;/code&gt; files and reduced application size when shared. The split
meant that a bug fix in a shared component could be deployed by updating the
BPL—if versioning was under control.&lt;/p&gt;
&lt;h3 id=&#34;third-party-components-and-the-ecosystem&#34;&gt;Third-party components and the ecosystem&lt;/h3&gt;
&lt;p&gt;Delphi&amp;rsquo;s component model encouraged a market for third-party controls: grids,
charting, reporting, database-aware widgets. TPW had little equivalent; you
built or hand-rolled most UI. Adopting a commercial component library
accelerated development but introduced dependency risk. Components that
assumed specific VCL versions, or used undocumented interfaces, could break on
upgrade. Teams learned to evaluate components for stability and source
availability, not just features. When a critical component was abandoned by
its vendor, having the source often meant the difference between a fix and a
rewrite.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Example package source (.DPK)
package MyComponents;

{$R *.RES}
{$DESCRIPTION &amp;#39;Custom component library&amp;#39;}

requires
  vcl;

contains
  MyButton in &amp;#39;MyButton.pas&amp;#39;;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The component model also introduced the &lt;em&gt;published&lt;/em&gt; keyword: properties
declared published appear in the object inspector and are streamed to the
&lt;code&gt;.DFM&lt;/code&gt;. This is where design-time configuration meets runtime behavior.&lt;/p&gt;
&lt;p&gt;Understanding the VCL hierarchy helped when extending or debugging components.
&lt;code&gt;TObject&lt;/code&gt; roots the tree; &lt;code&gt;TPersistent&lt;/code&gt; adds streaming and ownership hooks;
&lt;code&gt;TComponent&lt;/code&gt; adds the component container model and design-time support;
&lt;code&gt;TControl&lt;/code&gt; adds visual representation and parent-child layout; &lt;code&gt;TWinControl&lt;/code&gt;
adds the Windows handle. When a form failed to paint or a control behaved
oddly, tracing up this chain often revealed where the contract was violated.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// TForm inherits Handle, Parent, BoundsRect, Paint from TWinControl chain
// Override CreateParams, CreateWnd, WndProc for low-level customization
procedure TMyForm.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or WS_CLIPCHILDREN;
end;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;culprits-and-pitfalls-during-migration&#34;&gt;Culprits and pitfalls during migration&lt;/h2&gt;
&lt;p&gt;Migration from TPW to Delphi was rarely a clean mechanical translation. The
syntax was similar; the runtime model was not. Teams moving in that period
encountered several recurring failure modes. Recognizing them early saved
significant debugging time. What worked in TPW could fail subtly in Delphi,
and the failures were often intermittent—dependent on timing, handle state, or
initialization order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource and handle confusion.&lt;/strong&gt; TPW code often stored &lt;code&gt;HWND&lt;/code&gt; or &lt;code&gt;HMenu&lt;/code&gt;
values and passed them to API calls. Delphi wraps these in component properties.
Accessing the raw handle is still possible (&lt;code&gt;Handle&lt;/code&gt;, &lt;code&gt;Menu.Handle&lt;/code&gt;), but
component lifetime now governs when that handle is valid. Code that cached
handles across form recreate or destroy cycles could break.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message loop assumptions.&lt;/strong&gt; TPW applications sometimes relied on custom
message loops or &lt;code&gt;PeekMessage&lt;/code&gt;/&lt;code&gt;GetMessage&lt;/code&gt; patterns. The VCL provides its own
application message loop. Bypassing it or mixing models led to inconsistent
behavior and hard-to-reproduce bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;String and type mismatches.&lt;/strong&gt; TPW used ShortString by default. Delphi
introduced &lt;code&gt;AnsiString&lt;/code&gt; as the default string type (in 32-bit Delphi), with
automatic memory management. Code that relied on length-byte semantics or
passed strings to legacy APIs without conversion could fail.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Pitfall: assuming ShortString semantics with AnsiString
procedure LegacyInterop;
var
  S: string;  // AnsiString in 32-bit Delphi
  Buf: array[0..255] of Char;
begin
  S := Edit1.Text;
  // Wrong: AnsiString is null-terminated, not length-prefixed
  // Right: StrPLCopy(Buf, S, High(Buf)); then use Buf for API calls
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Unit initialization order.&lt;/strong&gt; Delphi units have &lt;code&gt;initialization&lt;/code&gt; and
&lt;code&gt;finalization&lt;/code&gt; sections. Dependency order affects startup and shutdown.
Circular unit references, or initialization that assumed a specific load order,
could cause subtle crashes. A unit that allocated resources in &lt;code&gt;initialization&lt;/code&gt;
and freed them in &lt;code&gt;finalization&lt;/code&gt; was generally safe—unless another unit&amp;rsquo;s
&lt;code&gt;initialization&lt;/code&gt; ran later and expected those resources to exist. Debugging
startup crashes often meant tracing the unit load order in the project&amp;rsquo;s &lt;code&gt;uses&lt;/code&gt;
clause and the uses clauses of each unit. Circular references between units
caused compile errors; circular logic in initialization (A init calls B, B init
calls A) caused runtime failure. Breaking cycles by extracting shared code
into a third unit, or deferring init to a later phase, was the standard fix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Over-reliance on global state.&lt;/strong&gt; TPW code often used global variables for
form references and shared data. Delphi encourages form instances and
component ownership. Migrating without refactoring globals led to
re-entrancy and lifetime bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modal vs modeless confusion.&lt;/strong&gt; TPW used &lt;code&gt;DialogBox&lt;/code&gt; for modal dialogs and
&lt;code&gt;CreateWindow&lt;/code&gt; for modeless. Delphi&amp;rsquo;s &lt;code&gt;ShowModal&lt;/code&gt; and &lt;code&gt;Show&lt;/code&gt; map to that, but
the timing of &lt;code&gt;OnShow&lt;/code&gt;, &lt;code&gt;OnActivate&lt;/code&gt;, and &lt;code&gt;OnCreate&lt;/code&gt; differs from the raw
API sequence. Code that assumed a specific order (e.g. painting before data
load) could break. Testing both modal and modeless code paths was essential.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integer and pointer size changes.&lt;/strong&gt; In 16-bit TPW, &lt;code&gt;Integer&lt;/code&gt; and &lt;code&gt;Pointer&lt;/code&gt;
were both 2 bytes (or 4 for far pointers). In 32-bit Delphi, &lt;code&gt;Integer&lt;/code&gt; stayed
4 bytes but &lt;code&gt;Pointer&lt;/code&gt; became 4 bytes in a flat address space. Code that
stuffed pointers into &lt;code&gt;Word&lt;/code&gt; or &lt;code&gt;Integer&lt;/code&gt; for storage could truncate or
corrupt. Using &lt;code&gt;LongInt&lt;/code&gt; or &lt;code&gt;Pointer&lt;/code&gt; explicitly for pointer-sized values
avoided surprises.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RecreateWindow and handle invalidation.&lt;/strong&gt; When a form&amp;rsquo;s &lt;code&gt;RecreateWnd&lt;/code&gt; or
similar mechanism ran (e.g. after changing &lt;code&gt;BorderStyle&lt;/code&gt; or &lt;code&gt;BorderIcons&lt;/code&gt;), the
underlying &lt;code&gt;HWND&lt;/code&gt; was destroyed and recreated. Code that cached the handle in
a variable held a stale value. The pattern &lt;code&gt;if HandleAllocated then&lt;/code&gt; before
using &lt;code&gt;Handle&lt;/code&gt; became a habit.&lt;/p&gt;
&lt;h2 id=&#34;build-and-release-workflow&#34;&gt;Build and release workflow&lt;/h2&gt;
&lt;p&gt;TPW builds were typically driven by the IDE or a small batch script that
invoked the compiler and linker. Output was a single &lt;code&gt;.EXE&lt;/code&gt; or &lt;code&gt;.DLL&lt;/code&gt;.
Delphi preserved that simplicity for many projects but added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;project files (&lt;code&gt;.DPR&lt;/code&gt;) as the entry point&lt;/li&gt;
&lt;li&gt;form units and &lt;code&gt;{$R *.DFM}&lt;/code&gt; as first-class build inputs&lt;/li&gt;
&lt;li&gt;package builds for component libraries&lt;/li&gt;
&lt;li&gt;conditional compilation and build configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project file (&lt;code&gt;.DPR&lt;/code&gt;) replaced the old &amp;ldquo;main program&amp;rdquo; as the coordination
point. It listed form units, marked which forms were auto-created (and thus
loaded at startup), and could embed conditional compilation for different
build targets. Auto-created forms simplified startup but could slow launch
when many forms were created eagerly. Teams learned to create forms on demand
(&lt;code&gt;Form2 := TForm2.Create(Application); Form2.Show;&lt;/code&gt;) when memory or startup
time mattered.&lt;/p&gt;
&lt;p&gt;A minimal Delphi project:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program MyApp;

uses
  Forms,
  MainForm in &amp;#39;MainForm.pas&amp;#39; {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Command-line builds became &lt;code&gt;DCC32.EXE&lt;/code&gt; (32-bit) or &lt;code&gt;DCC.EXE&lt;/code&gt; (16-bit in Delphi 1).
The linker (&lt;code&gt;ILINK32&lt;/code&gt; in 32-bit) consumed object files from the compiler; package
references and external object modules were configured in the project or unit
sources. Release builds often disabled debug info (&lt;code&gt;$D-&lt;/code&gt;), local symbol info (&lt;code&gt;$L-&lt;/code&gt;),
overflow checking (&lt;code&gt;$Q-&lt;/code&gt;), range checking (&lt;code&gt;$R-&lt;/code&gt;), and stack checking (&lt;code&gt;$S-&lt;/code&gt;).
Teams learned to freeze these settings per configuration. Enabling checks in
debug builds caught many bugs before they reached production; disabling them
in release improved performance. The discipline was to fix any violation exposed by checks rather than disabling
checks to silence the error. A build that succeeded with &lt;code&gt;$R+&lt;/code&gt; in one
configuration and failed with it in another indicated a latent bug. Treating
such failures as &amp;ldquo;the check is wrong&amp;rdquo; rather than &amp;ldquo;we need to fix the code&amp;rdquo; was
a common but costly mistake. Range and overflow checks were cheap enough in
debug that the performance argument against them rarely held.&lt;/p&gt;
&lt;p&gt;The shift to 32-bit also meant larger executables and different deployment
considerations—no more overlays, but more reliance on DLLs and packages for
modular delivery. A typical build script might invoke &lt;code&gt;DCC32&lt;/code&gt; with &lt;code&gt;-B&lt;/code&gt; (build
all), &lt;code&gt;-$D-&lt;/code&gt; (no debug info), and &lt;code&gt;-$R-&lt;/code&gt; (no range check) for release. Staging
the correct runtime packages (&lt;code&gt;VCL*.BPL&lt;/code&gt;, &lt;code&gt;RTL*.BPL&lt;/code&gt;) alongside the &lt;code&gt;.EXE&lt;/code&gt;
became part of the release checklist. The build pipeline itself was similar in
spirit to TPW: compile units to object files, link to executable. The difference
was scale—more units, form resources, and optional packages. Automated builds
that had been simple batch files grew into scripts with conditional compilation,
path setup, and post-build steps (e.g. version stamping, resource injection).
Teams that delayed automation paid a tax during release cycles when manual steps
were forgotten or executed in the wrong order.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Project options often embedded in .DPR or a separate .CFG
// Conditional defines for build variants
{$IFDEF RELEASE}
{$D-} {$L-} {$Q-} {$R-} {$S-}
{$OPTIMIZATION ON}
{$ELSE}
{$D+} {$L+} {$Q+} {$R+} {$S+}
{$OPTIMIZATION OFF}
{$ENDIF}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;testing-and-debugging-mentality-shift&#34;&gt;Testing and debugging mentality shift&lt;/h2&gt;
&lt;p&gt;TPW debugging was breakpoint-and-inspect. You set breakpoints, stepped through
&lt;code&gt;WndProc&lt;/code&gt; and message handlers, and used the CPU view when things went wrong.
The event model was explicit; you could trace from message to handler.&lt;/p&gt;
&lt;p&gt;Delphi&amp;rsquo;s event-driven model changed the mental model. A button click did not
map to a single linear path. Events could be chained (e.g. &lt;code&gt;OnChange&lt;/code&gt; triggering
further updates), and the call stack often included VCL framework code. Debuggers
gained form-aware inspection: you could inspect the live form, its components,
and their properties at breakpoints.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Event-driven debugging: understand the call chain
procedure TForm1.Button1Click(Sender: TObject);
begin
  // Set breakpoint here; Sender tells you which button fired
  UpdateStatus;  // May trigger other events
end;

procedure TForm1.UpdateStatus;
begin
  // Breakpoint here to see who called UpdateStatus
  Label1.Caption := ComputeStatus;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A recurring debugging scenario was &amp;ldquo;why did my form not update?&amp;rdquo; In TPW, you
traced &lt;code&gt;WM_PAINT&lt;/code&gt; or &lt;code&gt;InvalidateRect&lt;/code&gt;. In Delphi, you checked whether
&lt;code&gt;Invalidate&lt;/code&gt; or &lt;code&gt;Repaint&lt;/code&gt; was called, whether the control was visible, and
whether &lt;code&gt;OnPaint&lt;/code&gt; was overridden correctly. The data window (inspecting
component properties at breakpoints) became as important as the watch window.
Seeing that &lt;code&gt;Label1.Caption&lt;/code&gt; was empty when you expected text, or that
&lt;code&gt;Edit1.Visible&lt;/code&gt; was &lt;code&gt;False&lt;/code&gt;, often explained the bug without stepping through
framework code.&lt;/p&gt;
&lt;p&gt;The shift also encouraged a different testing approach: rather than
exercising raw message paths, tests targeted event handlers and component
state. Unit testing frameworks were rare in the mid-1990s, but the separation
of event handlers from UI layout made it easier to reason about behavior in
isolation.&lt;/p&gt;
&lt;p&gt;When debugging failed, the CPU view remained the fallback. Crashes in VCL
internals or third-party components often required setting a breakpoint on
exceptions, then inspecting the call stack and registers. The &amp;ldquo;Evaluate/Modify&amp;rdquo;
dialog let you execute expressions and change variables at breakpoints—useful
for testing fixes without recompiling. Teams developed a habit of creating
minimal reproduction cases: a blank form with one or two controls that
exhibited the bug, stripped of application-specific logic.&lt;/p&gt;
&lt;h2 id=&#34;architecture-implications&#34;&gt;Architecture implications&lt;/h2&gt;
&lt;p&gt;RAD and the VCL did not mandate architecture, but they pushed architects toward
certain patterns. Teams that resisted sometimes paid a maintenance tax; teams
that embraced them could scale. The framework rewarded specific ways of
organizing code and penalized others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Persistence and streaming.&lt;/strong&gt; The VCL&amp;rsquo;s streaming system allowed forms and
components to be saved and loaded without hand-written serialization. The
&lt;code&gt;TReader&lt;/code&gt;/&lt;code&gt;TWriter&lt;/code&gt; and &lt;code&gt;DefineProperties&lt;/code&gt; mechanism supported custom data in
components. Component authors who needed to store non-published state could
override &lt;code&gt;DefineProperties&lt;/code&gt; to read and write their data. This was powerful
but easy to get wrong—version mismatches between stored and current property
semantics could corrupt form files. Defensive readers that checked version
numbers or used try/except around property reads were common. Custom components
that stored complex data (e.g. tree structures, graphs) had to decide whether
to use &lt;code&gt;DefineProperties&lt;/code&gt; or separate files. Embedded storage simplified
deployment; separate files allowed formats that could be edited independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event-driven design.&lt;/strong&gt; Logic moved from a central message pump into
distributed event handlers. This improved locality (each component owned its
responses) but could scatter business logic across many handlers. Disciplined
teams extracted core logic into service units or classes, keeping handlers thin.
The &lt;code&gt;Sender&lt;/code&gt; parameter in events allowed one handler to serve multiple controls
(e.g. several buttons sharing an &lt;code&gt;OnClick&lt;/code&gt;), but that pattern could obscure
which control actually fired. Using separate handlers or &lt;code&gt;if Sender = Button1&lt;/code&gt;
kept intent clear. The balance between DRY and readability was project-specific.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Threading and the main thread.&lt;/strong&gt; The VCL was not designed for multi-threaded
UI updates. Modifying control properties or calling UI methods from a worker
thread could cause unpredictable crashes. The rule was: all UI updates must
happen on the main thread. &lt;code&gt;Synchronize&lt;/code&gt; and &lt;code&gt;Queue&lt;/code&gt; (in later Delphi
versions) marshaled work from background threads to the main thread. TPW code
that had used worker threads for long operations had to be adapted to this
model; the logic could stay in the thread, but any UI feedback had to go
through &lt;code&gt;Synchronize&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separation of concerns.&lt;/strong&gt; The form file (&lt;code&gt;.DFM&lt;/code&gt;) held layout and property
defaults; the Pascal unit held behavior. That split made it easier to
version-control and merge changes, though &lt;code&gt;.DFM&lt;/code&gt; binary format could be opaque.
Later Delphi versions supported textual &lt;code&gt;.DFM&lt;/code&gt; for clearer diffs. The
separation also meant that a designer could adjust layout without touching
code, and a developer could change behavior without risking layout. In
practice, the split was porous—event handlers often reached into control
properties, and layout could affect behavior (e.g. tab order, focus). But the
ideal was clear: form for structure, unit for logic. Tab order in particular
caused headaches: the designer set it visually, but adding or removing controls
could scramble the intended flow. Using &lt;code&gt;TabOrder&lt;/code&gt; explicitly, or the tab-order
dialog, was part of the polish that separated finished applications from
prototypes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Component reuse and ownership.&lt;/strong&gt; The &lt;code&gt;Owner&lt;/code&gt; parameter in &lt;code&gt;TComponent.Create&lt;/code&gt;
established parent-child relationships. Destroying a form destroyed its
components. This eliminated many manual cleanup bugs but required understanding
ownership when creating components dynamically. Creating a control with &lt;code&gt;nil&lt;/code&gt;
as owner meant you were responsible for freeing it—a common source of leaks
when the pattern was forgotten. The rule &amp;ldquo;always pass an owner when you have
one&amp;rdquo; became second nature.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Ownership: Created edit is owned by Form1, freed when Form1 is freed
procedure TForm1.AddDynamicEdit;
var
  E: TEdit;
begin
  E := TEdit.Create(Self);  // Self = Form1 = owner
  E.Parent := Self;
  E.Top := 10;
  E.Left := 10;
  E.Text := &amp;#39;Dynamic&amp;#39;;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Dependency direction.&lt;/strong&gt; Well-structured Delphi projects kept business logic
in units that did not depend on &lt;code&gt;Forms&lt;/code&gt; or &lt;code&gt;Controls&lt;/code&gt;. UI units depended on
business units, not the reverse. This preserved testability and reuse.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Good: business logic unit has no UI dependency
unit OrderLogic;

interface
function ValidateOrder(const OrderId: string): Boolean;

implementation
// No Forms, Controls, or Graphics
end.

// UI unit depends on OrderLogic
unit OrderForm;

uses
  ..., OrderLogic;

procedure TOrderForm.btnValidateClick(Sender: TObject);
begin
  if ValidateOrder(edtOrderId.Text) then
    ShowMessage(&amp;#39;Valid&amp;#39;);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Form bloat.&lt;/strong&gt; A common anti-pattern was the &amp;ldquo;god form&amp;rdquo;: one form with dozens
of controls and thousands of lines. Splitting into sub-forms, frames (when
available), or tabbed interfaces required discipline. The RAD temptation was to
keep adding controls; the architectural response was to extract coherent
panels into separate units.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data binding and the missing link.&lt;/strong&gt; Early Delphi did not ship a formal
data-binding framework. Developers manually moved data between controls and
business objects in event handlers. The pattern &amp;ldquo;read from controls, validate,
update model, write back to controls&amp;rdquo; was common. This worked but scattered
synchronization logic. Third-party data-aware controls and later framework
additions addressed some of this; disciplined teams often built thin adapter
layers to centralize the binding logic.&lt;/p&gt;
&lt;h2 id=&#34;delivery-model-and-team-process-changes&#34;&gt;Delivery model and team process changes&lt;/h2&gt;
&lt;p&gt;The RAD promise was faster delivery. The reality was more nuanced.&lt;/p&gt;
&lt;p&gt;TPW projects often had a single developer or a small team with clear handoffs:
one person owned resources, another owned logic. Delphi&amp;rsquo;s RAD workflow
encouraged faster iteration. A developer could design a form, wire events, and
see results without leaving the IDE. That accelerated prototyping but also
tempted teams to skip design—&amp;ldquo;we&amp;rsquo;ll fix it later&amp;rdquo; became a common anti-pattern.&lt;/p&gt;
&lt;p&gt;Delivery cycles shortened. Demo builds could be produced in hours. The flip
side was technical debt: forms with hundreds of controls, event handlers
doing too much, and little automated testing. Teams that adopted coding
standards (handler size limits, mandatory extraction of business logic)
fared better.&lt;/p&gt;
&lt;p&gt;When RAD went wrong, the symptoms were familiar: a form that &amp;ldquo;worked&amp;rdquo; until
you changed one thing and then everything broke; event handlers that called
each other in circular ways; business logic embedded in &lt;code&gt;OnClick&lt;/code&gt; that could
not be tested without spinning up the full form. The remedy was the same as in
non-RAD projects—extract, decompose, test—but the temptation to stay in &amp;ldquo;fast
mode&amp;rdquo; was stronger because the IDE made it easy to keep adding. Senior
developers learned to recognize the moment when a form or handler had crossed
the complexity threshold and needed refactoring.&lt;/p&gt;
&lt;p&gt;Distribution also changed. TPW produced a standalone &lt;code&gt;.EXE&lt;/code&gt; plus any DLLs.
Delphi could do the same, but package-based deployments (runtime packages
like &lt;code&gt;VCL50.BPL&lt;/code&gt;) allowed smaller executables and shared framework updates.
The trade-off was versioning: mismatched package versions caused load failures.
&amp;ldquo;DLL hell&amp;rdquo; extended to packages: installing a new application could overwrite
shared BPLs and break existing ones. Many teams chose static linking for
distribution to avoid that risk.&lt;/p&gt;
&lt;p&gt;Team roles shifted. The &amp;ldquo;resource person&amp;rdquo; role diminished; the &amp;ldquo;form designer&amp;rdquo;
and &amp;ldquo;component author&amp;rdquo; roles emerged. Code reviews began to ask &amp;ldquo;is this
handler too large?&amp;rdquo; and &amp;ldquo;should this logic live in a service unit?&amp;rdquo; Pair
programming, where it existed, often involved one person driving the form
designer while the other focused on event logic and backend integration. The
division was natural: layout and property wrangling on one side, data flow and
validation on the other. Teams that formalized this split—e.g. &amp;ldquo;form designer&amp;rdquo;
and &amp;ldquo;form programmer&amp;rdquo; roles—sometimes produced cleaner boundaries than those
where one person did everything. The risk was handoff friction when the
designer&amp;rsquo;s intent was not clear from the form alone.&lt;/p&gt;
&lt;h2 id=&#34;practical-migration-patterns&#34;&gt;Practical migration patterns&lt;/h2&gt;
&lt;p&gt;When porting TPW code to Delphi, these patterns proved reliable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extract message handlers into event-like procedures.&lt;/strong&gt; Wrap the core logic
in a procedure with clear parameters; call it from both the old &lt;code&gt;WndProc&lt;/code&gt; path
and the new event handler during transition.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure DoProcessInput(const AText: string);
begin
  if Trim(AText) = &amp;#39;&amp;#39; then Exit;
  // Core logic here
end;

// TPW: call from WM_COMMAND handler
// Delphi: call from Button1Click with Edit1.Text&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Introduce form classes gradually.&lt;/strong&gt; Start with a blank form, add controls
one at a time, and move logic from global procedures into form methods. This
avoids big-bang rewrites. Resist the urge to convert all dialogs in one pass.
Pick the simplest dialog first, migrate it, validate, then proceed. Each
successful migration builds confidence and surfaces patterns that apply to
the next.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a compatibility shim for shared code.&lt;/strong&gt; If both TPW and Delphi
executables need to call the same business logic during transition, extract
that logic into a unit with no UI dependencies. Both projects can use it.
Pass data via parameters, not globals. This keeps the migration reversible
and reduces the risk of fork drift. The shim unit should avoid VCL-specific
types where possible; use plain Pascal types (&lt;code&gt;string&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, records)
for interfaces that cross the TPW/Delphi boundary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verify string and API compatibility.&lt;/strong&gt; Use &lt;code&gt;StrPLCopy&lt;/code&gt; and &lt;code&gt;StrPCopy&lt;/code&gt; when
passing strings to Windows API. Check &lt;code&gt;PChar&lt;/code&gt; vs &lt;code&gt;PAnsiChar&lt;/code&gt; in 32-bit Delphi.
Test with empty strings and long strings; ShortString and AnsiString differ at
the boundaries.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Safe API string passing
procedure SafeAPICall(const S: string);
var
  Buf: array[0..259] of AnsiChar;
begin
  StrPLCopy(Buf, AnsiString(S), High(Buf));
  SomeAPI(@Buf[0]);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Lock build configuration early.&lt;/strong&gt; Decide debug vs release, range check on/off,
and optimization level. Document and automate. Avoid ad hoc changes during
release crunches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Migration checklist.&lt;/strong&gt; A practical sequence:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1. Inventory TPW dialogs and main windows; map each to a target form.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2. Create empty forms, add controls to match layout, wire stub events.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3. Move message-handler logic into event handlers; extract shared logic.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4. Replace global form references with Application.FindComponent or parameters.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5. Audit string types at API boundaries; add StrPLCopy/StrPCopy where needed.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;6. Run under range checking and overflow checking; fix violations first.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;7. Test modal/modeless behavior; verify focus and activation order.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8. Freeze build options; document and script the release build.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Use Application.OnMessage sparingly.&lt;/strong&gt; The global message hook can help
during migration to intercept specific messages, but it runs for every message
and can obscure the event-driven flow. Prefer component-level overrides or
message handlers (&lt;code&gt;TForm&lt;/code&gt; supports &lt;code&gt;WM_*&lt;/code&gt; procedure declarations) for targeted
handling.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Form-level message handler: more targeted than Application.OnMessage
type
  TMainForm = class(TForm)
  private
    procedure WMUserMsg(var Msg: TMessage); message WM_USER;
  end;

procedure TMainForm.WMUserMsg(var Msg: TMessage);
begin
  // Handle custom message; call inherited for default behavior if needed
  ProcessCustomMessage(Msg.WParam, Msg.LParam);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Preserve TPW project artifacts during transition.&lt;/strong&gt; Keep a known-good TPW
build and its sources in version control. If a Delphi regression appears,
you can compare behavior and isolate whether the bug is in migrated logic or
the new framework. When the migration is complete, archive rather than
delete—historical reference has value for onboarding and retrospective analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Treat the first migrated dialog as a prototype.&lt;/strong&gt; Use it to establish
conventions: naming (e.g. &lt;code&gt;btnOK&lt;/code&gt; not &lt;code&gt;Button1&lt;/code&gt;), handler structure, where
validation lives. Document those conventions and apply them consistently. The
first migration is always the hardest; later ones benefit from the patterns
you extract. Skipping the documentation step means each developer reinvents
the approach, and inconsistency makes maintenance harder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Expect a learning curve for the form designer.&lt;/strong&gt; TPW developers who had
never used a visual designer faced new concepts: alignment palettes, tab
order, anchor and alignment properties (in later Delphi versions), the
difference between selecting the form and selecting a control. Spending a few
hours on throwaway forms to learn alignment, anchoring, and the property
inspector paid off before tackling a real migration. Misunderstanding the
designer led to layout bugs that were hard to fix by hand-editing &lt;code&gt;.DFM&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;first-90-day-delphi-adoption-cadence&#34;&gt;First 90-day Delphi adoption cadence&lt;/h3&gt;
&lt;p&gt;Teams that transitioned cleanly usually followed a staged first-quarter plan,
not an all-at-once rewrite:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Days 1-30:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - pick one medium-complex form
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - define naming/event conventions
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - establish build options and debug baseline
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Days 31-60:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - migrate 3-5 related dialogs/forms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - extract shared non-UI logic into units
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - add regression checklist for core user flows
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Days 61-90:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - package reusable controls/components
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - document standard form lifecycle hooks
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - formalize release checklist and rollback criteria&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This cadence solved two chronic problems: premature abstraction and duplicated
mistakes. Premature abstraction happened when teams designed a full internal
&amp;ldquo;framework&amp;rdquo; before they had migrated enough screens to understand recurring
patterns. Duplicated mistakes happened when each developer migrated forms in
isolation with personal conventions. A short, staged cadence turned both into
manageable process work.&lt;/p&gt;
&lt;p&gt;A practical metric during this period was &amp;ldquo;time from UI change request to tested
build.&amp;rdquo; If that time dropped while defect rate stayed stable, Delphi adoption
was producing value. If the time dropped but defect rate climbed, the team was
moving too fast without enough shared conventions.&lt;/p&gt;
&lt;h2 id=&#34;summary-and-outlook&#34;&gt;Summary and outlook&lt;/h2&gt;
&lt;p&gt;The TPW-to-Delphi transition was more than a product upgrade; it was a paradigm
shift in how Windows UI was built: from imperative,
resource-centric Windows development to a visual, event-driven, component-based
model. VCL and the form designer changed how developers conceived of UI, and
the RAD mindset changed delivery expectations. Teams that understood both the
gains (faster iteration, clearer ownership, component reuse) and the pitfalls
(handle lifetime, string types, over-coupled forms) navigated the transition
successfully.&lt;/p&gt;
&lt;p&gt;Delphi&amp;rsquo;s influence extended beyond Borland. The component model, property
inspector, and form designer pattern appeared in other tools and languages.
The Object Pascal language evolved but remained recognizable to TPW
practitioners. For those tracing the Turbo Pascal toolchain into the Windows
era, Delphi is the natural continuation—and the RAD mindset it introduced
still shapes how many think about UI development today. The move from
&amp;ldquo;write code that creates UI&amp;rdquo; to &amp;ldquo;design UI and write code that responds&amp;rdquo;
has informed every major GUI framework since.&lt;/p&gt;
&lt;p&gt;The transition also illustrated a recurring tension in tool evolution: each
abstraction layer buys productivity at the cost of opacity. TPW developers
could read the SDK and understand every message; Delphi developers relied on
the VCL to do the right thing. When the abstraction leaked—handle lifetime,
recreate behavior, focus management—the ability to reason about the lower
level became valuable. The best Delphi practitioners kept that mental model intact. They knew when
to use &lt;code&gt;Sender&lt;/code&gt; in an event to identify the originating control, when to
override &lt;code&gt;WndProc&lt;/code&gt; versus using &lt;code&gt;OnMessage&lt;/code&gt;, and how to trace from a visible
bug back through the message or event chain. That knowledge, built during the
TPW-to-Delphi transition, remained valuable for as long as Windows and the VCL
evolved together.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;related-reading&#34;&gt;Related reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-6-object-pascal-tpwin-and-the-windows-transition/&#34;&gt;Turbo Pascal Toolchain, Part 6: Object Pascal, TPW, and the Windows Transition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-units-as-architecture/&#34;&gt;Turbo Pascal Units as Architecture, Not Just Reuse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
