Sunday, September 20, 2015

What Every Delphi Developer Should Know About Writing for Windows and Mac

I have always wanted a Mac version of my software. So, in September, 2011 when Delphi XE2 was launched  I was excited to finally get my chance to create software for both Windows and Mac. Has it happened. Nope not yet.

When it comes to software development I'm a slow starter. Perhaps I think too much. Perhaps I try and anticipate all the possibilities. Perhaps I'm just afraid of making huge mistakes, going down the wrong road and having to rip it all up only to start all over again.

Well, it's happening again, the slow start. However, this time it's not because I'm thinking too much it's because I've been thinking wrong. I have programmed my brain to think in terms of Windows. I need to unlearn how to think like a Windows developer and re-think like a cross-platform developer.

Fortunately there some great resources out there. I received permission from the editor of Blaise Pascal Magazine to reprint the following article in it's entirety.

Semper Fi,
Gunny Mike

Reprinted with permission from the editor of Blaise Pascal Magazine
http://www.blaisepascal.eu/
Issue 23 / March 2012

Writing Cross-Platform Code 

By Alexander Alexeev

How can you write Pascal code that is truly crossplatform? This article explains general techniques and best practice in writing code that will run on several platforms natively (that is, without use of a virtual machine or a web-based interface).

A few phrases may need clarification. By FMX I mean the FireMonkey framework in Delphi XE2 or later. The host system means both the hardware platform and operating system which is running your programming environment and compiler. The target system/platform means the hardware platform and operating system for which you are creating an executable program. The simplest case is where the host system and the target platform are identical (but of course that is not crossplatform).

What is cross-platform code?

Cross-platform software is “software that works on more than one hardware platform and / or operating system” - Wikipedia.

Writing successful cross-platform code requires that you consider three aspects:

  • The Compiler
  • The API / framework
  • The User Interface

The Compiler

Firstly, to create code that works on several platforms, you'll certainly need a compiler that can generate code for those platforms. There are two possible scenarios in compiling code for a specific target platform: either the compiler can run on the same platform or it can run on a different one. Examples of the first case would be Kylix and Free Pascal – both of these compilers can ru non Linux and compile for Linux. Examples of the second case are Delphi XE2 and (again) Free Pascal – both can run on Windows,while compiling for MacOS.

The API and/or framework

You need to consider more than the mere capabilities of your compiler. For cross-platform code it's very important how you rcode will interact with the environment in which it runs. Firstly,does it use the system API? For example, if your program uses the Windows API, it will run on any (hardware) platform, where Windows itself works. You may need to recompile the program for this platform (without needing to change the code). However, it will notwork under Linux, say. Conversely, if you use Linux function calls,then your program will run on any hardware platform running Linux (again, after recompilation), but it will not work under Windows.

An alternative to having your code make direct system is to use a special layer: a framework library or run-time library (RTL).The idea is that you make function calls to this auxiliary library,but do not call the operating system directly. The library translates your requests into calls to specific operating system functions.Then your code will run on any system and hardware platform supported by the compiler and the libraries it uses.

For example:

  • VCL supports Win16 (Delphi 1 only), Win32, and Win64 (only Delphi XE2+).
  • CLX (Kylix 1-3, Delphi 6-7) supports Win32 and Linux.
  • FMX (only Delphi XE2+) supports Win32, Win64, MacOS, and iOS.

This means that if you write your code using only the VCL, it will run on Win32 and Win64 (and in the case of Delphi 1 on Win16), but not on Linux, MacOS or iOS.

Also note that these libraries have different capabilities. For example, Qt (on which CLX is based) is a broad framework. That is, it contains functions for many different purposes: the interface, printing, file system, IPC, and so on.

On the other hand, the VCL includes support only for the user interface (not for IPC and so on).

The User Interface

Because Delphi has traditionally been a rapid development environment for graphical user interfaces, the user interface is the focus for most libraries and frameworks in Delphi.

Of course, if you choose a cross-platform library that supports multiple platforms, then you can create a user interface on each platform with the same code.

The only problem here is that the standard controls on different platforms look and behave differently. And if you replace them with generic controls provided by your cross-platform library – then both visually and behaviour-wise they will differ from the standard operating system controls. Your application will look "foreign" and "alien".

A hybrid approach is possible, in which you use a universal crossplatform library, but the library itself creates native controls for each of its platforms, instead of using 'universal' controls.

This has the advantages that your application now has a native look. The disadvantage is in the consequent complexity of development and testing, since you must take into account all the differences between the various target platforms as you develop and test.

The VCL is an example of a library which is wired to its platform. FMX is an example of library with universal controls. Examples of libraries with adaptable controls are CLX (Kylix) and the LCL (Lazarus).

Moreover, differences in the positioning and behaviour of controls between platforms is just the tip of the iceberg. The whole concept of “building an interface” may differ between various platforms. A simple example would be the order of the [OK], [Cancel] and [Help] buttons in dialogs.

Windows and MacOS each uses the reverse of the other's placement order. MacOS programs share a system-wide menu. On Windows this is simply not available. One platform can be focussed on mouse and keyboard interaction, another on gesture and touch. And a UI for a 21-inch monitor is substantially different from one for a PDA with a 640x400 pixel resolution.

Conclusions

You can conclude that the term "cross-platform" has, in a sense, two almost opposite meanings. On the one hand, cross-platform code is universal code which once written, will compile on multiple platforms. On the other hand, cross-platform code is code that takes into account the specific peculiarities of each platform.

The first approach (the so-called WOCA - Write Once, Compile Anywhere approach) gives us:

  • Diminished importance of the individual platform. Once code is written it is suitable for any compiler-supported platform
  • Limitation to generic solutions. You cannot use a specific feature (read: a strong point) of a particular platform 
  • A user interface that appears to be "universal" and "foreign" 
  • A complex implementation – it is both difficult and expensive to develop; and you are restricted to using only generic methods, and cannot make specialized calls

The second, platform-sensitive approach yields these features:

  • You cannot build your executable for the new platform until you have adapted your code for it 
  • You can make full use of any specific platform's features and advantages 
  • Your user interface will look native on each platform 
  • A complex implementation:
    – it is both difficult and expensive to develop;
    and you need to write individual code for each platform 

These are the extremes of the two opposing approaches, with different development costs and different results. Usually, realworld cross-platform applications combine both approaches in varying degrees.

When do you need cross-platform code?

Cross-platform code gives you the opportunity to distribute your application to new markets: to other hardware platforms and operating systems. The answer to how useful or necessary this is depends primarily on your application and its target audience. Cross-platform application development is always difficult. Is there any benefit from working on new platforms? Would it be easier to use emulation or a web-based interface?

It should be noted that in the not-too-distant past, the cross-platform issue was not so acute as it is today. Windows was the dominant platform for personal computers and x86-32 was the dominant hardware platform. If you wrote a Win32 application you covered 90% of your potential customers (both personal and corporate). And in reality very few people completed versions of their applications for other platforms.

Today we have Win64 and ARM, and tablets and PDAs are playing an increasingly important rĂ´le not only for ordinary home users, but also for corporate customers. Apple's iOS, as well as Google with Android, has captured a significant share of the mobile device market. Although Windows is still in a strong position for desktop applications, the idea of targeting Windows only no longer seems like a lucrative approach for many applications.

If you're writing a desktop application, Windows is still the main option. Of course, versions for MacOS and Linux are also sometimes helpful. Especially when you consider the differences in popularity of different platforms in different countries.

For server applications you should first consider various options for Unix (Linux, FreeBSD, Solaris, etc.), followed immediately by Windows, but you can forget about MacOS.

Mobile applications (for tablets and PDAs) are the most difficult to decide about since there are three major platforms: iOS, Android, and Windows Phone. Other platforms are much less relevant today (Windows Mobile, Blackberry, and Symbian).

Generally you will see various possible combinations in practice: Code is written for one platform using a specialized library, and direct calls to an API (for example: a VCL application for Win32). There is one platform and it's easy. Other platforms are not considered, or used only with an emulator. Often it's easier to use an emulator, and it gives acceptable results. Emulation is acceptable for the corporate customer, and for customised and highly specialized software; but not for boxed products.

Code is written for one platform, using cross-platform tools (for example: an FMX application for Win32). The intention here is either to hold open a future hope ("someday we'll do a version for MacOS"), or to use more powerful features of cross-platform libraries (such as hardware acceleration for 3D/2D). Code is written for two or three platforms, with minor differences, using direct calls to the APIs (for example: a VCL application for both Win32 and Win64). Typically this is used to exploit the limited adaptation needed between closely related platforms.

Code is written for two or three different platforms using a single-purpose solution (for example: a VCL Win32/Win64 application, with an FMX application for MacOS). This is a rare approach because of the high development costs. But it is the approach of choice when you need the maximum number of native features on each platform. The main objective in this case is to maximize use of 'universal' code which can be shared between platforms. As a rule, this approach targets no more than three platforms (selecting only the most popular three).

Code is written for multiple platforms using cross-platform tools (for example: an FMX application for Win32/Win64/MacOS/iOS). Native interface and platform capabilities are sacrificed for universality. This route is chosen when you need to reach the maximum number of customers, even at the risk of compromising your application's ease of use.

You code a web-based application.
Pro: it works wherever there is web-browser.
Con: a simplified web-interface often causes discomfort for users.

General Principles 

First you should select the target platform(s). You can do this on the basis of the recommendations above. Then you need to choose the appropriate development tools for these platforms (compiler, libraries and frameworks).

After settling on the list of platforms and development tools, you can write code just as you would do for a single platform. But you'll have to check the written code for each selected platform. You cannot just write generic code and expect it to work identically on all platforms. There will always be both small and more significant differences. Even between the highly compatible Win32 and Win64 platforms there are many differences and consequential pitfalls!

To develop and test code it is not necessary have a real machine or device - virtual machines and emulators are often sufficient. You need only one (quite powerful) computer and a few virtual machines - one for each platform (without host system). Of course, a final test on real hardware can be helpful.

As an alternative to virtual machines you can utilize a multi-boot machine with several different operating systems installed on a single physical machine, providing a choice among systems to load at start-up (when the operating system boots). Clearly, this is not the most convenient option (since a restart is required to change platform), and it is not always possible (you cannot dual-boot iOS and Windows), but this is still a working solution for "live" testing on a machine with limited resources.

Conditional compilation and platform-dependent code
In most cases you will not be able to write completely generic code. Often, your code will include some direct API calls. How do you proceed in these cases?

You accomplish this by using conditional compilation directives.

You tell the compiler not to compile certain code when a particular condition is met. Usually the condition is the presence or absence of specific markers (so-called conditional compilation symbols). This is done by using {$IFDEF}/{$IFNDEF}/{$IF} directives.  The conditional compilation symbol will either be specified (defined), or not specified (not defined).

That is, it is a binary feature: yes/no, on/off.

Each symbol will be checked for definition (existence) and depending on the test result the selected code is compiled (or not). For example:

procedure DoSomething;
begin
{$IFDEF DEBUG}
OutputDebugString('DoSomething called');
{$ENDIF}
// ...
// DoSomething's code
// ...
end;

Note that this choice is made at compile time (design time). That is , If the symbol DEBUG is defined, then DoSomething will look like this:
procedure DoSomething;
begin
OutputDebugString('DoSomething called');
// ...
// DoSomething's code
// ...
end;

If DEBUG is not defined, then the procedure becomes the following:
procedure DoSomething;
begin
// ...
// DoSomething's code
// ...
end;

In other words, the "filtered out code" will not appear at all in the
resulting application.

By the way, conditional compilation symbols are case insensitive.

Conditional compilation symbols can be defined in several ways:
  • Predefined – meaning that they are defined by the compiler. For example, WINDOWS, WIN32, DCC, CONSOLE, etc. 
  • You can specify them in the project options.
    For example, DEBUG. 
  • You can define them in your source code using
    {$DEFINE Symbol} directive. 
  • You can define them in an include file (.inc file which is linked through the {$IFileName.inc} directive).
    Formally, this is identical to the previous method, but is used more often. 
  • You can define them at compile-time by passing a special command line option to the compiler: -D Symbol.
You can use predefined conditional compilation symbols to compile platform-specific code for different platforms. Various IDEs define different sets of conditional compilation symbols. A new version of an IDE may add new symbols. The same symbol can be used by one IDE and not be used in another IDE; or it may have different meanings in the two different IDEs; or (if you're lucky) it might be identical in both IDEs. 

For these reasons, real projects rarely use predefined conditional compilation symbols directly. Most often, the project adds an include file (with the .inc extension), which establishes universal symbols for conditional compilation, adapted for each IDE. This file is then included at the beginning of each unit of the project (through the {$IFileName.inc}). After that, your code can use new "beautiful" and universal symbols. If you do not want to write such a file yourself, you can use the file jedi.inc file, which can be downloaded from here:

http://projectjedi.svn.sf.net/viewvc/projectjedi/trunk/shared/include/jedi.inc?view=log
(click "Download" next to the "Links to HEAD")

So, as a result, you have three options for dealing with the platformdependent code:

1. You put platform-specific code directly into the source code and surround it with conditional compilation directives. This option is suitable only for the simplest cases, and is not recommended.

2. You offload all platform-dependent code to a (single) separate unit. Inside the unit you use conditional compilation directives to generate code for the different platforms, and the outer (caller) code can simply call the functions and classes from your unit, without having to use conditional compilation.
Pro: the caller is universal and does not contain any platform-specific parts.
Con: the platform-dependent unit is full of conditional compilation directives.

3. You offload all platform-dependent code to several different units with different names - one unit for each platform. Each unit has the same interface section and different implementation sections. Within each unit you write the code for the specific platform without the use of conditional compilation directives. The recommended format for the unit name is: PlatformName.UnitName.pas. The caller will connect to the correct platform's unit by using conditional compilation directives, and the code will call the functions and classes directly, without using conditional compilation.
Pro: the code is better structured through not being cluttered with conditional compilation directives.
Cons: the calling code still contains conditional compilation directives (inside its uses clause), and it will be harder to add a new platform. In any case, here are a few tips on the use of conditional compilation for platform-specific code:

- Do not assume a fixed set of platforms
Bad:
{$IFDEF MSWINDOWS}
// Code for Windows
{$ELSE}
// Code for MacOS
{$ENDIF}

Better:

{$IFDEF MSWINDOWS}
// Code for Windows
{$ENDIF}
{$IFDEF MACOS}
// Code for MacOS
{$ENDIF}

Ideal:

// Code for (very) old Delphi
{$DEFINE UNKNOWN_PLATFORM}
{$IFDEF MSWINDOWS}
{$UNDEF UNKNOWN_PLATFORM}
// Code for Windows
{$ENDIF}
{$IFDEF MACOS}
{$UNDEF UNKNOWN_PLATFORM}
// Code for MacOS
{$ENDIF}
{$IFDEF UNKNOWN_PLATFORM}
{$MESSAGE FATAL 'Unknown platform'}
{$ENDIF}

// The modern (compact) version:
{$IF DEFINED(MSWINDOWS)}
// Code for Windows
{$ELSEIF DEFINED(MACOS)}
// Code for MacOS
{$ELSE}
{$MESSAGE FATAL 'Unknown platform'}
{$IFEND}

- Do not confuse symbols like WIN32 and MSWINDOWS, MACOS and MACOS32, etc. Symbols with suffixes 32/64 are combinations of a common symbol (like MSWINDOWS, MACOS, etc.) with the symbol for the CPU (like CPU386, CPUX64, etc.).
 - Do not use a binding to the compiler version.

 Bad:
{$IFDEF VER230}
// Some code for a specific version of Delphi
{$ENDIF}
{$IFDEF COMPILER6_UP}//
// Some code for Delphi 6 and above
{$ENDIF}

Good:

// In our .inc-file:
{$IFDEF VER230}
{$DEFINE HAS_FEATURE_X}
{$ENDIF}
{$IFDEF COMPILER6_UP}
{$DEFINE HAS_FEATURE_Y}
{$ENDIF}
// In real code:
{$IFDEF HAS_FEATURE_X}
// Code that runs with the X feature
{$ENDIF}
{$IFDEF HAS_FEATURE_Y}
// Code that runs with the Y feature
{$ENDIF}

Emulation of source code 

I want to stress the importance of source code emulation by devoting a separate section to it. It involves the following process: instead of writing generic functions from scratch, you write versions of existing functions for other platforms. For example the LoadLibrary function is a feature of the Windows API which does not exist on other platforms.

However, it is clear that the functionality of loading shared libraries is present on other platforms. Therefore, there must be analogous functions to the LoadLibrary function on other platforms. This means that we must be able to write our own LoadLibrary function, which will call the necessary function(s) of another target platform and, thus, will emulate the behaviour of the original Windows function. Incidentally Delphi does this, and many Windows API functions work on other platforms because they have been implemented in the System and SysUtils units (only for other platforms; under Windows they are just imported).

You can use this approach too. Use a single universal unit (or a unit dedicated to each platform you support, as described above). Then you may not need to change your Windows API coding - because the Windows API will work on another platform through your emulation (at least to the extent that you're implementing it).

A similar technique can be used to compensate for differences between various Delphi versions and other compilers. Simply create a new unit and add there all the features you want that are missing from certain compilers or IDEs.

Platform specifics

When you write cross-platform code (whether universal code for all platforms or specialized code for a particular platform) you should always keep in mind the differences between the platforms.

File system


  • In some systems, the file names are case sensitive, in others they are not. Use the function SameFileName to compare file names for specific platforms
  • Unix-like systems use / (forward slash) to separate file path components, and the Windows-like systems - use \ (backslash) (although almost all file functions understand both characters). Use the constant PathSep and functions like ExtractFilePath, and IncludeTrailingPathDelimiter to manipulate file paths.
  • Unix uses - or -- for command-line options, Windows uses /.
  • Windows has both alpha-character drive names and UNCnames. Unix does not use drive letters, it uses mounting instead.
  • Different platforms allow different sets of acceptable characters in file paths. File naming rules also differ. It makes sense to normalize all names to the canonical form.
  • Most popular systems use the same conventions for the current and parent directory (. an .. ) and also for file masks (* and ? ).
  • Windows uses file extensions for known file types. Unix mainly relies on the file attributes of executable files, and extensions are used less frequently.
  • In Windows, most files have two names - long and short. This may come as a surprise!
  • Almost all modern systems have different versions of file links (hard, symbolic, etc.) which have their own characteristics in each system.
  • In some cases, directories can be files. Be very careful when checking for the existence of a file with FileExists - make sure (by documentation or experimentally) that the function works as expected. The same is true for file references.
  • File access rights and opening modes are implemented in different ways and with different semantics, but it seems with similar functionality.

“Known Folders”


  • "Known Folders" is the phrase used by the Windows API, but it can be applied to other systems.
  • "Well-known folder" refers to a directory or a virtual path to a predefined folder which has system-wide implications - such as the system folder, your profile folder, the program folder, the settings folder, and so on.


Almost all programs work with known locations - to load the configuration, store data, etc. Therefore, they should be able to get the path to these folders. In spite of being "known", the locations of these folders are often not fixed but rather set during installation of the system (sometimes – or later when used).

Therefore, systems have special features to obtain paths to such folders. Of course, these functions are specific for each system. Moreover, the full set of possible known folders is different too.

You can get some of these folders using Delphi. For example via TPath.GetHomePath. For all those folders that do not have a ready-to-use implementation in Delphi you will have to write your own code.

Finally, here are some examples of important folders. This is not comprehensive, since exact details depend on the precise version of the operating system.

For Windows:

  • \Users (Documents and Settings) - root folder for the user profiles.
    Each user has their personal folder here, which takes their name.
  • \Users\UserName\Documents – the user's documents
  • \Users\UserName\Application Data - applications' data
  • \Users\UserName\Application Data\Local\Temp\ - folder for temporary files
  • \Program Files - installed applications
  • \Windows - system folder
  • \Windows\System32 (SysWOW64/SysNative)
    - common components

For Linux:


  • \boot, \bin and \sbin - boot software
  • \home - root folder for the user's documents.
    Each user has their personal folder which takes their name.
  • \usr - installed programs (also structured in subdirectories)
  • \lib - common components
  • \etc - program configurations
  • \var - temporary and frequently changed files (also structured in subdirectories)
  • \mnt - mounted devices
  • \proc - virtual file system,
    something similar to the Windows Registry


Further, it should be noted that some Unix systems do not have the concept of "application folder".

On these systems - ParamStr(0) and Application.ExeName sometimes cannot return the folder where the executable program is placed. For this reason applications in Unix often use a fixed installation location to be able to get their folders.

If the path can be changed, then it's better to store the path in a particular place. (This is how it's done in Windows: the program installer writes the program path as a predetermined path like \Software\AppName).

As you can see, despite some similarities, there are numerous differences.

Configuration, data and resources 

Across platforms there are both similarities and differences in placing data. The general similarities are: there is always a separation between the purely local (current user) and global (all users) data; roughly the same rules always apply (only an administrator can affect all users); user data is always separated from the program; read-only data (programs and master templates) is always separated from the re-writable data (configuration).

Now the differences: Windows application configuration data is often stored in the registry because it is easier and more efficient. Since there is no registry on other platforms, all other platforms store configuration in files (in differing formats for which there is no standard). This approach is also used in Windows as alternative to the registry.

Therefore, in general, if you eliminate the Windows registry from consideration (except when you cannot work without it), then all the usual recommendations and best practices for programs on Windows are also valid for other platforms - taking into account differences in the distribution of "well-known folders", of course.

For example, a local folder for your program data (read-write) on different systems can be (these are just examples, a specific value may vary):

  • Windows 98: C:\Windows\Local Settings\Application Data\ProjectName\
  • Windows XP: C:\Documents and Settings\UserName\Local Settings\Application Data\ProjectName\
  • Windows 7: C:\Users\UserName\AppData\Local\ProjectName\
  • Linux: /home/UserName/.config/ProjectName/
  • MacOS: /Users/UserName/.config/ProjectName/

A global directory:

  • Windows 98: C:\Program Files\ProjectName\
  • Windows XP: C:\Documents and Settings\All users\Local Settings\Application Data\ProjectName\
  • Windows 7: C:\ProgramData\ProjectName\
  • Linux: /etc/ProjectName/
  • MacOS: /etc/ProjectName/
The main thing here is to have at hand a universal function to get paths to known folders.

Resources in executables are specific to Windows and have no direct counterpart on other platforms. You can replace the resources by storing data in files alongside the program. Functionally, this would be equivalent.

MacOS differs from both Unix and Windows in that MacOS applications are distributed in a so-called "application bundle", which contains everything necessary for the application.

Encoding

Modern Windows uses Unicode (UTF-16), and also provides ANSI-versions of most functions for backwards compatibility. Unix often uses UTF-8. Many systems use null-terminated strings (sometimes explicitly specifying their length).

Usually you do not need to think about these issues, unless you call the system functions directly. In the latter case, you need to cast the strings to the desired format. In recent Delphi versions coding for these issues is handled automatically. You do not need to manually convert strings - a simple assignment of strings between different formats is enough.

Text files

Different systems use different line separators for text strings. Sometimes it is CR + LF (#13#10), sometimes only LF (#10). Use the constant sLineBreak instead of character literals. You must be particularly careful when analyzing and breaking lines since line breaks have different lengths on different platforms. So it is best always to use ready-made functions.

If you exchange text data between platforms, you will need to be prepared to convert one format to another. You can normalize text with the function AdjustLineBreaks.

Another difference is the BOM (Byte Order Mark). Windows follows the Unicode standard by putting a BOM at the beginning of all Unicode files (UTF-8, UTF-16, etc.). But UTF-8 files in Unix often lack a BOM.

Date and time

Different systems store timestamps differently. Usually you do not care about the specifics, unless you are going to exchange data between the platforms (either via a network, or via files/documents). In the latter case, you need to select a date format and convert the date-time in this format to your platform and back.

As a hint, for a "portable" format you should select the format of your most popular platform which will eliminate the need to convert timestamps there.

Bitness

You need to take account of bitness all the time – both the system bitness and its data model for integers. In other words, you need to know on your platform how many bytes Integer and Pointer occupy. I will not dwell on this here, as I wrote extensively on this in a previous Win64 article in Blaise Pascal Magazine.

Byte order

The byte order (or endianness) depends on the processor. One byte (say, $41 ) is treated uniquely. But if you have two bytes in a row (say, $41 $10), the processor can interpret them either as $4010 or as $1041.

Endianness refers to the order in which bytes are stored. The term is taken from a story in Gulliver's Travels by Jonathan Swift about wars fought between those who thought eggs should be cracked on the Big End and those who insisted on the Little End. With chips, as with eggs, it doesn't really matter as long as you know which end is up.

Endianness

The first byte
(Lowest address)
The last byte
(Highest address)NoteBigHigh byteLow byte
It is easier to write - the numbers are written in the usual way: the high byte is on the "left" LittleLow byteHigh byte It is more convenient for calculations (carry) – the byte value increases with the address

Each specific hardware processor can be either Little Endian or Big Endian. However, some processors are so-called Bi-Endian (or Dual-Endian) supporting both modes, and allowing a mode switch (set).

Again, you should not worry about the byte order, unless you are operating on a single byte within an integer, or sharing data with other systems (via network or files).

Fortunately Windows always works as a LE (Little Endian) system - even with the BE-processors. But other systems (e.g. MacOS and Linux) can be Big Endian on certain hardware.

Assembler

When developing cross-platform code, you should avoid assembler like the plague because assembler is symbolic text for processor-specific hardware commands. That is, assembly code is always written for a specific processor. It is specific to that processor and is not portable to other platforms.

Therefore for cross-platform development less efficient universal code is always preferable to faster, processor-specific assembly code.

Use assembler only in the rarest cases, where you cannot accomplish what you need any other way. And when you use assembler, as discussed above, make it platform-specific code via conditional compilation.

The paradigm behind the platform

The Unix philosophy is “from developers to developers”. Reusable and automated solutions are valued highly. That is why command-line utilities have pride of place, and the user interface is attached on top of them. Open source codebases and diverse OS distributions express the unfettered freedom of Unix.

MacOS (as well as other Apple products), although based on Unix, promotes exactly the opposite values, putting ease of use and userfriendliness at the heart of the interface. From this follow all the restrictions for developers, including a sandbox, an App Store, packaging applications in bundles, and so on. Rather than sporting many features – a single excellent (perfect) one is better.

Microsoft Windows is somewhere in the middle. The GUI is king, but the developer is not restricted. Microsoft's original mission ("a computer in every home") has already been accomplished.

You must consider all this when developing applications for specific platforms. If you write an application for MacOS following Linux traditions, it is unlikely that it will be warmly received by Mac users, who have very different priorities; and vice-versa.

###

About the Author: Alexander Alexeev

Alexander started working with Delphi while still at university. Following graduation as a Master of Mathematics, he took several jobs in the Delphi world. Today he works for the EurekaLab s.a.s. company, where he develops and supports EurekaLog for Delphi/C++ Builder. Alex lives in the Russian Federation and maintains a personal blog, where he writes about Delphi. Some of his articles are available in English at eurekalog.blogspot.com. His favourite topics are debugging-related.


Monday, June 15, 2015

Download Sites Have Become Software Pimps

I have spent the last couple months doing marketing instead of coding. And this weekend I stumbled across this thing called ProductHunt. It's a pretty cool idea check them out on the web producthunt,com or on twitter @ProductHunt.

The way software needs to be marketed today is completely different that it was 20 - 25 years ago. I refuse to become irrelevant so I am learning how to change my thinking and strategy. This weekend several things fell into place and I went on a rant about the current state of downloads and the download websites.

Here is my five minute rant on why I think download sites have become software pimps and how this is effecting all small software developers.

Semper Fi
Gunny Mike


( transcript )

Most download websites are software pimps.

When I went to the ISVCon in 2013 some of the vendors that the ASP got touted their secondary offer programs.

So for those of you who are not familiar with the secondary offer program, it goes like this. You’re not making money through selling of your software. Okay people find it and then they download it from all the different download sites on the web. But what’s your conversion rate? Is it one out of five-hundred. For every three-hundred downloads you get do you get one sale.

So the download sites their trying to make money. And I call them software pimps because this is what they do. They take your software, your a small independent software developer. You spend hours upon hours putting together a piece of software hoping that you can sell it.

Are you going be the next WinZip or whatever. No, you’re probably not but you’re trying. And you don’t want to get your dreams crushed. So lets say you finally get your software completed it’s gone through all the testing. It’s gone through this. It’s gone through that and you’re ready to push it out to the market.

How are you going to distribute it. You use the software download sites. They take your software and your nice pretty install and they wrap a monetization package around it. The secondary offer.

So here’s what happens. They’re a software pimp. They’re turning your software into a whore. Their platform becomes the mattress. Your software is the whore and then they get unsuspecting customers. So they’ve basically ruined the software download process It’s destroyed. No wonder why Google has the unwanted software thing going on.

The ASP tried 10 years ago to create a website called safer downloads that disallowed this kind of crap. But everybody wants their slice of pie. They’re all greedy little bastards who sit there and say okay I’m a download site. I know marketing. But, I can’t create a piece of software if my life fucking depended on it.

But they’re good at putting together websites. They have a list. Who knows how they compiled that list. And then in order to stay alive have to sell advertising. Well regular advertising doesn’t work. So then they create the Ask toolbar which is probably the worst offender that I’ve ever seen in my life.

Do you know how many times at work people have downloaded something and the Ask toolbar automatically gets installed and now you’ve got all this crap happening. Yeah, that is the secondary software offer program.

And then places like download.com. You go to download.com and your your stuff is listed. I remember 25 years ago when I had my first piece of software out there it’s like oh wow it’s on download.com that’s really cool. If you looked at that website 25 years ago to today it’s like what the hell. They got all these big green buttons that say download now, but none of them are for your software.

Someone finds your product. They go to the description page and there’s seven different friggin download buttons on that page. All of them are big green download buttons. The poor customer doesn’t know what the hell to do. Yeah, there is a need for a safer download website. And the problem is who’s going build it.

The ASP has some great ideas. But the ASP is just a very small organization. ProductHunt understands this. ProductHunt created an environment where they’re keeping the marketers out. Why? Because they are going to pollute it. They’re going to turn it into a secondary offer thing and that’s not what we need. We need something like safer downloads.

Yeah. I may have to talk to Suster and ask him. “Hey do you want to throw some money at a website called safer downloads?” Hey Suster. if you’re listening. Do you want to get involved in a website called safer downloads. If you do let me know.

We’ll put the reputation of the ASP behind you’re money. Go to Google and pitch them a safer download website. Where people can go and download software that’s not going to intrude on their computers and small little software developers like me and all the other product hunters out there who are trying to make this stuff  happen, actually can survive in a world that’s greedy.

So, that’s my take on download sites becoming software pimps.

Gunny out!


Saturday, February 7, 2015

Delphi Speed Tip: Create Local Variables Inline On The Fly

Here is another one of my Love Delphi / Hate Delphi nit pics. I love the strong typing aspect of Delphi. I believe you should always define a variable before you use it. I hate it though when you are inside the guts of a procedure or function and you need a new variable. You have to jump up above the begin statement define the variable and then go back to the spot you were just at and continue coding.

Well I just discovered you can create a new local variable inline on the fly and begin usinging it immediately.

I'd like to thank Ron Grove for demonstrating this handy tip in his video.


Enjoy - Semper Fi,
Gunny Mike
end.

Friday, January 16, 2015

Make Your Software Installs More Professional

I was showing a friend of mine one of the latest programs I just created. He downloaded it and it installed perfectly. I asked him to uninstall it after we got done looking at it. He went straight to the Control Panel - Programs and Features and to my surprise my application was not listed.

I use Inno Setup (highly recommended) for all of my install files. It turns out that you need to include two directives inside the [Setup] section of the Inno Setup script in order for your program to be listed. I'd like to thank TLama from StackOverflow for his answer.

[Setup]
UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName}

Adding unistall information with Inno Setup
TLama
Inno Setup

Enjoy!

Semper Fi
Gunny Mike
end.

Saturday, January 3, 2015

Most Frequently Used Shortcuts By Delphi Programmers

Here is a list of the most frequently used shortcuts by Delphi programmers

Ctrl+.
IDE Insight - Focus the IDE Insight input box

Ctrl+Space
Code Completion (window resizeable/sortable)
Video

Ctrl+Shift+Space
Code Parameter Hints (invoke tooltip help)
Video

Ctrl+Shift+A
Find Unit - Add to Implementation or Interface

Ctrl+Shift+C
Class Code Completion at Cursor

Ctrl+Shift+V
Declare Vaiable

Ctrl+Shift+D
Declare Field

Ctrl+J
Code Templates (window resizeable)
Video

Ctrl+Shift+J
Sync Edit
Video

Ctrl+Left Click
Alt+Up Arrow
Code Browsing (requires $YD)

Ctrl+Shift+Up Arrow
Ctrl+Shift+Dn Arrow
Code Jumping Between Declaration/Implementation

Alt+Left Click+Drag
Select Block

Tab
Indent Selected Code
Indent Selected Block

Shift+Tab
Outdent Selected Code
Outdent Selected Block

Ctrl+D
Format Selected Code
Format Selected Block

F9
Run

Ctrl+Shift+F9
Run Without Debugging

F4
Run To Cursor (Build Debug)

Ctrl+F9
Compile

F5
Set/Remove Breakpoint
Video

F7
Trace Into

F8
Step Over

Ctrl+F2
Program Reset

Code Folding Shortcuts


Ctrl+Shift+K+A
Expand All Blocks of code

Ctrl+Shift+K+C
Collapse All Classes 

Ctrl+Shift+K+M
Collapse All Methods 

Case Statement Code Completion
https://stackoverflow.com/a/13532718/195983

Complete List of Shortcuts

Enjoy,
Gunny Mike
end.

Sunday, December 28, 2014

How to Display Menu Item Hints in Delphi Applications (Revisited)

Last week I decided I wanted the main menu hints and popup menu hints to display in a tooltip window just like the hints for all the other controls. I figured it shouldn't be that difficult, right. Microsoft does it all over the place so why shouldn't my applications.

Wow did I under estimate what it takes to pull this one off.

It turns out that menuitem hints by design are intended to display in the status bar. Who's bright idea was that? I don't know about you but when I mouseover stuff my eyes look where the mouse cursor is. I hardly ever look down at the status bar.

So, I turned to google and began my search for code. I figured that someone has already done this and I can just implement their solution. Almost but not quite.

Here are my requirements:
  1. Display all main menu hints in a tooltip
  2. Display all popup menu hints in a tooltip
  3. Display multi-line menu hints as multi-lines
I'd like to thank Zarko Gajic and mghie (from stackoverflow) for doing all the hard work and providing the code base that I tweaked in my final implementation.

How to Display Menu Item Hints in Delphi Applications - Zarko Gajic
Display a ToolTip hint on a disabled menu item of a popup menu - mghie

I have heavily commented the code below for a very specific reason. I wanted it to standout from all the other code in my application. Here is what the folded code looks like in my IDE

Yes those are real box drawing characters. I like the way the structured comments keeps all the code needed for the menuhints implementation in a nice, visual group.

Semper Fi,
Gunny Mike

Add to Uses
  Vcl.Menus
  Vcl.ExtCtrls

Interface Section
{┌────────────────────────────────────────────────────────────┐}
{│ MenuHints Type Declaration                                 │}
{├────────────────────────────────────────────────────────────┤}
{│ How to Display Menu Item Hints in Delphi Applications      │}
{│ http://delphi.about.com/od/vclusing/a/menuitemhints.htm    │}
{│ Zarko Gajic                                                │}
{├────────────────────────────────────────────────────────────┘}
{│} type
{│}   TMenuItemHint = class(THintWindow)
{│}     private
{│}       activeMenuItem : TMenuItem;
{│}       showTimer : TTimer;
{│}       hideTimer : TTimer;
{│}       procedure HideTime(Sender : TObject) ;
{│}       procedure ShowTime(Sender : TObject) ;
{│}     public
{│}       constructor Create(AOwner : TComponent) ; override;
{│}       destructor Destroy; override;
{│}       procedure DoActivateHint(menuItem : TMenuItem) ;
{│}    end;
{│} {  End TMenuItemHint }
{└─────────────────────────────────────────────────────────────}


TForm Private Declarations
    { Private declarations }
    {┌────────────────────────────────────────────────────────────┐}
    {│ MenuHints Form Private Declartions                         │}
    {├────────────────────────────────────────────────────────────┤}
    {│ Adapted from Zarko Gajic's article called                  │}
    {│ How to Display Menu Item Hints in Delphi Applications      │}
    {│ http://delphi.about.com/od/vclusing/a/menuitemhints.htm    │}
    {│                                                            │}
    {│ Further adapted by mghie's stackoverflow answer to         │}
    {│ Display a ToolTip hint on a disabled menu item of a        │}
    {│ popup menu                                                 │}
    {│ http://stackoverflow.com/questions/470696/#471065          │}
    {│                                                            │}
    {│ Important:                                                 │}
    {│ Add call to MenuHintOnCreate in the form OnCreate method   │}
    {│ Add call to MenuHintOnDestroy in the form OnDestroy method │}
    {├────────────────────────────────────────────────────────────┘}
    {│} miHint : TMenuItemHint;
    {│} fOldWndProc: TFarProc;
    {└─────────────────────────────────────────────────────────────}
    {┌────────────────────────────────────────────────────────────┐}
    {│ MenuHints Form Private Declartions Contiinued              │}
    {├────────────────────────────────────────────────────────────┘}
    {│} Procedure MenuHintOnCreate;
    {│} Procedure MenuHintOnDestroy;
    {│} procedure WMMenuSelect(var Msg: TWMMenuSelect); message WM_MENUSELECT;
    {│} procedure PopupListWndProc(var AMsg: TMessage);  public
    {└─────────────────────────────────────────────────────────────}


Form OnCreate / OnDestroy
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  {┌────────────────────────────────────────────────────────────┐}
  {│ MenuHints:                                                 │}
  {├────────────────────────────────────────────────────────────┘}
  {│} MenuHintOnCreate;
  {└─────────────────────────────────────────────────────────────}
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  {┌────────────────────────────────────────────────────────────┐}
  {│ MenuHints:                                                 │}
  {├────────────────────────────────────────────────────────────┘}
  {│} MenuHintOnDestroy;
  {└─────────────────────────────────────────────────────────────}
end;


Implementation Section
{┌────────────────────────────────────────────────────────────┐}
{│ MenuHints Implementation                                   │}
{├────────────────────────────────────────────────────────────┤}
{│ Adapted from Zarko Gajic's article called                  │}
{│ How to Display Menu Item Hints in Delphi Applications      │}
{│ http://delphi.about.com/od/vclusing/a/menuitemhints.htm    │}
{│                                                            │}
{│ Further adapted by mghie's stackoverflow answer to         │}
{│ Display a ToolTip hint on a disabled menu item of a        │}
{│ popup menu                                                 │}
{│ http://stackoverflow.com/questions/470696/#471065          │}
{│                                                            │}
{│ Modified to accomodate multi line hints                    │}
{├────────────────────────────────────────────────────────────┤}
{│ Generic Section                                            │}
{├────────────────────────────────────────────────────────────┘}
{│} procedure TMenuItemHint.HideTime(Sender: TObject);
{│} begin
{│}    //hide (destroy) hint window
{│}    self.ReleaseHandle;
{│}    hideTimer.OnTimer := nil;
{│} end;
{├────────────────────────────────────────────────────────────┐}
{│ procedure: TMenuItemHint.ShowTime                          │}
{│ Modified:  12/27/2014                                      │}
{│ By:        Michael Riley                                   │}
{│ Reason:    Accomodate multi line hints                     │}
{│            Changed the position and size of the TRect      │}
{├────────────────────────────────────────────────────────────┘}
{│} procedure TMenuItemHint.ShowTime(Sender: TObject);
{│}
{│}   procedure Split(Delim: Char; Str: string; Lst: TStrings) ;
{│}   begin
{│}      Lst.Clear;
{│}      Lst.StrictDelimiter := True;
{│}      Lst.Delimiter     := Delim;
{│}      Lst.DelimitedText := Str;
{│}   end;
{│}
{│} var
{│}   r : TRect;
{│}   wdth : integer;
{│}   list : TStringList;
{│}   s,str  : string;
{│}   j,h,w : integer;
{│}
{│} begin
{│}   if activeMenuItem <> nil then
{│}   begin
{│}      str := activeMenuItem.Hint;
{│}      str := StringReplace(str,#13#10,'|',[rfReplaceAll]);
{│}      str := StringReplace(str,#13,'|',[rfReplaceAll]);
{│}      str := StringReplace(str,#10,'|',[rfReplaceAll]);
{│}      while AnsiPos('||',str) > 0 do
{│}      begin
{│}        str := StringReplace(str,'||','|',[]);
{│}      end;
{│}
{│}      list := TStringList.Create;
{│}      split('|',str,list);
{│}      s := '';
{│}      h := Canvas.TextHeight(str) * (list.Count);
{│}      w := 0;
{│}      for j := 0 to list.Count -1 do
{│}      begin
{│}        if j > 0 then s := s + #13#10;
{│}        s := s + list[j];
{│}        wdth := Canvas.TextWidth(list[j]);
{│}        if wdth > w then w := wdth;
{│}      end;
{│}      list.Free;
{│}
{│}     //position and resize
{│}     r.Left := Mouse.CursorPos.X;
{│}     r.Top := Mouse.CursorPos.Y + 20;
{│}     r.Right := r.Left + w + 8;
{│}     r.Bottom := r.Top + h + 2;//6;
{│}     ActivateHint(r,s);
{│}   end;
{│}
{│}   showTimer.OnTimer := nil;
{│} end; (*ShowTime*)
{├─────────────────────────────────────────────────────────────}
{│} constructor TMenuItemHint.Create(AOwner: TComponent);
{│} begin
{│}   inherited;
{│}   showTimer := TTimer.Create(self) ;
{│}   showTimer.Interval := Application.HintPause;
{│}
{│}   hideTimer := TTimer.Create(self) ;
{│}   hideTimer.Interval := Application.HintHidePause;
{│} end;
{├─────────────────────────────────────────────────────────────}
{│} destructor TMenuItemHint.Destroy;
{│} begin
{│}   hideTimer.OnTimer := nil;
{│}   showTimer.OnTimer := nil;
{│}   self.ReleaseHandle;
{│}   inherited;
{│} end;
{├─────────────────────────────────────────────────────────────}
{│} procedure TMenuItemHint.DoActivateHint(menuItem: TMenuItem);
{│} begin
{│}   //force remove of the "old" hint window
{│}   hideTime(self) ;
{│}
{│}   if (menuItem = nil) or (menuItem.Hint = '') then
{│}   begin
{│}     activeMenuItem := nil;
{│}     Exit;
{│}   end;
{│}
{│}   activeMenuItem := menuItem;
{│}
{│}   showTimer.OnTimer := ShowTime;
{│}   hideTimer.OnTimer := HideTime;
{│} end;
{├────────────────────────────────────────────────────────────┐}
{│ Form Specific Section                                      │}
{├────────────────────────────────────────────────────────────┘}
{│} procedure TfrmMain.MenuHintOnCreate;
{│} var
{│}   NewWndProc: TFarProc;
{│} begin
{│}   miHint := TMenuItemHint.Create(self);
{│}   NewWndProc := MakeObjectInstance(PopupListWndProc);
{│}   fOldWndProc := TFarProc(SetWindowLong(VCL.Menus.PopupList.Window, GWL_WNDPROC, integer(NewWndProc)));
{│} end;
{├─────────────────────────────────────────────────────────────}
{│} procedure TfrmMain.MenuHintOnDestroy;
{│} var
{│}   NewWndProc: TFarProc;
{│} begin
{│}   NewWndProc := TFarProc(SetWindowLong(VCL.Menus.PopupList.Window, GWL_WNDPROC, integer(fOldWndProc)));
{│}   FreeObjectInstance(NewWndProc);
{│} end;
{├─────────────────────────────────────────────────────────────}
{│} procedure TfrmMain.WMMenuSelect(var Msg: TWMMenuSelect);
{│} var
{│}   menuItem : TMenuItem;
{│}   hSubMenu : HMENU;
{│} begin
{│}   inherited; // from TCustomForm
{│}
{│}   menuItem := nil;
{│}   if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
{│}   begin
{│}     if Msg.MenuFlag and MF_POPUP = MF_POPUP then
{│}     begin
{│}       hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem);
{│}       menuItem := Self.Menu.FindItem(hSubMenu, fkHandle);
{│}     end
{│}     else
{│}     begin
{│}       menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand);
{│}     end;
{│}   end;
{│}
{│}   miHint.DoActivateHint(menuItem);
{│} end; (*WMMenuSelect*)
{├─────────────────────────────────────────────────────────────}
{│} procedure TfrmMain.PopupListWndProc(var AMsg: TMessage);
{│}
{│}   function FindItemForCommand(APopupMenu: TPopupMenu; const AMenuMsg: TWMMenuSelect): TMenuItem;
{│}   var
{│}     SubMenu: HMENU;
{│}   begin
{│}     Assert(APopupMenu <> nil);
{│}     // menuitem
{│}     Result := APopupMenu.FindItem(AMenuMsg.IDItem, fkCommand);
{│}     if Result = nil then begin
{│}       // submenu
{│}       SubMenu := GetSubMenu(AMenuMsg.Menu, AMenuMsg.IDItem);
{│}       if SubMenu <> 0 then
{│}         Result := APopupMenu.FindItem(SubMenu, fkHandle);
{│}     end;
{│}   end;
{│}
{│} var
{│}   Msg: TWMMenuSelect;
{│}   menuItem: TMenuItem;
{│}   MenuIndex: integer;
{│}
{│} begin
{│}   AMsg.Result := CallWindowProc(fOldWndProc, VCL.Menus.PopupList.Window, AMsg.Msg, AMsg.WParam, AMsg.LParam);
{│}   if AMsg.Msg = WM_MENUSELECT then begin
{│}     menuItem := nil;
{│}     Msg := TWMMenuSelect(AMsg);
{│}     if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then begin
{│}       for MenuIndex := 0 to PopupList.Count - 1 do begin
{│}         menuItem := FindItemForCommand(PopupList.Items[MenuIndex], Msg);
{│}         if menuItem <> nil then
{│}           break;
{│}       end;
{│}     end;
{│}     miHint.DoActivateHint(menuItem);
{│}   end;
{│} end;
{└─────────────────────────────────────────────────────────────}
end.

Sunday, December 14, 2014

Software Built With Delphi Celebrates 20 Years

I am pleased to announce the release of Credit Card Math 2014, a program written with Delphi XE4.

When I started this project I had just upgraded to Delphi 2010. If you go back and read my first blog post I mentioned one of the reasons for upgrading to Delphi 2010 was "I need to bring my products up to date (no more 640 X 480 windows) etc." 

It's hard to believe that it's been five years since I first mentioned I needed to upgrade my software. Over the past five years I've learned a lot. I've made a shit-load of mistakes. I've developed new coding habits. I've discovered new components. And most importantly I made a bunch of new friends in the Delphi community.

I still have two other software products that need to be updated. I do not believe it will take me five years to upgrade each of them.

I would like to thank the following people, who's software components helped me with my project:

Ray Konopka, Raize Components
Tim Young, ElevateDB 
Boian Mitov, Basic Video 
Nard Mosely, ReportBuilder
Jordan Russell, Inno Setup

Here are some screen shots of Credit Card Math from 1994 - 2014

Credit Card Math 2014: Delphi XE4
Credit Card Math 2004: Delphi 5
Credit Card Math 1998: Delphi 3
Credit Card Math 1994: Turbo Pascal 5.5
Credit Card Math 1994: Turbo Pascal 5.5

Semper Fi,
Gunny Mike



end.

Wednesday, September 10, 2014

Taking the Mystery out of ListView Column Display

Here is a great article about how to display your data in columns using a ListView. Marjan does a fantastic job explaining how to modify the ListView so it displays your data in a nice column display. I'm definitely using this the next time I have a need.

Thank you Marjan!

How to get a ListView to display your values in columns

Enjoy - Semper Fi,
Gunny Mike
end.

Sunday, July 13, 2014

Video: Effectively Using Raize Components by Ray Konopka

I forgot what the RzMenuController from Raize Component did so I went back and watched the video Ray did at CodeRage 7 to remind me. Then I decided to make this list of links that jumps to a specific item for the next time I forget how to do something with one of these controls.

Effectively Using Raize Components by Ray Konopka
  1. Edit Button (4:04)
  2. Menu Button (5:55)
  3. Menu Controller (7:18)
  4. Tray Icon (9:26)
  5. Line Component (11:29)
  6. Progress Display (12:24)
  7. LED Display (14:27)
  8. DBGrid (15:35)
  9. Track Bar (19:23)
  10. Radio Group (24:42)
  11. Persistence Property Store (27:05)

Enjoy - Semper Fi
Gunny Mike
end.

Friday, July 11, 2014

How to Replace Global Variables by Using a Static Class

One of the first things I used to do when creating a new Delphi application was add a copy of the almighty global.pas unit to the project. I'd immediately go in to this file and start tweaking this and tweaking that so it would work with my new application. And, I made sure to ALWAYS add Globals to the uses clause of every new form I created.

My Delphi buddy Bob Wight, who is Scottish and speaks with a very heavy brogue, called his "Globs". Which stood for globs.pas. I remember him saying, "The programmer's life. Another day, another global." It sounds really cool with his Scottish accent.

Anyway, I thought this was the normal way of doing stuff. I thought every Delphi programmer did this. I never challenged it. Not ever.

Now, as I'm re-learning Delphi all over again for the first time, I hear several Delphi people, in the various Delphi hangouts, say stuff like, "Never use global variables" and "Pretend like global variables don't exist".

I'm thinking to myself... "Okay how the heck do you program if you can't use global variables?"

So, last weekend learned how to replace global variables in my Delphi application by using what is called a static class. It turns out that a static class can simply exist without having to be instantiated. That is pretty cool.

A static class can have constants, variables, functions and procedures. Its like having global variables on steroids. I have replaced my globasl.pas file with a file called ApplicationInfo.pas. I've included a copy of this file below.

This is a fairly new concept for me and it will no doubt go through some changes and refactoring. But so far, I'm liking it.

Here's a couple ways I'm implementing the use of my TAppInfo static class.


The cool part is the Intelisense... I just type TApp and hit [Ctrl] + [Space] and I can pick the item I want. No more "Globs" for this Marine!

ApplicationInfo.pas

Enjoy - Semper Fi,
Gunny Mike
end.

Sunday, July 6, 2014

Just had to share - XE4 includes Beyond Compare

Wow, I just discovered that Delphi XE4 comes with Beyond Compare.

I right-clicked on the project-name from within Project Manager with the intent of choosing the Show in Explorer option when I noticed the Compare option at the bottom. I was pleasantly surprised to see Launch Beyond Compare.

There are two ways to launch Beyond Compare from within the IDE:

 

Makes me think I should spend more time exploring the IDE.

Enjoy - Semper Fi,
Gunny Mike
end.

Friday, July 4, 2014

Choose the Right Folder for Your Application Data

I'm in the process of updating an older program that will now use a local database. I followed my typical "old school" habits and quietly went about my way thinking "I'll just plop the database in a folder called Data that resides in the same location as the exe file. No big deal."

I figured, I'll just use the reliable ExtractFilePath(Application.ExeName) to get the location of where this Data folder needs to go. Right?

Wrong!

I had forgotten to think about how Delphi's "debug" and "release" paths will play into this scenario. Not to mention 32Bit and 64Bit targets. I'm only focused on creating a Windows product at the moment, so for me this means there are a minimum of five paths:

Source
Source\Win32\Debug
Source\Win32\Release
Source\Win64\Debug
Source\Win64\Release

I know I could use "Post-Build" commands (Project > Options > Build Events) to make sure the files are copied from the $(PROJECTDIR) to the $(OUTPUTDIR). However, I was thinking that this is not a very efficient way to go about it. Did I really want to have five copies of a 140MB database cluttering up my hard drive. The answer is no.

So I posed the question "Looking for best practice for using single-user local database." to the Delphi Community on Google+ (https://plus.google.com/u/0/105522328114529031567/posts/Q8go8eWRAfz)

I received several comments and the consensus was to use an application specific subfolder of CommonAppData. Fair enough. CommonAppData it is. What the heck is CommonAppData?

I tend to be a "Show Me" guy I decided to dive into this head long and found out that CommonAppData actually refers to the folder called C:\ProgramData.

Then I wondered what all the other "AppData" type folders were. So I wrote a simple little program to help figure this out. It turns out there are 17 different AppData folders on my computer. I've included a copy of my program for you to use.

AppData Only: Sorted by Path Column

DFM:
object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Paths'
  ClientHeight = 411
  ClientWidth = 804
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnResize = FormResize
  OnShow = FormShow
  DesignSize = (
    804
    411)
  PixelsPerInch = 96
  TextHeight = 13
  object StringGrid1: TStringGrid
    Left = 16
    Top = 39
    Width = 772
    Height = 354
    Anchors = [akLeft, akTop, akRight, akBottom]
    ColCount = 3
    FixedCols = 0
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Tahoma'
    Font.Style = []
    Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goFixedRowClick]
    ParentFont = False
    TabOrder = 0
    OnFixedCellClick = StringGrid1FixedCellClick
  end
  object Button1: TButton
    Left = 16
    Top = 8
    Width = 75
    Height = 25
    Caption = 'Close'
    TabOrder = 1
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 97
    Top = 8
    Width = 75
    Height = 25
    Caption = 'Get Folders'
    TabOrder = 2
    OnClick = Button2Click
  end
  object CheckBox1: TCheckBox
    Left = 184
    Top = 12
    Width = 97
    Height = 17
    Caption = 'AppData Only'
    TabOrder = 3
    OnClick = Button2Click
  end
end


Program:



Enjoy - Semper Fi,
Gunny Mike
end.

Tuesday, May 20, 2014

Changing the Cursor in FireMonkey

I just watched a video on How to Change the Cursor in FireMonkey by Alister Christie. As I watched this video I learned just how much of a Delphi noob I really am. I had no idea you could go from in-line code to class to interface.



Thank you Alister for a great video and mind-opening experience.

Semper Fi,
Gunny Mike
end.

Friday, May 16, 2014

Make the Most of EMBT's DocWiki

I'm just getting ready to explore chapter 3 of Cary Jensen's Delphi in Depth: ClientDataSets and I stopped to research one of the DataSetProvider options - poFetchDetailsOnDemand.

So, I highlighted the poFetchDetailsOnDemand option in the Object Inspector and pushed F1. It brings up the woefully inadequate local help reference. I was expecting to see a list of all the options with their associated meanings.

Then I googled delphi poFetchDetailsOnDemand, not what I was looking for.

So then I googled site:docwiki.embarcadero.com poFetchDetailsOnDemand

Google Search Syntax:
http://google.com/search?q=site%3Adocwiki.embarcadero.com+poFetchDetailsOnDemand

And... poof... perfect... just what I was looking for.

EMBT should redirect the F1-key to do a web search on their DocWiki and do away with the local help.

Enjoy!

Semper Fi,
Gunny Mike
end.

Thursday, April 17, 2014

Are Your Software Sales Where They Should Be?

As an Independent Software Vendor running a one-person, part-time business I'm always wondering if sales are where they should be. If I'm honest with myself I say "No". Trying to understand why can be a very difficult.

I rediscovered this article called "If No Independent Developers Are 100 Times Smarter Than You, Then Why Do Some Get 100 Times the Results?" written by Steve Pavlina when he was President of the Association of Software Professionals.

I remember reading Pavlina's article about 10 years ago and thinking to myself this is something that should be read and re-read often. Unfortunately, I forgot to bookmark the article back then and had a hard time finding it.

Last year I joined the ASP and found this article on one of the resource pages. It's still a good read.

http://asp-software.org/www/misv_resources/business-articles/results/

Enjoy!

Semper Fi,
Gunny Mike
end.

Sunday, March 9, 2014

Get More out of StackOverflow

It's no secret that StackOverflow or SO as it's commonly referred to is one of the best resources for Delphi enthusiasts. We all have some favorite questions, favorite answers and favorite users out there.

What makes StackOverflow so useful are tags. Tags are the single most important feature. Here is a snippet of what you see when you search tags for Delphi:

StackOverflow Tag Search - Delphi


If you mouseover a tag there is a little pop-up that gives a description of the tag along with some very interesting links. One of the most interesting links is the "top users" link. This "top users" link is how you can get more out of StackOverflow.

By following this link you get a list of which users have answered the most questions and which users have asked the most questions relating to this tag. By drilling down onto the users profile, you can see all the answers and questions this user has participated in.

You can very quickly build a list of your favorite StackOverflow Delphi users. Reading answers by these users will most definitely increase your knowledge about Delphi.

Delphi Tags Top Users
http://stackoverflow.com/tags/delphi/topusers
http://stackoverflow.com/tags/delphi-xe5/topusers
http://stackoverflow.com/tags/delphi-xe4/topusers
http://stackoverflow.com/tags/delphi-xe3/topusers
http://stackoverflow.com/tags/delphi-xe2/topusers
http://stackoverflow.com/tags/delphi-xe/topusers
http://stackoverflow.com/tags/delphi-2010/topusers
http://stackoverflow.com/tags/delphi-7/topusers

FireMonkey Tags Top Users
http://stackoverflow.com/tags/firemonkey/topusers
http://stackoverflow.com/tags/firemonkey-fm3/topusers
http://stackoverflow.com/tags/firemonkey-fm2/topusers

Semper Fi,
Gunny Mike
end.

Saturday, March 8, 2014

Delphi Product and Compiler Versions Quick Reference

I was doing some research on Delphi ClientDataSet QC issues at http://qc.embarcadero.com/ and noticed that the Delphi Product Versions are reported as 18.0, 16.4, etc. When an issue is resolved they show the Resolved in Build as XE5 or sometimes they show it as a number such as 17.0.

When I think of Delphi versions I think of XE5, XE4, D7. I don't think of 19, or 18, or 7. Here is an Embarcadero quick reference webpage that lists all the different Product versions for Delphi.

http://docwiki.embarcadero.com/RADStudio/XE5/en/Compiler_Versions

Up Thru Tokyo
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Compiler_Versions


Semper Fi,
Gunny Mike
zilchworks.com

Tuesday, January 14, 2014

Embarcadero's Upgrade Policy is Deceiving

I am so frustrated and angry with Embarcadero right now. You have no idea how angry I am. Embarcadero's upgrade pricing policy is deceiving.


I upgraded to XE4 Enterprise from D2010 Professional in May of 2013. I blogged about my decision on May 19, 2013. (You can read about it here)


My upgrade decision was mostly made based on Embarcadero stating that the upgrade to XE4 is only valid for users of D2010 and higher. I did not want to miss out on an opportunity. It only stood to reason that when XE5 came out Embarcadero would only allow upgrade pricing from XE and higher.


Like all of you, I received all the year end marketing emails from Embarcadero. Needless to say, I did not take advantage of these year end offers.


I continued to receive upgrade notifications from Embarcadero. So, I decided to follow the embedded links within the email and now I discover that Embarcadero is allowing upgrades to XE5 to users of D2009 and higher.


THIS IS AN OUTRAGE


If I had waited a few short months I could have spent my upgrade money and received XE5. Now, I have XE4 Enterprise and it's going to cost me another 1,500 dollars to upgrade to XE5. This has got me so angry I don't know what to do. (I'm one pissed off Marine)


I'm a die-hard Delphi fanatic like most of you are. I have a love/hate relationship with Delphi mainly because it takes so dang long to learn how to do something in Delphi and after you figure out how to do it you say to yourself... "It shouldn't have taken that long."


I'm starting to really resent Embarcadero for what I consider to be deceptive marketing and upgrade policies.


I am not a happy camper right now.


Gunny Mike
end.