64-bit in DataFlex

With DataFlex 2021 and higher, programs can be compiled and run in both 32- and 64-bit. The Studio is 64-bit only, but you can choose to either compile programs to be 32- or 64-bit. So, a single codebase can serve to generate both a 32-bit and 64-bit application. Also, you can run and debug both a 32-bit and 64-bit program from the same 64-bit Studio.

There is a new dropdown selector available to quickly switch between 32- and 64-bit.

This setting can be configured per project and it can also be set through the Project Properties window, on the Compiler tab page.

Both the 32- and 64-bit compiled programs end up in the programs folder. They are differentiated by appending a suffix to the executable name. In the screenshot above, the 32-bit version will be named Order.exe, and the 64-bit version will be named Order64.exe. If you do not differentiate the executables with the appropriate use of suffixes, they would get the same name. Any compilation would overwrite the one that is already there. This is, in fact, the desired situation for a webapp.

The compilation platform and executable name suffix are stored in the project cfg file:

Platform=x64     (or  Platform=x86)

64BitSuffix=64

32BitSuffix=32

For a simple, well-written program, switching to 64-bit and hitting compile may be all that is needed (our examples illustrate this well). However, for more advanced applications, significant changes may be needed. The following sections contain detailed documentation about all the possible changes (though not all may apply to your specific applications).

Data Types

The main difference between 32-bit and 64-bit DataFlex is the fact that the pointer size is increased from 32 to 64 bits. The same is true for Handle. It is important to realize that no pointer in 64-bit mode can be moved to the integer data type because of pointer truncation. Pointer truncation means that a 64-bit value, which exceeds 2^32 is transferred to a 32-bit data type (or smaller), leading to removal of the higher 32 bits, and thus to an incorrect value. Referring to the truncated pointer address will most often be illegal and also lead to a crash.

Handles (Windows data type) are treated differently: although its size is 64-bits in 64-bit mode, its upper half bits will be empty, which means that moving it to the integer type will work and will not truncate the value (with an exception, which is HTreeItem, which is actually a pointer). However, it is not advised to move a Handle to an Integer, but to keep it a Handle at all times.

The integer in 64-bit DataFlex stays 32-bit, which is in line with other Windows environments. Also, for technical reasons, we have added an integer-like data type that is equally sized to the pointer, called Longptr. This data type is only needed in advanced cases.

Longptr Data Type

DataFlex 2021 introduced the new data type Longptr, which is also available when compiling 32-bit. It is a memsize type: it is a 32-bit size integer in 32-bit compilation and a 64-bit size integer in 64-bit compilation. This way, it can always hold a pointer value without being truncated and without needing to use a compiler switch. The single-character identifier for this type is “P”, while Integer is “I” and Timespan has changed to “?”. This makes the following a statement to set a constant to a value of type Longptr:

Define SOME_LARGE_VALUE   for |CP$03762874671

Address and Pointer

In previous versions, Pointer was in fact a replacement for Integer. In DataFlex 2021 and higher, Pointer is a replacement for Address, which is the native pointer type. Address has been made obsolete for clarity and consistency in the documentation. Address can still be used for backward compatibility.

Alias Data Types

This table shows the aliasing of a number of data types. For example, internally, OLE_Handle is not a data type by itself, but it is an alias for a different data type, Integer in this case. To be clear: Longptr is a data type on its own, it is not an alias.

Alias data type

Alias for in 19.1 (and earlier)

NEW Alias for in 32-bit

NEW Alias for in 64-bit

OLE_Handle

Handle

Integer

Integer

Handle

Integer

Longptr

Longptr

Pointer

Integer

Pointer

Pointer

DWord

Integer

UInteger

UInteger

ULongptr

-

Uinteger

UBigInt

The take-away message here is to always implement the data type that it really is: use handle when it is a handle, use pointer when it is a pointer, use integer when it is an integer that will never exceed 2^32, and use Longptr when it is an integer type that may hold a pointer value.

The Longptr and ULongptr types were already available in DataFlex 19.1 as a preparation step towards DataFlex 2021. However, note that in 19.1 Longptr is not a data type on its own, but an alias for Integer. It allowed users to start preparing their code for 64-bit.

DWord

For many years, DWord has been an alias for Integer. In DataFlex 2021 this is changed to an alias for UInteger. This is not as straightforward and simple as it may seem. A realistic consequence would be that assignments of negative values (such as -1) to a DWord, which had always been possible, would now lead to an Out of Range runtime error. We have solved this issue by using value wrapping of Integer and UInteger (DWord), similar to how it works in C/C++.

For example, the binary value of -1 is 0xFFFFFFFF, and when this is assigned to a DWord, it will get the unsigned value of this binary value (which is 4,294,967,295). It also works the other way around: A UInteger with a value larger than 2^32 (e.g. 4,294,963,020) will, when moved to an Integer, be wrapped to a negative value (-4276).

This way, we expect that (almost) all usage of DWord will still just work well, although one may want to check their code on correct usage of DWord.

A related small change is that logical evaluations of UInteger types is made possible. For example:

UInteger uiTest

If (uiTest iand 15) Begin

In previous versions this could not be compiled. Now it will just work.

32-bit DataFlex – Possible Code Changes

Two 64-bit related issues may be relevant to your applications when compiling 32-bit.

DWord

We have changed DWord to be an alias for UInteger instead of Integer. In theory this might have an influence on your code. For example, you could have a test on a DWord variable being less than zero. This would now never be true.

Handle arrays

The Handle data type is an alias, but will no longer be an alias for Integer, but for Longptr. As a consequence, the following code will raise a runtime error in DataFlex 2021 and higher, while it was fine before:

Property Handle[] phStaticViews

Integer[] iStaticViews

Get phStaticViews to iStaticViews

The reason for the error is that an array of handles (Longptr) is moved to an array of a different data type (Integer). In order to correct this, iStaticViews has to be changed to Handle[].

64-bit DataFlex – Possible Code Changes

Simple applications may not need any changes when compiling 64-bit. However, this depends a lot on the complexity, the use of third-party DLL’s and correct data type coding. Below is a list of changes that you may need to make for your application to work in 64-bit.

Compiler Switch

In some cases, changes must only be active for the 64-bit environment, not 32-bit, for which you can use the new compiler switch IS$WIN64.

Example:

#IFDEF IS$WIN64

    #Replace LONGPTR_DTSIZE 8

#ELSE

    #Replace LONGPTR_DTSIZE 4

#ENDIF

Illegal Data Type Conversions

In 64-bit mode (not 32-bit), some conversions are now illegal, due to the risk of data loss (pointer truncation). For this reason, in 64-bit, conversions from Pointer or Address to Integer are not allowed. They will lead to runtime illegal conversion errors, whether or not overflow would happen. So, whenever such conversions are in your code, they will become problematic in 64-bit. The reverse, conversion from Integer to Pointer/Address is not illegal, but in 64-bit they make no sense in most cases. Conversions from Longptr to Integer or Pointer or Address and vice versa are allowed and most often do make sense.

Moving a pointer value to an integer type could be considered sloppy coding and has never been an advisable thing to do. When porting to 64-bit, it’s time to correct that. The advised way to change this is to always use the Pointer data type, i.e. in local data types and function/procedure parameters. Alternatively, you could also use the new Longptr data type.

The biggest issue here is finding the illegal conversions (Pointer to Integer) since many of them only show up at runtime. A simple move of a pointer to an integer will be detected by the compiler, though, and reported as compile error. At this moment, a global search for the keywords Pointer and Address and the function AddressOf is likely to be a good start to find more possible problems.

Correct Data Type Usage

In general, it is advised to use the right data type for each variable and parameter in order to prevent potential problems. This is even more important in 64-bit. As mentioned above, pointer must be used correctly. Also, it is advised to use OLE_Handle for OLE Handles and Handle for all other handles, even though nothing will go wrong when mixing them up or using Integer or DWord (except when using arrays). One exception is the HTreeItem data type. While this looks like a handle, it is actually (in Windows) a pointer to a struct. The correct data type here is either Pointer, Longptr or Handle (not Integer or DWord).

External Functions

When implementing data types for input and output parameters of external functions, the data type used must match that of the Windows function. While implementations work in 32-bit, they might break in 64-bit. This table may be helpful in getting it right:

Windows data type            

Advised DataFlex type in
external function

Allowed alternatives

Handle, hWnd, hTreeItem, HItemList, HInstance, hIcon, HGlobal, HDC, etc.

Handle

Longptr / Pointer

Pointer (such as: VOID *lpx), LPCSTR, LPCTSTR, LPVOID, PUINT, LPDWORD (almost everything that starts with LP)

Pointer

Longptr

OLE_Handle

OLE_Handle

Integer / Uinteger / Dword

lParam, wParam, lResult

Longptr

Pointer

DWord

DWord

Integer / UInteger / OLE_Handle

Size_t, UINT_PTR, DWORD_PTR

ULongptr

Longptr / Pointer

INT_PTR, LONG_PTR

Longptr

Pointer

INT, INT32, UINT, UINT32, LONG, ULONG, etc.

Integer / UInteger

Dword / OLE_Handle

SHORT, INT16, UINT16, WORD

Short / UShort

 

BYTE

UChar

 

BOOL     

Integer

DWord / OLE_Handle

DWORDLONG

UBigInt

 

 

In particular, look out for parameters named LParam and WParam that are typed integer. Those will most often have to be corrected to Longptr.

Users may face a very peculiar and rare problem when using the POINT structure by value in an external function. Since DataFlex cannot pass structs by value, one should pass them using pointer. However, there are Windows functions that only accept them by value. For a solution to this, take a look at the system package Winuser.pkg.

Structs

Similar to external functions definitions, data types in Windows structs must be typed correctly when they are exposed to the outside world. In addition, there is an issue called structure alignment (or structure padding), when structs are passed to other DLLs or Windows functions. This is because the (C) compiler ensures that each struct instance will have the alignment of its widest scalar member (for performance reasons) and extra memory space may be inserted within the struct (unless structure alignment is explicitly switched off in the DLL). Please look at the documentation below for more information.

DataFlex does not do structure alignment. This means that you might have to add extra padding items yourself to exposed Windows structs. This issue is especially relevant to the 64-bit platform. Take this example:

Struct tWinChooseFont

  DWord lStructSize

  Handle hwndOwner

End_Struct

In 32-bit, both DWord and Handle are 32-bit items (4 bytes), which does not lead to any padding. However, in 64-bit, Handle has become 64-bit (8 bytes) and that causes the struct to have 8-byte alignment, which means that in Windows compilers there will be 4 bytes of space inserted after lStructSize. If this doesn’t get corrected in 64-bit environments, there can be an unexpected runtime error or crash upon calling the external function. The solution in DataFlex code is:

Struct tWinChooseFont

  DWord lStructSize

#IFDEF IS$WIN64

  Integer iStructAlignment

#ENDIF

  Handle hwndOwner

End_Struct

Be aware that such changes might influence code where you do a SizeOfType() on that struct.

Note: The structure alignment issue is not relevant to structs in COM class interfaces. In that case, structs will be exposed correctly.

More information:

https://msdn.microsoft.com/en-us/library/ms253935.aspx

http://www.catb.org/esr/structure-packing/#_structure_alignment_and_padding

Third Party Binaries

This is probably often the biggest hurdle for making applications ready for 64-bit. Any 32-bit third party dependency must be replaced by a 64-bit version, since a mix of 32- and 64-bit binaries is not possible. This means that you may have to request a 64-bit version from the vendor or, when you are in possession of the code yourself, recompile it in 64 bits (and solve the possible issues that come with it). Theoretically, when neither option is possible, you may have to find another solution, such as removing the 3rd party library from the application or replacing it with an alternative component.

DataFlex applications can be compiled either 32- or 64-bit (using the same codebase). To use the right version, you can use a compiler switch to use either the 32- or 64-bit component:

#IFDEF Is$Win64

    External_Function FuncName "FuncName" xxx64.dll Integer iLength Returns Handle

#ELSE

    External_Function FuncName "FuncName" xxx32.dll Integer iLength Returns Handle

#ENDIF

 

COM Classes (Generated by the COM Class Generator)

The COM class generator can take a DLL or OCX as input for generating DataFlex wrapper classes. It can do that for both 32- and 64-bit binaries, which may result in either equal or dissimilar generated pkg-files depending on the contents. The CLSIDs may be equal, but that must be validated (Windows knows whether to use the 32- or 64-bit DLL when CLSIDs are equal). The classes in the generated PKG file will in many cases be identical, because the COM Class Generator will use Longptr (or ULongptr) for pointer-sized C-types like INT_PTR. This is the full list of supported platform dependent C data types that convert to either Longptr or ULongptr: INT_PTR, LONG_PTR, LPARAM, HMODULE, ULONG_PTR, UINT_PTR, WPARAM.

However, when the COM object signature of 32- and 64-bit is different, for example with C data type LONG in 32-bit and __int64 in 64-bit (by using compiler switches), the generated classes will be different. Obviously, the COM class generator has no way of knowing about a platform-dependent type here. When there are only a few differences, one might decide to use just one class PKG file and edit it manually to use IS$WIN64 switches. You can also use the define OLE_VT_INT_PTR , which is a replacement for OLE_VT_I4 in 32-bit and OLE_VT_I8 in 64-bit. Alternatively, when there are many changes or when the CLSIDs are different, it might be a better choice to have two files and use an IS$WIN64 switch for the USE statement for that file.

GetWindowLong and Others

Although the Windows functions GetWindowLong, SetWindowLong, GetClassLong and SetClassLong are still available on the 64-bit platform, they will lead to errors when used with pointer values, because of pointer truncation. Therefore, it is strongly advised to replace those functions by the respective xxxPtr functions, such as GetWindowLongPtr, which will work on both 32- and 64-bit platforms. So, it is not needed to use a compiler switch to use either of the two. All that is needed is to add “Ptr” to the name of the function call to make the code work well in both 32- and 64-bit.

 

See Also

Unicode in DataFlex

What's New in DataFlex 2022

What's New in DataFlex 2021