A Survival Guide to Migrating Applications to Delphi 2.0

Illés Péter (Peter Illes)
 

Delphi 2.0 is the most powerful RAD tool available today. The optimizing compiler produces first class Win32 code. The support for OLE Automation, the even faster scaleable database technology, native support for OLE controls (OCXs), the complete support for the Windows 95 user interface, and advanced features like multi-threading and MAPI, make it the choice of platform for many new and improved applications. Borland did a lot to assure that most of the Delphi 1.0 code simply recompiles. But  mainly due to the differences between Win16 and Win32  it takes a little more than just recompiling to port your non-trivial ("legacy") code to

this new platform. And even some sorcery to continue supporting Win16, and maybe MS-DOS for a while (one or two years) with the same code base. In this article we will examine this porting process, pointing out pitfalls that should be avoided and giving guidance on finding the fastest, least painful way to your next release.

Introduction

Delphi was a magnificent success in 1995 due to its combination of an established, easy to learn yet flexible object-oriented programming language (Object Pascal), a world-class Rapid Application Development environment, and its extensive support for Client/Server and database development. Meanwhile we have witnessed the dawn of the Win32 age by Microsoft's introduction of Windows 95. The successor of Windows 3.x is actually the third Win32 platform, but its predecessors (Win32s and Windows NT) could not achieve the critical mass that is necessary to transform the whole industry. The situation right now closely resembles the dawning of the Windows era. Many members of the industry (analysts, journalists, managers, and developers

alike) were skeptical about Windows 3.0 and not willing to jump on the bandwagon. And in a year or so, most MS-DOS based applications became obsolete, and many companies lost their leadership positions because they didn't have a Windows version of their most popular apps. Now the industry is transforming again and it is time to start working on the Win32 version of our applications. That's why it is so important for us that Borland is introducing the second generation of its RAD product line, Delphi 2.0  the most powerful Win32 RAD tool available today.
 

Many of us have managed transformations like this before (at least from DOS to Windows) and still others will face this challenge the first time. To be successful in the porting process one should go on in an orderly fashion. We'll introduce an organized approach that will result in early success with a robust architecture for future refinements. In order to do so we'll define the starting points, target platforms, porting goals, and stages of porting in Part I. In Part II we're getting into details of general Win16 to Win32, and specific Borland Pascal

and Delphi 1.x to Delphi 2.0 conversion issues. We will also visit critical Win32 concepts (processes, threads, flat memory, etc.) briefly in this part. We won't delve into some areas of porting (notably database issues) and won't touch stage 2 of the porting process and beyond (see Part I for an explanation of stages) in this article due to space constraints. For an extended explanation see our whitepaper, or other references included at the end of the article.

You should be aware of the fact that most porting issues discussed here are rooted in the changes introduced by Win32. We could treat them in an abstract sense (independent of a development platform) but decided to show all issues with a Delphi perspective - so you can put them into practice right away.
 

Let's introduce some notations before we go on. DOS stands for any incarnation of the old Disk Operating System used on IBM PC compatibles (MS-DOS, PC-DOS, DR-DOS, etc.). Win16 stands for Windows 3.0, 3.1x, and Windows for Workgroups 3.11. Win32 should denote the new 32 bit API. WinNT stands for Windows NT 3.51 or later and Win95 means Windows 95 build 950 (the August release) or later.

Now, let's start our quest.
 

Part I: Managing the Porting Process

Where are You Coming From?
 

Thanks to Borland's 13+ years of success with Pascal, you may come from different environments. Here are the most typical scenarios:

    Real mode or Protected mode DOS  Turbo Pascal or Borland Pascal. Yes, there are still some of us out there who will make the Transition now. You'll have the most work to do, but you may be at an advantage as you don't have to learn the internals of Win16 and you can start exploiting the power of Win32 right away. You don't have to face the steep learning curve generally associated with Windows programming due to the fact that you are going to use a RAD tool that will hide most of these details. Many very useful applications can be created without digging deep into the Windows API. Most of your code may have to be re-written, but you can save the engine of your apps, and even your user interface architecture, if you were using an event-based approach (e.g. Turbo Vision). We were able to port a DOS (Protected mode) app's core with a functioning user interface (it was a CAD type app!) without architectural alterations in a month... You'll have to update your object oriented programming knowledge since it is a norm in Delphi, but you can get along easily with the Delphi manuals. You also need some good books on Win32 programming. Don't forget to study and implement user interface design principles, as your UI will be a major factor in your success or failure in this user-centric world. And Chapters 19, 20, and 21 (among the others) of Delphi Developer's Guide from Xavier Pacheco and Steve Teixeira is a must-read for you  it will have some detail on moving from DOS to Delphi that we cannot cover here due to space limitations. See the References section for sources of information.
 

    Windows API or Windows OWL  Turbo Pascal or Borland Pascal for Windows. You have an established understanding of the Windows architecture, and the Win16 APIs, so you can focus on the differences between Win16 and Win32. With smart approaches you can save most of your work. The core of another CAD product of ours was ported from BP7/OWL to Delphi 2.0 in 2 weeks (if we omit the concept development of porting, doing the RTL70/Objects.Pas port, etc.  things that you don't have to repeat). Of course we have since spent a lot of time fine-tuning and re-implementing portions that can be done better now. But it did work. You may also find the Delphi Developer's Guide a valuable tool.
 

    Windows VCL  Delphi 1.x. According to Borland, you have to do the following: Use File|Open to open your project and Project|Compile to have a 32 bit version. Period. Now the interesting thing is that it's basically true. If you have kept with Delphi 1.x without reverting to the Win16 API, assembly and inline programming, depending too much on the size of variables, etc. you will get a Win32 app right away (except some tiny changes). And if you did utilize features like that, you're still far ahead of the rest of us in getting into the 32 bit world  most of your code is Win32 ready. Borland did plan ahead in architecting Delphi 1.x so that the transition shouldn't be a big effort on your side (they have done most of the porting for you in Delphi 2.0 and the new VCL).
 

No matter where you are coming from, you can be assured: you have the easiest job getting into the Win32 world. Delphi 2.0 is a unique opportunity for you.

Where are You Going To, and How Far?

32 bit Windows? Which one? Win95 or Windows NT? Do you want to just have your application run on these platforms, or to take full advantage of their new features?

Where To? The 3 Versions of Win32

Win32 does not denote an environment or operating system, it denotes an Application Programming Interface (API) specification, implemented by at least three platforms:
 

    Win32s  the extension of Win16 with a minimal subset of the Win32 API. It is a set of DLLs and support files that can be freely distributed by software publishers to support their applications on Win16.

    Windows NT  the full implementation of the Win32 API on many types of hardware, including x86, Alpha, and MIPS processors. It also supports multiple processors, that is, it can run "threads" (the unit of execution in Win32 parlance) simultaneously. It is a completely new, robust design from the ground up, but it requires hardware that won't get into mainstream usage for some more time, so many developers won't target it specifically in the short run.
 

    Windows 95  the mainstream implementation of Win32 for x86 processors, with most of the original Win32 API functionality plus some additions that will be introduced in the next release of Windows NT, too.

Win32s is mostly considered obsolete now with the release of Windows 95. Its limitations are so numerous that it's best to forget it completely as a possible platform. Also, Delphi 2.0 does not support Win32s development: the RTL would require adjustments. So you should decide between Windows NT and Windows 95. Fortunately, in most cases this means that you don't have to decide at all, since you can have the same binary code running on both platforms (on x86 architectures). Windows 95 is the mainstream Win32 implementation, so if you develop consumer-type or (hardware) cost-sensitive applications, you should target this platform. If you are developing heavy duty applications, like high-end CAD, (client/)server databases, Internet server apps, etc., you should choose Windows NT for its robustness and power.
 

Another question is: we have to hurry with the porting project, but how much? By the time of writing, there are only some hundred apps with the Windows 95 logo, and only some thousand apps that are for Win32 specifically. And there are tens of thousands of apps that are still for Windows 3.x. There are 10 million copies of Windows 95 sold, whilst there are 50+ million copies of Windows 3.x out there. But you should not be confused by these numbers. The major players no longer develop apps for Win16. 1996 will be the year of Win32 and you should act right now.
 

A final word on platform selection: for some time you may have to support your Windows 3.x users (given the numbers above). That means that you will have to manage to keep either a common code base for both Win16 and Win32, or even worse, keep in sync two code bases. We will return to this question later.

How Far? Three Stages of Porting

There are three well separated stages of porting to any platform. In our particular case these are the following:

    Stage 1: Run on the platform. This means that you can successfully compile you program with Delphi 2.0 and it runs as expected. The rest of this article will mostly deal with this problem.

    Stage 2: Behave as a native application on the platform. In our case this means Windows 95 logo compliance, including: OLE support, Registry usage, using the right mouse button for context menus, etc.
    Stage 3: Exploit the platform to achieve the best possible performance. This may include multithreading, overlapped I/O, Unicode, memory usage optimizations, etc.

Of course, you don't have to precisely follow these stages. Go whichever way your application's needs lead you. This may mean that you re-design the data structures of your application at once to take advantage of the flat memory model, though this activity belongs to stage 3. You most probably will start using the common dialogs at once (Delphi will does this for you), though this is a stage 2 process.
 

Key Elements of Successful Migration

Our experience has shown that both managers and developers have roughly the following considerations when porting applications (some of these considerations apply only if you are not coming from Delphi 1.x  if you are, you are in a much better position):

    Do it as fast as possible. This is an ever-lasting request from management. As competition is increasing in the industry, it is getting more-and-more important to be there before the competitors.
 

    Save as much code as possible. This is the only way to satisfy the above point. By smart decisions you can spare a lot of time and coding. Without porting the RTL70 core we should have had to re-implement most of our old apps. And with minimal trickery around the OWL dispatch mechanism we saved parts of even the user interface code (we did re-create the user interface in Delphi, but were able to save the dispatch mechanism and architecture).

    Have code that compiles as early as possible. This will please both management and the developers. You are dead if you have to wait 2 to 3 months (or more) for a complete rewrite before you can start compiling. Instead, compile with the necessary fixes in 3 or 4 days, then start building the skeleton anew in Delphi (while your whole app is still compiling!) and add the converted engines, dialogs, etc. step-by-step. You can compile - run - test - debug - compile-... whenever you want, to find design problems, etc. This approach will help you greatly. You will be able to split the conversion among many developers and test the new parts as soon as they are ready.
 

    Have backward compatibility with versions of apps on older platforms (e.g. DOS, Win16). You have DOS and Windows 3.x apps. How do you make bug fixes in two (or three) different code bases? Have one engine code for all the target platforms. Engines typically don't exploit the features of an environment. So find the least common denominator (this may mean Delphi 1.x features, as it can compile DOS code too) and use it in your code wherever possible.

    Become as fully compliant with the new environment as possible. You won't want to support OWL any longer, since Borland discontinued it for the Delphi product line. VCL is the future: Borland will continually add new features as they have demonstrated it with Delphi 2.0 (enhanced OLE, better database support, etc.). You'll definitely be in a better position to achieve a Stage 2 or 3 port (see the next section for an explanation

    of stages) with the support of the development environment. On the other hand you may be forced to use a mixed object model, as replacing old type objects with new ones is a difficult and error prone process if you have a more than trivial object architecture. Fortunately, Delphi 2.0 has a solid implementation of the old type objects.

We will have detailed discussions about achieving the first three points, and will touch the latter two to a certain extent.
 

Part II: Porting Issues
 

Overview

We recommend reading this part through and then formulating a porting checklist (plan) that may look like this (assuming that you first want a released Win32 app, then want the Win95 logo; there are things that do not apply depending on where you are coming from):

    Make a backup copy of your code!  Get the code to compile (in this phase comment out difficult to port code like assembly, inline, and platform specific parts  create stubs) - in this phase you should use a tool

    like DelphiPort™ Expert to lighten your load.

    Fix Windows API changes
    Eliminate obsolete Delphi/BP7 routines
    Replace Objects.Pas and the OWL units (DOS/OWL)
    Get the code to run (fix errors in the main portion of your app) - DelphiPort™ Expert will also help here to pinpoint some issues that may cause problems.  Re-create the main window in Delphi (DOS/OWL)  Fix 16 bit pointer manipulations (Seg, Ofs, etc.)
        32 bit data structure fixes (casting with Word instead of THandle, etc.)

        String and integer issues
    Assure basic operation & fix release.
        Re-implement commented-out code
        Long filenames
        File compatibility (packed records, integers, etc.)
        Fix architectural changes (e.g. data sharing between apps, asynchronous input model)
        Re-build and fine-tune the whole user interface in Delphi
        Turn on Hints & Warnings in Delphi and check all messages: many will lead to long
        lurking bugs, many to porting problems

    Add new features
    Get the Win95 logo
    Fine-tune for Win32

During conversion it's a good idea to put in some informative comments. Some of my favorites:

    {!PORT!}  the commented out part must be ported later
    {!FIX!}  the code will have to be fixed later (temporary solution)
    {!TEST!}  this part should be thoroughly tested
    {!FYI!:...}  for your information; the comment explains decisions that will limit
    performance, usability, or other characteristics (handy to explain that the

    implementation is limited in Win16 only and will do magic under Win32)
    {D!HACK}  dirty hack; something that has a good chance to stay in the code for quite
    some cycles, but is against all norms... (a confessed sin)

Use the WIN32 symbol in conditional compilation directives to create Win32 branches. WINDOWS is not defined in Delphi 2.0 by default, so you can use it to identify Win16 code. Do something like this:

{$IFDEF Win32}
  Win32 code comes here ...

{$ELSE}
{$IFDEF Windows}
  Win16 code comes here ...
{$ELSE}
{$IFDEF DPMI}
  DOS protected mode code comes here ...
{$ELSE}
  DOS real mode code comes here ...
{$ENDIF}
{$ENDIF}
{$ENDIF}

The following sections contain background information, tips, and samples to achieve the above steps as easily as possible.

    This symbol will indicate issues that are specific to Delphi 2.0. The rest is true for all development platforms.

Top Issues

In this section we'll overview the most important issues. These issues are detailed in later sections (with many others). As you will see most of these issues are caused by the changes introduced with Win32 and are not a specialty of Delphi 2.0. Actually, Borland did hide most of the changes, so porting VCL code is really easy. Here we are:
 

    Memory segmentation is gone. You can create data structures of any size, but you will have to fix code that used to manipulate huge data structures (larger than 64 kB). In other cases, memory architecture changes won't cause much headache for you as they are carefully and cleverly wrapped in the familiar RTL functions.

    Many 16-bit types grew to 32 bits, including integers, handles, graphics coordinates, and wParam. This may cause file incompatibility problems and type conversion bugs.
 

    Many Windows API functions have changed or have been replaced by others. Use a utility that finds them all and tells you what to do.

    The new asynchronous input model of Win32 may cause problems to your old mouse capturing logic.

    Long filenames are easier to implement than you would expect. Just make sure you're using the new long String type and the RTL filename manipulation routines. The new common dialogs will be used by VCL automatically.

    You will have to fix your code that does data sharing between applications and already-running-instance determination, as these aspects of the Win32 API have changed drastically.
 

        The new long String type will not cause much problem if you look out for some bits discussed later. And yes, you are no longer limited to 255 characters in a string!

    DLL initialization and finalization has changed in Win32. You will also have to look out for your calling conventions.

        OWL is gone, but it's easy to replace with VCL. You may save your old code that uses the old RTL70 streams and collections with third-party implementations.

        Assembly code should be fixed, or rather eliminated, as Delphi's new optimizing compiler produces world-class code. Inline code must be re-implemented as it is gone.
 

        There are some minimal changes in the VCL and RTL that will need fixes.  VBXes will have to be replaced with OCXes and 16 bit third-party DCUs/DLLs must be upgraded to their 32 bit versions. VBXes are gone since Microsoft does not support them in Win32.

Windows Architectural Issues

Processes and Threads

The term process identifies an instance of a running program. It owns a 4 GByte address space and certain other resources such as threads, that are the atomic unit of execution. The process itself is not running, it is static. Your application starts execution because the system creates the primary thread after it successfully loaded your app (the process). This thread will enter at the Begin statement of your program. One process may create and own more than one thread. These threads are scheduled to execute by the operating system. Under WinNT, if you have more than one CPU in your machine, threads (as many as the number of CPUs) can

execute simultaneously. In other cases (fewer CPUs than threads, one CPU, or under Win95) the threads are scheduled preemptively. The scheduling is based on thread priority (this can be set by API calls; the system changes it dynamically to guarantee the execution of low priority processes). When the last thread (not necessarily the primary thread) stops, the process is terminated by the operating system. At this point all resources owned by the process will be automatically freed.
 

When you start using multiple threads in your Delphi application, you will want to synchronize them, otherwise corruption of data will/may occur. The use of will/may is intentional: in theory it is only 'may', but it will generally lead to very difficult-to-figure-out bugs (non-deterministic bugs that depend on the will of the thread scheduler of the OS to show up or not), so it's better to imply that if you have multiple threads without proper synchronization, corruption will occur (the question is not whether it will, but when and how: not in all cases, and not the same way!).
 

Memory and Pointers

In Win32 each application has a separate 4 Gigabyte linear virtual address space. Linear means that you can address the whole addressable range with a 32 bit offset between 0 and 4,294,967,295 (232-1) without worrying about crossing 64 kB segment boundaries. Segments are gone. This also means, that you are no longer limited to 64k on a single data structure: you can easily create multi-megabyte structures and manipulate them with one pointer, without hacking with AHIncr, Seg, Ofs, DSeg, CSeg, SSeg, SPtr, and the like. You will have to review your code and rewrite all routines that made use of 16 bit pointer operations like the ones above. Your new code will be much simpler.  Since few systems have 4 GB RAM (for each process!) installed, the operating system is using address mapping to translate a subset of each process's virtual addresses into physical storage. That is, only portions of the whole 4 GB address space are stored in physical storage

at any given time. The system (generally with CPU hardware support) uses private lookup tables to convert virtual addresses to physical memory addresses. This means that there are 'holes' in the address space of your application  areas that are not mapped to physical storage. One advantage of mapping is that the memory manager can easily relocate code or data in the physical memory without the need to modify the code of any application (it only updates its tables). It is also possible to optimize physical storage usage this way: for example the same copy of the operating system will be mapped to each process's address space (see Figure 1

later).

The term physical storage was used intentionally in the above paragraph instead of RAM. In order to run multiple and demanding applications, modern operating systems use page swapping to simulate RAM with hard disk swapfiles. When the CPU is to access code or data that is not in the system RAM, then the OS will bring it in from the swapfile, making room for it by pushing out pages of RAM that are currently not in use. So the total physical storage available consists of the RAM installed in your system (this is fixed) and the size of the

hard disk swapfile(s), that can vary dynamically depending on how much free space you have on your hard drive and as the load of the system dictates. For example: although you have only 16 MB of RAM, your system may have 100 MB of physical storage at a given time (the remainig 84 MB comes from the hard disk swapfile). Of course, if you have more RAM you will have better performance, as the system will have to turn to the hard disk swapfiles less frequently to re-load portions of the physical storage (a very time consuming operation).
 

The concept of virtual memory has already been used in Win16 (in enhanced mode). What's different is that in Win16 all programs shared the same view of the memory, while in Win32 each process has its own private 4 GB address space. This is a major plus for robustness: applications cannot access each others' data structures, thus cannot corrupt them. The problem is that you cannot share data between apps the old way by passing pointers, since one process's pointer has absolutely and definitely no meaning in another process's address space (it may point to a different data structure, or nowhere at all).
 

The 4 GB address space contains your code, data, stack, heap, all DLLs, resources, and the operating system itself. Again, you don't have separate segments. The address space is partitioned as follows:
 
 

Figure 1 - Memory Layout

Under WinNT the upper 2 GB is reserved for the OS. It is inaccessible (and generates an access violation), thus the system is protected from tampering with. Under Win95 the top 1 GB contains the OS core and (unfortunately) is not protected, so a "badly behaved" application can corrupt the OS data. The 1 GB above $8000,0000 (i.e. the third GB) is shared among applications under Win95 (as is the OS core). The lower half of the address space is the process's private area. Some sections are still reserved. Under WinNT there are two 64 kB

guard blocks at the top and the bottom. Under Win95 the lowest 4 MB is reserved for MS-DOS and Win16 compatibility (don't touch it), out of which the lowest 4 kB is a NIL pointer guard block. The guard blocks will generate access violations (EAccessViolation exception) if accessed: this comes in handy to catch NIL or invalid pointers. Under WinNT, memory mapped files and Win32 DLLs (kernel32, user32, etc.) will be mapped into the lower 2 GB.

Keep in mind that the system is more reluctant to identify "wandering" pointers (in the same process) than it was in Win16 (or 16 bit DPMI), because there crossing the segment limits caused General Protection Faults (Runtime Error 216  this helped to find quite some bugs). There is no such thing under Win32 (it is possible to protect pages (4 kB blocks in most implementations) of memory, though). You will get access violations (EAccessViolation) whenever you access an area marked as protected (see the discussion above) or an area that is not assigned (mapped) to physical storage at all. You should be even more careful to make sure

that each pointer stays within its intended bounds.

The notations "global" and "local" memory no longer apply. All memory is equal and is in your 4 GB address space. The GlobalXX and LocalXX Windows APIs are kept for compatibility, but they work on the same memory.

In Win32 (if you are working with the Windows API), to use portions of the 4 GB address space, you must first reserve a region of memory, and then commit physical storage to it (both can be done with the VirtualAlloc function  even in one step). Reserving a region means that you tell the system to set apart a specific portion of your address space for you (you can even tell where this area should be). This is a fast operation, as there is no physical storage assignment. Then, when you want to use this region, you commit physical storage to it (or to a

portion of it  you can commit storage in steps). You can reserve and commit memory in pages (4 kB in most implementations).

    The Delphi memory manager is still doing the sub-allocation scheme for optimum performance (to allow you to allocate smaller chunks of memory than a page and to speed up things by not going to the OS with every request), but it has been refined for the Win32 environment. It is smart enough to unify memory management of different modules in the same process (if you use the ShareMem unit). This means that your application and your Delphi DLLs will share the same memory manager so it won't cause a problem if one module (e.g. a DLL)

frees a data structure on the heap that was owned (allocated) by another module (this is important, as the new long String type is automatically managed on the heap).You can generally use the Delphi routines since they can handle any size data. You may want to revert to the Win32 API to achieve special effects, like sparse matrices (hold a 500 MB matrix in actually 4 kB...).

Under Win16, memory allocation routines returned pointers with zero offsets. This is no longer the case, so code that depends on this behavior has to be revised.
 

Another important issue is structure alignment. Delphi 2.0 will align data members of structures (records, objects, etc.) at addresses that are "natural" for the type of the data member by default (you can turn off this behavior, but a slight loss of performance will occur). It is done to improve performance with current processor technology. Aligned structures will generally be larger than their non-aligned counterparts. This may break code
that depends on the fact that data members are packed. For an example, look at the following fragment:
 

Type
  T1 = Record
    b: Byte;
    w: Word;
  End;

  T2 = Record
    b: Byte;
    li: LongInt;
  End;

Under Win16, SizeOf(T1) would be 3 and SizeOf(T2) would be 5. Under Win32, they are 4 (+1 byte after b to pad w to an even word boundary) and 8 (+3 bytes after b to pad li on an even dword boundary), respectively. So use the Packed keyword on all records where you depend on tightly aligned fields (e.g. old structures that you read in from a file), or even better, fix the code to cope with the change.
 

    As we have discussed, it is not trivial how much free physical storage is in your system (it is a function of the size of the system paging file that can change its size dynamically). That's why MemAvail and MaxAvail have been removed. You can use the new GetHeapStatus function instead.

In Win16 you had a limitation of 64 kB for the data segment, stack, and local heap together. In Win32 there is no such limit. Your static and dynamic data can be as large as the system will handle, and Delphi manages the stacks (one for each thread) so that they can grow to 1 MB each automatically. Welcome to 32 bit world!
 

Asynchronous Input Model

An important issue for many applications raises from Win32's departure from the synchronous input model of Win16. In Win16, there was a linear path of execution with cooperative multitasking, so message processing was synchronized. In Win32, the goal of robustness requires that one hung thread should not bring down the entire system, so each thread has its own message queue, and the messages in these queues are processed asynchronously. Operations like SetFocus(), SetActiveWindow(), or SetCapture(), work on per thread basis, i.e. you can only operate on those windows that are created by the current thread, so a malfunction in one

application does not hang other applications. Also, the GetXxx counterparts of the above functions can return NIL (you should check for it!) to indicate that the thread does not own the queried state (e.g. it does not have the input focus), even if a previous SetXxx call succeeded.

Mouse handling is even more complicated. The mouse shape is managed on a per thread basis, i.e. if you set the mouse to a given shape, then it is moved to a window of another thread, it will be set to the other thread's requirements, and when it is moved back over a window owned by your thread, it will be automatically reset to the shape you requested. Mouse capture is also different. SetCapture() guarantees a system-wide capturing of mouse events while a mouse button is down only. If no mouse button is down, your capture window will receive mouse events only while the mouse is over a window created by your thread (thread-local level). This

indicates that old code that relies on capturing mouse events while a mouse button is not down may be malfunctioning under Win32.

All in one, you should thoroughly test your mouse capture logic and other related issues. A detailed explanation of these problems can be found in Richter's Advanced Windows.

Windows General Issues

Windows API Changes

Win32 is designed so that porting from Win16 should not be too difficult. Still, there are many changes in the API. Here's a brief listing of these changes. To get into more details (with a listing of all affected functions and messages), consult "Porting 16-Bit Windows-Based Applications to Win32" by Randy Kath on the MSDN Development Library or check out the Win32 API Help on "Porting 16- bit Code to 32-bit Windows" (included with Delphi 2.0).
 

    Handles became 32 bit (THandle, etc.). You should check your code for places where you have used variables of type Word or typecasts to Word instead of the handle type, since this is no longer valid. The Get/SetWindowWord and Get/SetClassWord functions and GWW_xxx, GCW_xxx constants should be replaced with Get/SetWindowLong, Get/SetClassLong, GWL_xxx, and GCL_xxx for handle access. This change necessitated modifications to all messages that contained handles.

 The Windows API Boolean type is 32 bits now. Use BOOL (or LongBool) wherever this may be

    of concern.

    Graphics coordinates have also become 32 bit. In Win16 many API calls returned coordinates in the LoWord and HiWord of a LongInt return value. Now these functions take either a Var parameter of type TPoint or TSize (they have grown from 32 bits to 64 bits) or a pointer to such a parameter (if it is valid to pass NIL in order to ignore the returned coordinates) to return these coordinates, and have a BOOL return value that indicates success or failure. These functions have been renamed generally by appending 'Ex' to the end of the original function name (e.g. MoveTo became MoveToEx). Please note, that though the storage space is 32 bits on all Win32 platforms, current versions of Win32s and Win95 use only the lower 16 bits. This may lead to problems with negative coordinates on these systems (for example, coordinate 65535 on WinNT will show up as coordinate -1 on Win95!). See Lou Grinzo's Zen of Windows 95 Programming for many hard-to-figure-out details like this.
 

    On the other hand, mouse screen positions (in messages) remained 16 bit integers (SmallInt). So you will have to use the TSmallPoint type, and the SmallPointToPoint and PointToSmallPoint functions to convert between the two types. If you are not using Delphi's event handlers for such messages (that will crack the message for you) you should be aware of a possible pitfall:

    Integer( LoWord(lParam) ) {this will not work correctly - BAD!!!}
    Integer( SmallInt( LoWord(lParam) ) ) {this is OK}
 

    The first version will loose the sign of the coordinate (it can be negative) because integers are now 32 bits and can represent values above 32767. The second version works fine on both Delphi 1.x and 2.0.

    wParam became 32 bit, so check your message handlers for declarations of wParam as Word, and replace it with WPARAM (like this: wParam: WPARAM  it will work!). Many messages had to be rearranged to accommodate the growth of handles to 32 bits. Now the handle itself occupies lParam, so the other 16 bit part of the lParam had to move to the HiWord of wParam. You should check all your WM_ message handlers, especially WM_COMMAND. This is not the case if you were using event handlers (like OnMouseMove) or message handlers with the Borland-defined message types (e.g. TWMCommand) as these crack the messages properly. There are other messages that have been re-arranged, like EM_GETSEL.
 

    The WM_CTLCOLOR message has been replaced with a collection of messages (WM_CTLCOLORBTN, WM_CTLCOLORDLG, WM_CTLCOLOREDIT, etc.) since it had two handles, and each growing to 32 bits, there was no space left for the control type.

    Many functions became obsolete and have been removed either because they were MS-DOS or hardware specific (e.g. GlobalDOSAlloc, AllocSelector). These changes were done to facilitate the portability of the Win32 API. Some other functions have been removed due to the architectural changes form Win16 to Win32 (e.g. GetModuleUsage).
 

    Some functions have been modified to make them more robust (e.g. DlgDirSelect).

    There are functions that exist for compatibility only. These functions are no-ops (do nothing). You may remove them to make the code cleaner or leave them in your code to preserve compatibility with Win16. Such a function is MakeProcInstance that will simply return the value of its input parameter (there is no need for instance thunks in Win32: all functions can be called directly).
 

    Communications functions have been completely changed, with a standard file-based API in Win32. Now you use CreateFile with a filename of 'COM1' to open a com port, ReadFile and WriteFile to handle I/O, and CloseHandle to close it.

    Most of the (low level) sound API has been discarded except the multimedia PlaySound function. You will have to rewrite your code to use wave files or resources with PlaySound.

    The Dos3Call function has been removed. It was used (in Asm blocks) for calling MS-DOS Int 21h advanced file I/O operations. Win32 introduces a more robust, platform independent file I/O API. This may affect your code, though many affected calls were already implemented since BP7 in the Pascal RTL and they are updated for Win32.
 

    If your program did its own customized icon painting (e.g. in response of the WM_PAINTICON message), then you should remove any such code, as Win95 does not support icon painting: the Taskbar displays 16x16 static icons only.

Long Filenames and File Compatibility

An important issue under Win32 is the change in file naming. The user is no longer restricted to the old 8.3 format (8 characters name, 3 characters extension), rather he/she can create filenames (and directory names) up to 255 characters long. There may be multiple periods ('.') in a filename (along with characters like '~!@#$%^&()_+={}[];'). This means that you will have to parse for the last period instead of relying on the 8.3 scheme or looking for the first period. Even better, use standard functions to parse your paths (ExtractFileName , ExtractFileExt, ChangeFileExt, etc.). When comparing filenames, accommodate for the fact that they preserve case from now on (but the OS is case-insensitive). The whole path never can be longer than MAX_PATH (that is 260 characters in current implementations of Win95 and WinNT). Another limitation is that the registry can record associations with 3 character file extensions only.
 

Most of the general Win32 books will tell you to allocate space for MAX_PATH characters to support long filenames. The problem with this solution is that in next generation operating systems the maximum length may change. Another solution is to use GetVolumeInformation to check the maximum length at runtime (Win32 supports multiple file systems concurrently, so it is possible, that your drive A is FAT, drive C is VFAT (Win95 long filename-enabled FAT), drive D is HPFS (from OS/2), and drive E is NTFS (WinNT 'native' file system)  with different limitations on filenames...). Fortunately, you're in Delphi, so you can simply use the new

huge String type for filenames. It will accommodate any length. Please see the section on strings for possible pitfalls. You will still have to check for the maximum length when passing filenames to the Win32 API occasionally.

Most of the long filename issues are hidden from you if you are using the common dialogs. You will still have to review your code for String[13]s and other limitations and turn them into Strings.

Another issue is file compatibility, i.e. that you read in the files of the older (Win16, DOS)

versions of your applications. Here are some quick tips to get you started:

    Look out for types that have grown  for example, replace Integers with SmallInts in records you read in from binary files (you may alternatively modify the reading code to read the old size record with SmallInts, and then convert to the new size record with Integers).

        Compensate for the differences between old and new strings (read into an old string, and then assign the result to a new type string).
 

    Make all records you read in from a file packed to prevent compatibility problems caused by field alignment.

        You may need a port of the RTL70 TStream and TCollection (TOStream and TOCollection in our parlance) if you made use of them.  If you want bi-directional compatibility (the files that your new Win32 app writes must be read by your old apps), make sure to modify your new writing logic accordingly.

Be warned that replacing Integers with SmallInts and creating misaligned records with the packed keyword may have a slight performance penalty, so consider to hide these restrictions into your file i/o logic.
 

The procedures BlockRead and BlockWrite have changed to allow the read/write of blocks that are larger than 64 kB. The Count parameter and the optional Result parameter (number of records successfully read/written) are now Integer.

Sharing Data between Applications

In Win16 you were able to share data (including GDI objects) between applications through DLLs or by passing handles or pointers.

In Win32, DLLs are not shared, they are loaded into the private address space of each

application. You can't use global variables in a DLL that is used by two applications to pass
information between the applications.

You neither can share memory by allocating it with the GMEM_DDESHARE or GMEM_SHARE flags as the returned pointer has meaning only in the address space of the allocating application, it cannot be accessed by any other process. Passing GDI handles (of bitmaps, for example) is also an error, as these handles are valid in the creating process's context only.
 

To share information between processes, you should create a named memory mapped section (CreateFileMapping) in one app, and open that named memory map in the other app (OpenFileMapping). Note that you don't have to have a file, you can use these routines to create shared memory areas. Another method would be to create a DLL that has a shared data area (DATA SINGLE),  but Delphi 2.0 does not support this feature currently.

An alternate way of sharing data is to send the new WM_COPYDATA message to one of the other processes' windows. SendMessage will automatically copy the data of the message from the sending process's address space to the receiving process's address space. This message cannot be posted. The data being sent must not be modified (by another thread of the sending process) before SendMessage returns. This message also works between Win16 and Win32 applications.
 

Finding Other Instances of the Same Application

In Win16 applications were able to determine whether another copy of the application was already running by examining the hPrevInst variable (System unit) at startup. In Win32 this variable is always 0, because the instance handle of a process is the load address of the process, and it has no meaning outside this address space (thus it is very likely that many running applications will have the same hInstance). An alternative approach is to use FindWindow to determine whether any other copy of an application is running.
 

Another interesting difference between Win16 and Win32 is that in Win32 the instance handle and the module handle are interchangeable and have the same value (to be more precise, it is the module handle that is passed to the application in hInstance), while in Win16 they are quite different: the module handle was an 'index' into the module database and had the same value for each copy of a given EXE or DLL, while the instance handle was indeed the task's default data segment and as such had a unique value for each copy of an EXE or DLL.
 

DLLs

Existing Win16 DLLs won't work readily with Win32 applications. To use existing 16 bit code, you should use thunking or an IPC (inter-process communication) scheme like DDE or OLE. All these solutions are rather complicated so it is advisable to convert the 16 bit code to 32 bits right away. For thunking you will need to use the Microsoft thunking compiler and DLLs (in the Win32 SDK).

If you port your Win16 DLL to a Win32 DLL, use the following checklist:
 

    The Win32 DLL entry point (the Begin...End. part of the DLL) is called with every process attach and detach while in Win16 it is called only once.
    You should make your DLL "thread safe" as it can be called from multiple threads preemptively or functions can be reentered that may corrupt data without synchronization of access to critical resources.
    You cannot share data between apps with DLLs the old way, as each Win32 process gets its own copy of the Win32 DLL's data.

    The export directive is no longer necessary because of the flat memory model of Win32.  Put the ShareMem unit first into your Uses clause both in your DLL and in your application if you want to pass new long strings between the two.

Now the details.

    To handle the DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH, and DLL_PROCESS_DETACH events in a Delphi32 DLL, you have to do something like this:

Library SkeletalDLL;

Uses
  Windows;

Procedure DLLEntryPoint( dwReason: DWord );

Begin
  Case dwReason Of
    DLL_PROCESS_ATTACH: { Handle process attach.
                          This occurs for each process that loads the DLL.
                          Put initialization code here. } ;
    DLL_THREAD_ATTACH:  { Handle thread attach.
                          This occurs for each newly created thread in a
                          process that has already loaded the DLL. It won't be sent
                          for threads that existed before the DLL was loaded. } ;

    DLL_THREAD_DETACH:  { Handle thread detach.
                          This occurs for each thread that exits in a
                          process that has already loaded the DLL. } ;
    DLL_PROCESS_DETACH: { Handle process detach.
                          This occurs for each process that cleanly unloads the DLL.
                          Put your old ExitProc code here. } ;
  End;
End;

Begin { _InitDll calls DllProc if it is not NIL, that is, except the first time. }

  If DllProc = NIL Then Begin
    DllProc := @DLLEntryPoint;
    DLLEntryPoint(DLL_PROCESS_ATTACH);
  End;
End.

The Begin statement calls an internal routine called _InitDLL in SYSTEM.PAS. _InitDll sets IsLibrary to True so you can easily test if you are in a DLL (no more PrefixSeg <> 0). DllProc is a NIL pointer by default.

Under Win16, with cooperative multitasking, there were well-defined points in your application where it could lose the attention of the processor, so you did not have to care about preemption and reentrancy, they didn't happen. Under Win32 applications can be multithreaded. This means that you should build your DLL as multithreaded to support preemption and reentrancy. The runtime library (RTL) of Delphi 2.0 is thread-safe, so the open question remains to guard global data- structures against corruption. A possible scenario of corruption

can be the following: Thread A starts modifying a global record that includes an index value (integer) and a corresponding string. First it writes the index value. Then Thread A is preempted, and Thread B is scheduled by the operating system (there is no guarantee where your code will be preempted!). Thread B's task is to copy the global data structure to another place. It reads the index, and then reads the string, but that string does not belong to the index, as Thread A was preempted before it could write the string. So Thread B has a corrupted

data structure. The solution for such problems is to use synchronization objects like mutexes and critical sections. Your threads will also be preempted by threads of other processes. This leads to the same preemption and reentrancy problems as above, if you do data sharing among the processes.

The "DS != SS" issues common to 16-bit DLLs no longer apply. A Win32 DLL is mapped into the linear address space of each process that attaches to it (loads it) and there is no segmentation of this address space. All DLL functions and procedures are called using the calling thread's stack and all pointers are 32-bit linear addresses.
 

Delphi 2.0 currently does not support shared data segments in DLLs.

    You should import DLL functions by name under Win32 as opposed to importing by index, since ordinals may change from version to version. Exported names are case sensitive (spell them correctly in import libraries and with GetProcAddress). You must supply the extension when you statically import to Delphi 2.0 (you will get along without supplying the extension as far as you run your 32 bit app under Win95, but WinNT will not find your DLL if you don't supply the extension), and you must not in Delphi 1.x. This is a Win16 loader bug. It appends .DLL to a static import module name. To prevent $IFDEFs on each line, put the DLL name in a string constant like this:
 

Const
  DllName = {$IFDEF Win32} 'MyDll.DLL' {$ELSE} 'MyDll' {$ENDIF} ;

Keep in mind that though it's absolutely legal to pass a pointer to a DLL, this pointer has meaning only in the address space of the calling process, so the DLL should not pass it off to a second application (e.g. with a SendMessage).

Keep in sync your calling convention declarations in your DLL and in its import unit. The standard calling convention for Win32 DLLs is StdCall, and you have to specify this in your program when you declare and import the DLL function. Failing to synchronize calling conventions will cause access violations.
 

The address of a function imported statically will differ from the address of the same function if imported dynamically (with GetProcAddress), because static imports go through a jump table which makes loading executable code a lot faster in 32 bit apps than the rewrite-the-code-segments 16 bit process.

The startup time of an application and overall performance can be boosted by assuring that all DLLs have different load addresses (this will make the relocation of code unnecessary).