RAD Studio for Microsoft .NET
ContentsIndex
PreviousUpNext
Language Issues in Porting VCL Applications to RAD Studio

The VCL in RAD Studio was created with backward compatibility as the primary goal. However, there are some ways in which the managed environment of .NET imposes differences in the way VCL applications must work. This document describes most of these differences, and indicates some of the steps you should take to port a VCL application to the .NET environment. 

This document does not attempt to describe the new extensions to the Delphi language. It is limited to the way existing Delphi code maps to the new RAD Studio language and VCL framework. This document does contain links into specific topics within the Delphi Language Guide, where new language features are explained in detail. 

This topic covers the following material:

  • Migrating Pointer types
  • Migrating Char and string types
  • Creating and destroying objects
  • Calling the Win32 API
  • Migrating Variants
  • Working with resources
  • Change to OnCompare

Pointer types are not CLS compliant, and are not considered "safe" in the context of the .NET Common Language Runtime environment. The port of the VCL has, therefore, eliminated pointers, replacing them with appropriate alternatives such as dynamic arrays, indexes into an array or string, class references, and so on. When porting a VCL application, one of the first steps is to locate where you use pointer types and replace them as appropriate.

Untyped Pointers

Untyped pointers are considered unsafe code. If your code includes untyped pointers, the .NET utility PEVerify will fail to verify it. Code that cannot be verified for type safety cannot be executed in a secured environment, such as a web server, SQL database server, web browser client, or a machine with restricted security policies. 

In the VCL, untyped pointers have been replaced with more strongly-typed values. In most cases, where you used to find an untyped pointer, you will now find TObject. For example, the elements of TList are now of type TObject, rather than of type Pointer. Your code can cast any type to an object, and cast a TObject to any other type (even value types such as Integer, Double, and so on). Casting TObject to another type will generate a runtime error if the object is not, in fact, an instance of the type to which you are casting it. That is, this cast has the same semantics as using the as operator. 

In some cases, the Pointer type has been replaced with a more precise type. For example, on TObject, the ClassInfo function returns a value of type Type rather than an untyped pointer.  

Untyped pointers that were used for parameters whose type varied depending on context have typically been replaced by overloading the routine and using var parameters with the possible types. In the case of untyped pointers that are used with API calls to unmanaged code (such as the Windows API or calls to a data access layer such as the BDE) the untyped pointer is replaced with System.IntPtr. Thus, for example, the TBookmark type, defined in the Db unit, now maps to IntPtr. 

Code that used the address operator (@) to convert a value to an untyped pointer must now change. When the untyped pointer has changed to TObject, usually all you need to do is eliminate the @ operator. On value types, you may need to replace the @ operator with a typecast to TObject, so that the value is "boxed". Thus, the following code

var
  P: Pointer;
  I: Integer;
begin
  I := 5;
  P := @I;

could be converted to

var
  P: TObject;
  I: Integer;
begin
  I := 5;
  P := TObject(I);

When the untyped pointer has changed to IntPtr, you need to use the Marshal class to allocate a chunk of unmanaged memory and copy a value to it, rather than just using the @ operator. Thus the following code:

var
  P: Pointer;
  R: TRect;
begin
  R := Rect(0, 0, 100, 100);
  P := @R;
  CallSomeAPI(P);

would be converted to

var
  P: IntPtr;
  R: TRect;
begin
  R := Rect(0, 0, 100, 100);
  P := Marshal.AllocHGlobal(Marshal.SizeOf(TypeOf(TRect)));
  try
    Marshal.StructureToPtr(TObject(R), P, False);
    CallSomeAPI(P);
  finally
    Marshal.FreeHGlobal(P);
  end;

Note: All unmanaged memory that you allocate using the Marshal class must be explicitly freed. The .NET garbage collector does not clean up unmanaged memory.

Procedure Pointers

A special case for untyped pointers is when they represent procedure pointers. In managed code, procedure pointers are replaced by .NET delegates, which are more strongly typed. Declarations of procedural types are delegate declarations in RAD Studio. You can obtain a delegate for a method or global routine using the @ operator. The code looks the same as obtaining a procedure pointer on the Win32 platform, so in many cases there is nothing you need to change when porting code. However, it is important to keep in mind that when you use the @ operator, you get a newly-created delegate, not a pointer.  

If you are passing a procedure pointer to an unmanaged API using the @ operator, for example,

Handle := SetTimer(0, 0, 1, @TimerProc);

the only reference to the delegate is the one passed to the API call because the delegate is created on the fly. This means that the garbage collector will eventually dispose of the delegate after the return of the unmanaged API. If, as in this case, the unmanaged code may call the procedure after the return of the API call, you will encounter a runtime exception because the delegate no longer exists. You can work around this situation by assigning the delegate to a global variable, and passing the global variable to the unmanaged API. 

When you call the Windows API GetProcAddress to obtain a procedure pointer, it is returned as an IntPtr. This value is not a delegate. You can’t cast it to a delegate and call it. Instead, typically such code is translated to use Platform Invoke to call an unmanaged API. GetProcAddress is useful to determine whether the API is available so that you do not get a runtime exception when you use Platform Invoke. Thus, code such as the following:

type
  TAnimateWindowProc = function(hWnd: HWND; dwTime: DWORD; dwFlags: DWORD): BOOL; stdcall;
var
  AnimateWindowProc: TAnimateWindowProc = nil;
  UserHandle: HMODULE;
begin
  UserHandle := GetModuleHandle('USER32');
  if UserHandle <> 0 then
    @AnimateWindowProc := GetProcAddress(UserHandle, 'AnimateWindow'); 
  ...
  if AnimateWindowProc <> nil then
    AnimateWindowProc(Handle, 100, AW_BLEND or AW_SLIDE);

Would be translated to the .NET platform as follows

[DllImport('user32.dll', CharSet = CharSet.Ansi, SetLastError = True, EntryPoint = 'AnimateWindow')] 
function AnimateWindow(hWnd: HWND; dwTime: DWORD; dwFlags: DWORD): BOOL; external;
var
  UserHandle: HMODULE;
  CanAnimate: Boolean;
begin
  UserHandle := GetModuleHandle('USER32');
  if UserHandle <> 0 then
    CanAnimate := GetProcAddress(UserHandle, 'AnimateWindow') <> nil
  else
    CanAnimate := False;
  ...
  if CanAnimate then
    AnimateWindow(Handle, 100, AW_BLEND or AW_SLIDE);

Note: The .NET example above is still late bound to the AnimateWindow API. An exception will not be generated when this code is loaded, if the DLL or function aren't available. The function call is resolved only when the code is executed for the first time.

String Pointers

Code that uses the PChar type usually serves one of three purposes:

  • The type refers to a null-terminated string (especially when it is used with a Windows API call or an older RTL function).
  • The type is used to navigate through a string when processing its value.
  • The type is used to reference a block of bytes, relying on the fact that in Delphi for Win32, the Char type is a byte (the Char type is two bytes on the .NET platform).
In the first case, you can usually replace the PChar type with the type string. In the case of Windows API calls, the managed versions of the APIs now use a string or StringBuilder rather than a PChar, with the marshaling layer handling the conversions implicitly. Note that many of the RTL functions that supported the PChar type have been eliminated from the RTL, and you must replace them with corresponding versions that use the string type. The following table lists functions from the SysUtils units that have been eliminated because they relied on the PChar type, and the corresponding functions that use the string type:

PChar version 
String version 
AnsiExtractQuotedStr  
AnsiDequotedStr or DequotedStr  
AnsiLastChar, AnsiStrLastChar  
(use index operator and string length)  
AnsiStrComp, StrComp  
CompareStr, AnsiCompareStr, WideCompareStr  
AnsiStrIComp, StrIComp  
CompareText, AnsiCompareText, WideCompareText  
AnsiStrLComp, StrLComp  
System.String.Compare (StartsStr)  
AnsiStrLIComp, StrLIComp  
System.String.Compare (StartsText)  
AnsiStrLower, StrLower  
AnsiLowerCase, WideLowerCase,  
AnsiStrUpper, StrUpper  
UpCase, AnsiUpperCase, WideUpperCase  
AnsiStrPos, StrPos, AnsiStrScan, StrScan  
Pos  
AnsiStrRScan, StrRScan  
LastDelimiter  
StrLen  
Length  
StrEnd, StrECopy  
(no equivalent)  
StrMove, StrCopy, StrLCopy, StrPCopy, StrPLCopy  
Copy  
StrCat, StrLCat  
  • operator, Concat
 
StrFmt  
Format, FmtStr  
StrLFmt  
FormatBuf  
FloatToText  
FloatToStrF  
FloatToTextFmt  
FormatFloat  
TextToFloat  
FloatToStr  

When a PChar type is used to navigate through a string, you must rewrite the code, replacing the PChar with an Integer that represents an index into the string. When rewriting such code, you must is recognize when you have reached the end of the string. When using the PChar type, there is a null character at the end of the string, and the code typically recognizes the end of the string by finding this null character. With a string-and-index approach, there is no such null character and you must use the string length to identify the end of the string. Be careful to check that the index is not past the end of the string before reading a character or you will get a runtime error.

Note: String data is immutable, so you can’t write a single character into an existing string using a PChar
. You can accomplish this using string indexing (e.g. s[5]), however. When a PChar is used to reference a block of bytes, it is typically replaced by either an IntPtr or a dynamic array of bytes ( TBytes). If replaced by an IntPtr, the issues in translating are the same as when replacing an untyped pointer. When replaced by TBytes, you my need to replace some PChar values with an index into the byte array if it is used to navigate the block of bytes. This is like replacing PChar with Integer to navigate through a string, except that indexes into TBytes are 0-based while indexes into strings are 1-based.

Writing Strings to Streams

In Delphi for Win32, it is common to find code similar to the following:

S1 := 'This is a test string';
Stream.WriteBuffer(S1[1], Length(S1));

On the Win32 platform, this code results in the entire string being written to the stream. On the .NET platform however, this same code produces a quite different result. On the .NET platform, the compiler generates a call to the Char overloaded version of WriteBuffer, with the result being only a single character (S1[1]) being written to the stream.

Other Pointer Types

Other typed pointers have been eliminated from the VCL. Typically, they are replaced by the type to which the original pointed. If the pointer type was the parameter to a procedure call, it is typically converted to a var parameter so that the resulting code still passes a reference rather than a copy of the argument. Sometimes, it is useful to change a value type into a class type so that rather than passing a typed pointer, your code passes an object reference.

In RAD Studio, the string type maps to the .NET String type, and you can freely access the members of String using a Delphi string type, as demonstrated in the following example:

var
  S: string;
begin

  S := 'This is a string';

  // Note the typecast is not necessary.
  // S := System.String(S).PadRight(25);  


  // Direct access to string class members
  S := S.PadRight(25);
  S := ('This is a new string').PadRight(25);

 

ANSI Strings and Wide Strings

The biggest difference for strings in RAD Studio is that the string type is now a Unicode wide string rather than an AnsiString. This simplifies code for some locales, because you no longer need to worry about multibyte character sets. However, you must examine your code for any assumptions about the size of a Char, because it is now two bytes rather than one. You can still use strings with one-byte characters, but you must now declare them as AnsiString rather than string. The compiler converts between wide and narrow strings if you use an explicit typecast or if you implicitly cast them by assigning to a variable or parameter of the other type. 

If your code calls any of the AnsiXXX routines for manipulating strings, you may want to change these to the corresponding wide string version of the routine. The AnsiXXX routines have (deprecated) overloads that map to the wide versions, and the overloaded routines accept wide strings for their parameters; this avoids implicit conversion back and forth between wide and single-byte strings.

Note: Information can be lost when converting from wide to single-byte characters, therefore, you should avoid downcasting as much as possible.

String Operations

Following the CLR value-type semantics, typically operations on strings return a copy of the string rather than alter the existing string. This may make some code less efficient, because there is more copying going on. For example, consider the following:

var
  S: string;
begin
  S := 'This is a string';
  S[3] := 'a';
  S[4] := 't';

When compiled using on the Win32 platform, the character substitutions only require a single byte of memory to change each time. In RAD Studio, each substitution results in a copy of the entire string. Because of this, it is a good idea to use a StringBuilder instance when you are manipulating string values. StringBuilder allocates a chunk of unmanaged memory and manipulates the string the way you expect. When you are finished, you can convert the result to a string by calling the ToString method.

Note: The conversion to string
from a StringBuilder is a low-cost operation. The string data is not copied again.

Uninitialized Strings

In RAD Studio, an uninitialized string has the value of nil. The compiler will automatically compensate if you compare an uninitialized string with an empty string. That is, if you have a line such as

if S <> '' then ...

The compiler handles the comparison and treats the uninitialized string as an empty string. However, unlike code compiled on the Win32 platform, other string operations do not automatically treat an uninitialized string like an empty string. This can lead to Null Object exceptions at runtime.

Unlike Delphi for Win32, in RAD Studio, there is no distinction between an explicit typecast and the as operator. In both cases, the cast only succeeds if the variable being cast is really an instance of the type to which you cast it. This means that code which used to work (by casting between incompatible data types) may now generate a runtime exception.

Message Crackers

Perhaps the most common situation where the change to typecasts causes a problem is in the use of the message cracker types. In the VCL on Win32, the Messages unit defined a number of record types to represent the parameters of a Windows message. These records were all the same size, with the fields laid out to extract the information from the Windows message. Thus, you could have the message parameters in one form (say, TMessage), and typecast it to another (say TWMMouse), and extract the information you wanted. This worked because the two types were the same size, and an explicit typecast did not raise an exception when you reinterpreted the type with the cast. Such a reinterpret cast is not allowed in .NET, and the same code would lead to an invalid cast exception in RAD Studio. 

To work around this situation, the message cracker types in RAD Studio are not records at all, but classes. Instead of casting a TMessage value to another type such as TWMMouse, you must instantiate the other type, passing the original TMessage as a parameter. That is, instead of

procedure MyFunction(Msg: TMessage);
var
  MouseMsg: TWMMouse;
begin
  if Msg.Msg = WM_MOUSE then
    with Msg as TWMMouse do
    ...
end;

you would do something like the following:

procedure MyFunction(Msg: TMessage);
var
  MouseMsg: TWMMouse;
begin
  if Msg.Msg = WM_MOUSE then
    with TWMMouse.Create(Msg) do
    ...
end;

To convert in the other direction (from a specialized message type to TMessage), you can use the new UnwrapMessage function that is declared in the Messages unit.

Accessing Protected Members from Classes in Other Units

Another technique that involves what is now an invalid typecast is when you need to access the protected members of a class that is declared in another unit. In Delphi for Win32, you can declare a descendant of the class whose members you want to see:

type
  TPeekAtWinControl = class(TWinControl);

Then, by casting an arbitrary TWinControl descendant to TPeekAtWinControl, you could access the protected methods of TWinControl, because TPeekAtWinControl was defined in the same unit. 

In general, this technique does not work in RAD Studio, because the arbitrary TWinControl descendant is not, in fact, an instance of TPeekAtWinControl. The cast leads to an invalid cast exception at runtime. 

Because this is a widely used technique in Win32, the compiler will recognize this pattern and allow it. However, the compiler can't know what assembly a unit will be linked into when it compiles the source code. If the units are linked into assemblies, this technique will fail at runtime with a type exception. 

When you need to cross assembly boundaries, one workaround is to introduce an interface that provides access to the protected members in question. Some of the classes in the VCL ( TControl, TWinControl, TCustomForm) now use this technique, and you can find the addition of interfaces to access protected members (IControl, IWinControl, IMDIForm).

Specific language issues with programming in Delphi on the memory-managed .NET platform are explained in the topic Memory Management Issues on the .NET Platform

Because of differences in the way objects are instantiated and freed, it is not possible to have a BeforeDestruction or AfterConstruction method on a RAD Studio class. Any classes that override these methods must be rewritten. 

The fact that these methods and the OldCreateOrder property do not exist in the VCL on the .NET platform impacts forms and data modules that relied on OldCreateOrder being False. The OnCreate and OnDestroy events now act as if the OldcreateOrder property is set to True, and will only be called from the constructor or destructor.

Note: Because OnDestroy is called from a destructor, it is not guaranteed to be called – if the application does not call Free, the object’s destructor is not called, even though it is garbage collected.

Most of the VCL is designed for working with the Windows API. This is handled in a way analogous to the way Systems.Windows.Forms works: The VCL is a managed API that calls into the Windows API, marshaling between the managed structures on the VCL side and the unmanaged types that the Windows API uses. Some units, particularly in the RTL, have been ported so that they sit on top of CLR rather than the Windows API. Such units are more flexible, because they can work with any .NET environment, even those that do not support the Windows operating system (for example, the Compact Framework, Mono, and so on). Units that require the Windows operating system are tagged with the platform directive. In units that are not tagged with the platform directive, any methods or classes that require Windows are tagged with the platform directive.

Isolating Windows Dependencies

In order to maintain relative platform independence in RTL units, some methods functions that rely on Windows have been moved into the WinUtils unit. In addition, some classes have been changed to rely more on CLR than Windows. 

TObject, Exception, TPersistent, and TComponent, all map directly to classes implemented in the .NET Framework. In this way they integrate more smoothly with other .NET applications. Because the corresponding CLR classes (System.Object, System.Exception, System.Marshal, and System.Component) do not include all the methods that the VCL requires, the missing methods are supplied by Delphi class helper declarations. In most cases, this mechanism is transparent. However, there are a few cases where it requires you to make minor tweaks to your code. For example, with TComponent, FComponentState is now a property of TComponentHelper rather than a true field of TComponent. This means that you can’t use the Include and Exclude methods on FComponentState, because when passed a property, they operate on a copy of the property value, which does not alter FComponentState. Thus code such as

Exclude(FComponentState, csUpdating);

Must be rewritten as

FComponentState := FComponentState – [csUpdating];

TThread has also been changed to map to the CLR thread object. This means that the Thread handle is no longer an ordinal type, but is rather a reference to the underlying CLR thread object. It also means that TThread no longer supports a ThreadID, which is not supported by the CLR thread object. If your thread class requires a ThreadID, you should change it to derive from TWin32Thread instead.

Calling the Windows API

Many Windows APIs have changed to use a more managed interface. Often, the types of parameters have changed, typically to eliminate pointers. One common change is the PChar types have been replaced by string or StringBuilder. 

When your application calls a Windows API, it is making a call into an unmanaged DLL. Because of this, all parameter values must be marshaled into unmanaged memory, where Windows can work with it, and results are then unmarshalled back into managed memory. In most cases, this marshaling is handled automatically, based on the attributes that have been added to API declarations or type declarations. There are some cases, however, when your code must explicitly handle the marshaling – especially when dealing with a pointer on a structure. To do this marshaling, use the System. Marshal class. Another class that can be very useful when marshaling data to or from unmanaged memory is the BitConverter class. For example, the Marshal class does not include a method to read or write a double value, but it can read or write Int64 values, which are the same size, and the BitConverter class can convert these to or from doubles:

// copy double into unmanaged memory:
Mem := Marshal.AllocHGlobal(SizeOf(Int64));
Marshal.WriteInt64(Mem, BitConverter.DoubleToInt64Bits(DoubleVariable));
...
// copy double from unmanaged memory
DoubleVariable := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Mem));

When using the marshal class, remember that you must always free any unmanaged memory you allocate – the garbage collector does not collect unmanaged memory.

Working with Windows Messages

One of the changes in the way RAD Studio applications work with Windows is the way message handlers work. The basics of declaring and using messages handlers is the same, but the message-cracker types have changed from records to classes, and you can no longer simply typecast from one message-cracker type to another. Most of this has already been covered in the section on typecasts, but there are a few additional issues that bear mentioning:

  • When porting code that sends a message, it is no longer sufficient to declare the message cracker on the stack, fill out its fields, and pass it to a call to SendMessage. You must now add a call to create the message cracker, because it is now a class.
  • Inside a message handler, you can still call an inherited message handler using the inherited keyword. However, if you do this, you must now be sure that the message cracker type is the same as that in the inherited message handler. For example, if the inherited message handler has a parameter of type TWMMouse, and your message handler only needs TMessage, declaring your message handler to use TMessage and calling inherited will lead to an invalid cast exception at runtime. Thus, if you call the inherited message handler, you must now ensure that your message parameter matches that of the inherited handler.
  • If a message has parameters that are pointers to records (or pointers to anything, for that matter), then the corresponding message cracker will have properties that represent those records. It is important to realize, however, that these are properties and not fields. Thus, you can read the fields of the record directly from the property, but if your handler needs to change any field values, you can no longer make assignments directly to the fields of the record. Instead, you must copy the record to a local variable, make your changes, and then assign the result back to the property.
Using Windows messages is somewhat more expensive in RAD Studio, because in addition to the overhead of working with the message queue, there is now the overhead of marshaling values to and from unmanaged memory. This is particularly expensive when a parameter represents a pointer (an object reference or a pointer to a structure). Such parameters are ultimately converted to a WPARAM or LPARAM using an IntPtr, which acts as a handle to a block of unmanaged memory that contains a copy of the structure. Object references are converted using a GCHandle. In most cases, the predefined message cracker types handle the marshaling of these parameters, to and from the IntPtr, but if you defining your own messages, you may need to perform your own marshaling. The message cracker classes defined in the Controls unit illustrate how to handle these marshaling issues. 

The VCL defines and uses a number of private message types. These are, for the most part, defined in the Controls unit, and have identifiers of the form CM_XXX or CN_XXX. Because of the extra overhead in marshaling messages, several of the CM_XXX message types have been changed or eliminated, replaced by other mechanisms that are less expensive in the .NET environment. The following table lists the message types that have changed, and how the same task is accomplished in RAD Studio:

Message type 
Change 
CM_FOCUSCHANGED  
Replaced by a protected method (FocusChanged) on TWinControl. Replace message handlers by an override to the FocusChanged method. Instead of sending messages, call FocusChanged using the IWinControl interface.  
CM_MOUSEENTER  
Meaning of LPARAM has changed. It used to pass an object reference to the child control where the mouse entered – now it passes the index of that child in the FWinControls or FControls list.  
CM_MOUSELEAVE  
Meaning of LPARAM has changed. It used to pass an object reference to the child control where the mouse exited – now it passes the index of that child in the FWinControls or FControls list.  
CM_BUTTONPRESSED  
Replaced by a protected method (ButtonPressed) on TSpeedButton. This was only used by TSpeedButton. The CMButtonPressed message handler was replaced by ButtonPressed, which is called directly.  
CM_WINDOWHOOK  
Retired. TApplication.HookMainWindow and TApplication.UnhookMainWindow are both public methods that can be called directly.  
CM_CONTROLLISTCHANGE  
Replaced by a protected method (ControlListChange) on TWinControl. Replace message handlers by an override to the ControlListChange method.  
CM_GETDATALINK  
Replaced by a protected method (GetDataLink) on various data-aware controls. Call this using the new IDataControl interface. When creating your own data-aware control (that does not descend from an existing class in DBCtrls), you must implement IDataControl if the control is to work in a DBCGrid.  
CM_CONTROLCHANGE  
Replaced by a protected method (GetDataLink) on various data-aware controls. Call this using the new IDataControl interface. When creating your own data-aware control (that does not descend from an existing class in DBCtrls), you must implement IDataControl if the control is to work in a DBCGrid.  
CM_CHANGED  
Meaning of LPARAM has changed. It used to pass an object reference, now it passes a hash code for the object that changed.  
CM_DOCKCLIENT  
Replaced by a protected method (DockClient) on TWinControl. Replace message handlers by an override to the DockClient method.  
CM_UNDOCKCLIENT  
Replaced by a protected method (UndockClient) on TWinControl. Replace message handlers by an override to the UndockClient method.  
CM_FLOAT  
Replaced by a protected method (FloatControl) on TControl. Replace message handlers by an override to the FloatControl method.  
CM_ACTIONUPDATE  
Retired. TApplication.DispatchAction was promoted to public, and is called directly rather than using a message.  
CM_ACTIONEXECUTE  
Retired. TApplication.DispatchAction was promoted to public and is called directly rather than using a message.  

 

Changes to the Threading Model

Sometimes, Windows API calls require the use of the Single Threaded Apartment (STA) model to function properly on some operating systems. For example, on some versions of Windows 98, the Open and Save dialogs do not work unless your RAD Studio application uses the Single Threaded Apartment model. Any portion of the VCL that uses COM requires this model. 

The threading model is established when the process first starts up. If you are creating an executable, this is easy: just add the [STAThreadAttribute] attribute to the line immediately preceding the begin statement in the dpr file. When creating a DLL, you can’t force the threading model. However, you can call the CheckThreadingModel procedure in the SysUtils unit to raise an exception when the application calls a method that requires a particular threading model. 

This restriction is fairly common in .NET. By default, Microsoft Visual Studio adds the STAThreadAttribute attribute to applications it creates.

The Variant type is very different in RAD Studio. Whereas the Win32 compiler maps Variant onto the record type that COM uses for Variants, in RAD Studio, a Variant is more general. Any object (which in RAD Studio is any type) can act be manipulated as a Variant. Thus, in RAD Studio, you could assign a control to a Variant. 

The Delphi Variant type is a Delphi language notion that is not CLS compliant. If you are writing code in RAD Studio that uses Variants, to the outside world will see, these will map to only as System. Object. Thus, to code written in other languages, the flexibility in type conversions that Delphi Variants support provide is not available.

Changes to TVarRec

If your code uses Variants, chances are it should still work. However, because Variants are no longer based on the TVarRec type, any code that works with the internals of a Win32 Variant by getting into the underlying TVarRec record must be rewritten for .NET.

Note: Nearly all of the functions provided by the Variants unit are implemented in RAD Studio. If you need to get the VarType of a Variant, you can accomplish this and still maintain platform portable code.

Changes to OLE Variants

The COM Interop layer automatically marshals Objects (and hence Variants). Thus, you can use RAD Studio Variants with COM. However, when using RAD Studio Variants with COM, you should restrict the types you assign to the Variant to COM-compatible types. 

In Delphi for Win32, the compiler enforces COM restrictions on the kinds of data that can be assigned to an OleVariant. In RAD Studio, OleVariant is simply a synonym for Variant. It does nothing to ensure that the Variant value is a COM-compatible type.

Changes to Custom Variants

Custom Variants are completely different in RAD Studio. Because Variants are just objects, you do not need to do anything at all to create a custom Variant – any class you define is already a Variant type. However, to work well as a custom Variant, it helps to implement some CLR interfaces: IComparable, IConvertible, and ICloneable. The Delphi compiler can use these to implement Variant operations. Even with these interfaces, however, other, arbitrary Variant types, can’t be converted into your Variant (class) unless you implement a FromObject method:

class function FromObject(AObject: System.Object): TObject; static;

FromObject takes an arbitrary source object (the Variant to convert to your class type) and returns the corresponding instance of your class as a TObject.

RAD Studio can link Windows resources (res files) into your assemblies. This means that when first porting an application, you do not need to change the way you declare and use resources, and it will still work. In some cases, this is what you want to do anyway. For example, if you use custom cursors, it is simpler to use the Windows API LoadCursor function to add the cursor to TScreen.Cursors than to bring in the overhead of using Cursor and then obtaining a handle to the underlying cursor. However, for resources that are not Windows-specific (such as bitmaps, icons, and strings) you will probably want to update to a .NET resources file.

Resource Strings

When you use the resourcestring keyword, RAD Studio automatically creates the string resources as .NET resources rather than Windows resources. This happens automatically and there is nothing special you need to do. The one thing to watch out for is that you no longer can use the PResStringRec type.

Bitmaps

You can convert bitmaps into .NET resources using the ResourceWriter class. The resulting resources file can be linked into your RAD Studio application, or deployed as a satellite assembly. To use these converted bitmaps, LoadFromResourceName has new overloads for working with .NET resources (and the old version of LoadFromResourceName as well as the LoadFromResourceID method have been deprecated.) Thus, for example, if your bitmaps are in a resources file with a name such as MyResources.en-US.resources, you can load your bitmap as follows

MyBitmap.LoadFromResourceName('MyFirstBitmap', 'MyResources', System.Assembly.GetCallingAssembly);

Note that this example assumes the resources are compiled into the assembly that is making the method call that contains this line. If the resources are compiled into a different assembly, you can use System.Assembly.GetAssembly (using a type that is defined in the relevant assembly) or System.Assembly.GetExecutingAssembly (to obtain the currently executing assembly).

The signature for the OnCompare event in the TTreeView class has changed in the VCL for .NET. Existing code will cause a runtime exception when the event handler is called.  

In Delphi 7, the signature was:

TTVCompareEvent = procedure(Sender: TObject; Node1, Node2: TTreeNode; Data: Integer; var Compare: Integer) of object;

In Delphi for .NET, the new signature is:

TTCompareEvent = procedure(Sender: TObject; Node1, Node2: TTreeNode; Data: TTag; var Compare: Integer) of object;
Copyright(C) 2008 CodeGear(TM). All Rights Reserved.
What do you think about this topic? Send feedback!