For example, if one implements a method like this:
where the interface IDoSomething is specified in a different assembly in C# as follows:
type MyDemo = class( System.Object, IDoSomething )
public
procedure PlayWithData( data : array of System.String );
// ....
end;
public interface IDoSomething {
void PlayWithData( String[] data );
}
In Delphi.W32 language the data parameter thus specified is a non-var parameter and hence it tries to implement the C++'s "const object" semantics preventing the callee from changing the content. That it is the address of object is passed by value and the content is then protected from bring modified.
In C++, this protection is enforced via const methods but in Delphi there is no way to specify a const member functions/procedures. So it is kind of half-baked adoption. In C++, the compiler ensures that only const member functions can be used to access const objects.
In .Net world there is no support for const object in parameter passing. If the parameter is not specified and if the object is a reference object the ECMA standard says (12.4.1.5.1):
For built-in types (integers, floats, etc.) the caller copies the value onto the stack before the call. For objects the object reference (type O) is pushed on the stack.Therefore, if someone passes an "array of String" to the PlayWithData(), .Net permits the callee to change any element in the array. The only thing .Net does not permit under this condition is for the function to replace the object reference on the stack upon returning from the function.
In fact, if the parameter of type Foo, the callee can change the state of Foo with any methods the callee can access. There is nothing the caller can do to prevent callee from changing it other than passing a copy to the callee.
OK. Now comes the crunch time. In Delphi.Net, the compiler generates code in clear violation of this call convention. If one examines the code generated in PlayWithData(), the first thing Delphi does is to emit code (in pseudo-code style) like this:
data := (data.Clone as String[]);
In other words, before anyone is allowed to deal with the array, the function's code is actually dealing with a cloned version of the array. Hence anything you do to the array element will be discarded by the function upon function exit.
Using a cloned version inside the function clearly will upset the expected behaviour of any .Net assembly because of the lack of const control in the parameter passing. Whether it is good or bad is not the debate in this blog message. Delphi.Net emitting code like that inside the callee will produce incompatible assembly because there is a published ECMA standard governing the convention.
It may be perfectly logical to do that in Delphi.32 where there is no standard at all. But now the world is different.
In fact, this standard actually forces C++ code to disobey the ISO C++ standard when code is compiled with /clr switch when it comes to the treatment of virtual function call convention in the Constructor and Destructor.
Hence it is wise for Delphi to toe the CLI line rather than creating incompatible assemblies.
This means that C# implementation of IDoSomething can result in different behaviour of that using Delphi.Net. This clearly violates ECMA standard introducing incompatibility.
Not only that, such parameter passing convention can result is memory and CPU wastage because of cost of cloning an array can be very high if the array is an array of some objects with deep object model. Remember ICloneable.Clone() is a deep copy function. Not very smart!
For example implementations a swap interface in Delphi and C# will resulted in totally different behaviour. In Delphi.Net, a swap method will require by-ref parameter passing, while C# can be done with a by-value passing.
One should not use By-ref parameter passing convention for this usage because (12.4.1.5.2):
By-ref parameters are the equivalent of C++ reference parameters or PASCAL var parameters: instead of passing as an argument the value of a variable, field, or array element, its address is passed instead; and any assignment to the corresponding parameter actually modifies the corresponding caller’s variable, field, or arrayIn essence, home is defined as (12.1.6.1):
element....
Passing a value by reference requires that the value have a home (see §12.1.6.1) and it is the address of this home that is passed.
The home of a data value is where it is stored for possible reuse.Since the function is not asked to redefine a new array of string. It merely allowing it to change any or all of the elements in-place as permitted in ECMA standard and hence the use of pass by-value is correct.
At the moment, I have not explored this parameter passing conflict between Delphi.Net/Pascal and that in ECMA-CLI with other parameter types. But this will be reported in another blog once I have done sufficient investigation.
For the moment, be careful when you are implementing interfaces specified by others and for users of assembly to beware of using assemblies developed in Delphi.Net language. Delphi.Net does not follow the ECMA standard and this can cause integration problem.
Could this be another example of bastardisation of CLR?
This issue is actually more thorny to Borland than their special treatment of assembly accessible member because in that situation Borland uses the Delphi Compiler to restrict Delphi's private member to behave like CLR private when accessed outside the unit but with an IL assembly access qualifier. In that situation, one needs to use their compiler to compile Pascal code and hence they can control this kind of strange behaviour - until they support netmodule.
But in this situation it involves calls to and from any .Net assembly out of the control of Borland's compiler!!
No comments:
Post a Comment