Delphi’s typeless var params have been mentioned a few times recently as one of the (many) new black magic bits of Delphi for .NET. Here’s how we got typeless params to work in strongly typed .NET managed code.
First, let’s make sure we’re all talking about the same thing. This is a typeless var param in use:
procedure foo(var x); // typeless var param. "out" also supported typeless var local: sometype; begin local := sometype(x); use local; x := new value; end; var outer: TMyClass; begin outer := TMyClass.Create; foo(outer); end>;
There are a few tricky parts with typeless var params: sending arbitrary data into the param, using that param within the procedure, and then sending data back out again to a variable whose type is not known to the proc.
Sending arbitrary data into the param is fairly simple in .NET, thanks to boxing. The actual type of the ‘x’ param in the IL metadata is System.Object& (& for pass by reference). Integers or other non-object data can be boxed and sent in.
Using the param inside the routine isn’t all that bad as long as you don’t use Lcasts – typecasts on the left side of the assignment operator. RCasts (on the right side) can transform the data into temp variables, but LCasts have to modify the variable itself, which .NET generally doesn’t like. So, the technique is to declare a local var and assign the typeless param to the local, then operate on the local. That saves a lot of ugly casting in the source code. When you’re done, assign the final value back to the typeless param.
The coolest part is in sending the data back out to the variable that was passed into the typeless var param. In a sense, you’re pushing data “uphill”, from a less specific type (TObject) to a more specific type (the type of the variable). That isn’t an issue in Win32 because the Intel x86 chip doesn’t know anything about types (other than int,float, and pointer). But in .NET, pushing data uphill requires an explicit cast.
The trick in Delphi is that the compiler doesn’t pass a reference to the given variable itself, but instead creates a temp variable of type System.Object and passes that by reference to the routine. Upon returning from the call, the compiler casts the temp variable to the type of the original variable, then assigns the result of the cast back to the original variable.
If the routine assigned something to the typeless var which is incompatible with the type of the variable you passed in, you will get a cast exception at the source line that made the call. The cast is actually executed after the function call returned, but it’s still considered part of the instruction stream associated with that line of source.
Language Features vs Language Interop
Typeless var parms are a Delphi language feature that will not be fully understood by other .NET languages. C# code can certainly see and call this foo() routine, and C# code can pass data in by reference as System.Object instances, but C# doesn’t understand the symantics around the call that the Delphi compiler has set up. C# and other .NET languages won’t know that they should generate a cast at the call site after the call returns. You’ll have to create the local temp and make the cast yourself in C# code.
Artifacts / Side effects
There is a small risk of seeing artifacts using this technique. Specifically, the routine that is operating on the typeless var param is not operating on the address of the actual variable passed to the routine, it’s operating on a copy of that reference. The artifact is known as parameter aliasing. If you pass a global variable to the typeless var of the routine, and the routine modifies the parameter and then compares the parameter to the global variable, the change will not yet appear in the global variable. The change will not be seen in the global variable by any code until execution returns from the call to the routine and the compiler generated code can cast and assign from the temp to the original (global) var.
We decided this parameter aliasing artifact introduced by this implementation technique was an acceptable risk to enable typeless vars. Parameter aliasing artifacts are nothing new and can be created in Win32 code through a number of questionable coding habits. The general rule for typeless var params is: be very careful of comparing or using global vars inside routines that take typeless var params. 99% of the time, this isn’t an issue because the routine with the typeless var param is an isolated leaf node that has no knowledge of any global variables.
Delphi 8 for .NET uses typeless var params to implement FreeAndNil() and the Supports() family of functions.