Delphi.
Why
type
myInt = Integer;
myNewInt = -2147483648..2147483647;
var
a: myint;
b: myNewInt;
begin
a := b;
end;
It is compiled normally though types formally different - one is declared here, another undertakes from other module. And if
uses
windows;
type
_FILETIMEA = record // Copy from windows.pas
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;
var
x: _FILETIMEA;
y: windows._FILETIME;
begin
x := y;
end;
Will cause a compilation error (in line x:=y; [DCC Error] ... E2010 Incompatible types: 'windows._FILETIME' and '_FILETIMEA'), though type _FILETIMEA = Windows._FILETIME ?
Delphi doesn't support duck typing. You have 2 different records. They just look a like, but they are 2 different types for the compiler. If you want to assign the two variables you have to type cast them what is possible because they have the same size.
x := _FILETIMEA(y);
Because you're not doing the same thing. :) Delphi is strongly typed (there aren't many exceptions - almost everything has a specific type).
type
myInt = Integer;
myNewInt = -2147483648..2147483647;
There is nothing incompatible about these types. They're both the same type (integer), and support the same range of values. There's no conflict about allowing the assignment of one to the other.
// Your unit name
unit GuTypes;
Type
_FILETIMEA = record // Copy from windows.pas
dwLowDateTime: DWORD;
dwHighDateTime: DWORD;
end;
This is a new type, defined by your code (even though it's identical to the one in Windows.pas, it's not the same). It's GuTypes._FileTimeA, which is not the same as Windows._FileTimeA.
This is actually a good thing, because it allows different units to use the same typenames without conflict; you can prefix with the unit name as a "namespace" to clarify which one you mean to use. For instance, if you have yours defined in GuTypes, and Windows has one declared there, in a third unit MyAppCode, you can do this safely:
var
GFileTime: GuTypes._FILETIMEA;
WFileTime: Windows._FILETIMEA;
Even if one of the types changes, your code is safe from side effects, because the two types can't accidentally be interchanged.
You can typecast to force the assignment to work, but that's usually a bad idea; it defeats the whole purpose of using a strongly typed language. Typecasting tells the compiler "I'm smarter than you, and I know this is wrong but I want you to ignore it and do it anyway."
A better solution would be either to declare your variable as you did the y in your example (Windows._FILETYPEA), or to declare a compatible type (type TMyFileTimeA = Windows._FILTETIMEA;).
See the XE2 Wiki Pages on Type Compatibility and Identity. Look specifically at the Assignment Compatibility section.
Related
I just tried my first use of generics in Delphi 2009 and am perplexed on how to use a generic type as the input to the Supports function used to see if an object implements a given interface. I've created a small sample illustrating the problem.
Given the following types and utility function:
IMyInterface = interface
['{60F37191-5B95-45BC-8C14-76633826889E}']
end;
TMyObject = class(TInterfacedObject, IMyInterface)
end;
class function TFunctions.GetInterface<T>(myObject: TObject): T;
var
specificInterface: T;
begin
// This would compile, but looses the generic capability
//Supports(myObject, IMyInterface, specificInterface);
// This results in compile errors
Supports(myObject, T, specificInterface);
result := specificInterface;
end;
and the following code snippet:
class procedure TFunctions.Test;
var
myObject: TMyObject;
myInterface: IMyInterface;
begin
myObject := TMyObject.Create;
myInterface := GetInterface<IMyInterface>(myObject);
end;
I would expect no problems but I get the following compile time errors:
[DCC Error] GenericExample.pas(37): E2029 '(' expected but ',' found
[DCC Error] GenericExample.pas(37): E2014 Statement expected, but expression of type 'T' found
I'm not sure what the compiler is expecting me to do with the T when used as the actual argument to the function.
I've searched around quite a bit and haven't been able to crack this one. A part of me suspects that if I could understand how an interface name gets converted to the IID: TGUID type during compilation, when using a concrete interface name, I could make some headway, but that has evaded me also.
Any help is much appreciated.
There is no guarantee that T has a GUID associated with it, and there is no means in the language to write a constraint on the type parameter to make that guarantee.
The interface name is converted into a GUID by the compiler looking up the name in the symbol table, getting the compiler's data structure representing the interface, and checking the corresponding field for the GUID. But generics are not like C++ templates; they need to be compiled and type-checked and known to work for any valid type parameter, and that means constraining the type parameter in its declaration.
You can get the GUID using RTTI (first checking that T does indeed represent an interface) with something like GetTypeData(TypeInfo(T))^.Guid and pass the GUID to Supports that way.
Why are you even bothering?
To use this TFunctions.GetInterface you need:
an interface
an object reference
If you have those, then you can just call Supports() directly:
intf := TFunctions.GetInterface<IMyInterface>(myObject);
is exactly equivalent to:
Supports(IMyInterface, myObject, intf);
Using generics here is a waste of time and effort and really begs the question "Why Do It?".
It is just making things harder to read (as is so often the case with generics) and is more cumbersome to use.
Supports() returns a convenient boolean to indicate success/failure, which you have to test for separately using your wrapper:
intf := TFunctions.GetInterface<IMyInterface>(myObject);
if Assigned(intf) then
// ...
versus:
if Supports(IMyInterface, myObject, intf) then
// We can use intf
When creating wrappers around functionality it is generally the case that the result is an improvement in readabilty or usability.
imho this fails on both counts and you should just stick with the Supports() function itself.
There is an old hack to making semi-generic containers in Delphi using include-files.
See http://www.delphikingdom.com/asp/viewitem.asp?catalogid=453&mode=print and start with 3d listing to grasp the idea.
However having two inter-dependent INC-files in units' interface and implementation sections leads to troubles: seems like XE2 compiles those include files as independent units instead and implementation cannot find functions declared in interface. It does not happen every time, but i failed to determine the conditions and thus failed to make a workaround.
Trying to re-formulate this in Delphi generics unit with as little changes as possible (i had to move huge legacy project to XE2 and "working" should be 1st, optimizations and refactoring later), i stuck into the following pit:
TemplateList<_DATA_TYPE_> = class
public
const MaxListSize = Maxint div (sizeof(Integer)*sizeof(_DATA_TYPE_));
type
TIntList = array[0..MaxListSize - 1] of _DATA_TYPE_;
PIntList = ^TIntList;
private
FList: PIntList;
FCount: Integer;
This gives an error that Low-bound for TIntList is upper than High-bound. Which, i think, means that const MaxListSize is evaluated to zero, but the TIntType tries to be evaluated immediately, not when actually instantiating the type.
I wonder if XE3 or XE4 fixed this. And if there is a way to make this compile in XE2 without major re-working
PS. making the array 0..0 and suppressing bounds checking is usual solution, yet it make a lot of fragile non-checked code. Maybe i'd end up using real TList or TList<integer\> instead...
PPS. Funny thing, reformulating inner type with copy-paste
TIntList = array[0..Maxint div (sizeof(Integer)*sizeof(_DATA_TYPE_)) - 1] of _DATA_TYPE_;
changes the error into "const expression required".
So the same expression is considered const-enough in one branch of compiler and non-const in another... I wonder if it constitutes an inconsistency bug per se.
The problem with the compiler appears to be that at the generic phase of the compilation, sizeof(_DATA_TYPE_) is not known. And so the compiler appears to use a place holder value of 0. By the time you instantiate the generic type, sizeof(_DATA_TYPE_) is replace by the true value. But it's too late. The array type bounds checking is performed at the generic phase of the compilation at which point sizeof(_DATA_TYPE_) is 0 and so the compiler gags.
That this is the case can be seen by the following code:
type
TemplateList<_DATA_TYPE_> = class
public
const
SizeOfDataType = sizeof(_DATA_TYPE_);
MaxListSize = Maxint div (sizeof(Integer)*SizeOfDataType);
end;
which produces this compiler error:
[dcc32 Error]: E2098 Division by zero
But, if you try this variant:
{$APPTYPE CONSOLE}
type
TemplateList<_DATA_TYPE_> = class
public
const
SizeOfDataType = sizeof(_DATA_TYPE_);
end;
begin
Writeln(TemplateList<Integer>.SizeOfDataType);
Writeln(TemplateList<Double>.SizeOfDataType);
Readln;
end.
the output is:
4
8
This shows that your constant declaration has a place holder value for the array type bounds checking, but has a true value once the generic has been instantiated. So, if the compiler postponed array type bounds checking until instantiation, then all would be well in terms of the compiler warning. But even then, the code would not do what you expect and desire. This program:
{$APPTYPE CONSOLE}
type
TemplateList<_DATA_TYPE_> = class
public
const
SizeOfDataType = sizeof(_DATA_TYPE_);
type
TMyArray = array [0..SizeOfDataType] of _DATA_TYPE_;
end;
begin
Writeln(high(TemplateList<Integer>.TMyArray));
Writeln(high(TemplateList<Double>.TMyArray));
Readln;
end.
produces somewhat undesirable output:
0
0
So it seems that not only are the array bounds checked at the generic phase of compilation, but the array bounds are fixed at that phase, and fixed using the place holder value of the type parameter size. What this means is that you cannot hope to achieve array bounds that vary based on the size of the data type.
The same behaviour is present in XE3, and I don't have XE4 at hand to check there.
I personally feel that this is a design flaw in the compiler that warrants a QC report.
In my opinion, the only viable way to resolve this is to give up trying to specify array bounds. I would declare it like this:
type
TemplateList<_DATA_TYPE_> = class
public
type
TIntList = array[0..0] of _DATA_TYPE_;
PIntList = ^TIntList;
private
FList: PIntList;
FCount: Integer;
end;
Obviously you'll need to disable range checking in this unit, but that's no real hardship since range checking doesn't do you any favours for the original code.
Using Delphi 2010...
I have a set of binary properties I want to group together. I have defined it as such...
type
TTableAttributeType = (
tabROOT = 1,
tabONLINE = 2,
tabPARTITIONED = 3,
tabCOMPRESSED = 4,
);
// Make a set of of the Table Attribute types...
type
TTableAttrSet = Set of TTableAttributeType;
In my MAIN.PAS unit, I can create a variable of type TTableAttrSet.
Another Unit, UTILS.PAS needs to understand the TTableAttrSet type as well. That is taken care of by the USES clauses...
Main USES Util...
Util USES Main (The 2nd uses clauses, under implementation section, so I don't get circular reference problems....
So far so good. My problem is that I need to pass a var variable of type TTableAttrSet FROM main to Utils.
In main.pas
procedure TForm1.Button1Click(Sender: TObject);
var
TabAttr : TTableAttrSet;
begin
TestAttr (TabAttr);
end;
and in UTILS.PAS, I have
procedure TestAttr(var Attr: TTableAttrSet);
begin
Attr := [tabROOT, tabCOMPRESSED];
end;
When I try this, I run into several problems...
Problem 1). When I define my procedure definition at the top of utils.pas,
procedure TestAttr(var Attr: TTableAttrSet);
I get the error that TTableAttrSet is an Undeclared Identifier. This makes sense because the definition is in Main.pas, and the 'uses Main.pas' is AFTER my procedure definitions. How do I get around this? For now, I have duplicated the TTableAttrSet type definition at the top of the Utils.pas file as well as Main.pas, but this does not 'seem the right way'.
Problem 2). The bigger issue that I am running into is a compile error. on the calling line in Main.pas
TestAttr(TabAttr);
I get the error 'Types of actual and formal var parameters must be identifical'. To my knowledge they are identical. What is the compiler complaining about?
The simple solution is to move the declaration of TTableAttributeType to the Utils unit. You can't declare it twice because then you have two distinct types. That's no use to you, you need only a single type.
This solution will work so long as the Main unit does not need to reference TTableAttributeType in its interface section. Since the Utils unit clearly needs to do so then that would create a circular dependency between unit interface sections which is illegal.
If both the Main and Utils units need to reference TTableAttributeType in their interface sections then you need to create another unit that just contains type declarations. That unit could be used by both Utils and Main in their interface sections.
I just tried my first use of generics in Delphi 2009 and am perplexed on how to use a generic type as the input to the Supports function used to see if an object implements a given interface. I've created a small sample illustrating the problem.
Given the following types and utility function:
IMyInterface = interface
['{60F37191-5B95-45BC-8C14-76633826889E}']
end;
TMyObject = class(TInterfacedObject, IMyInterface)
end;
class function TFunctions.GetInterface<T>(myObject: TObject): T;
var
specificInterface: T;
begin
// This would compile, but looses the generic capability
//Supports(myObject, IMyInterface, specificInterface);
// This results in compile errors
Supports(myObject, T, specificInterface);
result := specificInterface;
end;
and the following code snippet:
class procedure TFunctions.Test;
var
myObject: TMyObject;
myInterface: IMyInterface;
begin
myObject := TMyObject.Create;
myInterface := GetInterface<IMyInterface>(myObject);
end;
I would expect no problems but I get the following compile time errors:
[DCC Error] GenericExample.pas(37): E2029 '(' expected but ',' found
[DCC Error] GenericExample.pas(37): E2014 Statement expected, but expression of type 'T' found
I'm not sure what the compiler is expecting me to do with the T when used as the actual argument to the function.
I've searched around quite a bit and haven't been able to crack this one. A part of me suspects that if I could understand how an interface name gets converted to the IID: TGUID type during compilation, when using a concrete interface name, I could make some headway, but that has evaded me also.
Any help is much appreciated.
There is no guarantee that T has a GUID associated with it, and there is no means in the language to write a constraint on the type parameter to make that guarantee.
The interface name is converted into a GUID by the compiler looking up the name in the symbol table, getting the compiler's data structure representing the interface, and checking the corresponding field for the GUID. But generics are not like C++ templates; they need to be compiled and type-checked and known to work for any valid type parameter, and that means constraining the type parameter in its declaration.
You can get the GUID using RTTI (first checking that T does indeed represent an interface) with something like GetTypeData(TypeInfo(T))^.Guid and pass the GUID to Supports that way.
Why are you even bothering?
To use this TFunctions.GetInterface you need:
an interface
an object reference
If you have those, then you can just call Supports() directly:
intf := TFunctions.GetInterface<IMyInterface>(myObject);
is exactly equivalent to:
Supports(IMyInterface, myObject, intf);
Using generics here is a waste of time and effort and really begs the question "Why Do It?".
It is just making things harder to read (as is so often the case with generics) and is more cumbersome to use.
Supports() returns a convenient boolean to indicate success/failure, which you have to test for separately using your wrapper:
intf := TFunctions.GetInterface<IMyInterface>(myObject);
if Assigned(intf) then
// ...
versus:
if Supports(IMyInterface, myObject, intf) then
// We can use intf
When creating wrappers around functionality it is generally the case that the result is an improvement in readabilty or usability.
imho this fails on both counts and you should just stick with the Supports() function itself.
In extension to this question, I guess I'll best show what I've got so far.
What I'm trying to do is create a Firefox extension with Delphi, that'll work with the Firefox versions of the future that will use an exported NSModule structure, and no longer an NSGetModule function.
Main questions I'm struggling with for the moment is:
Is the code below correct? I may be wrong with how the pointers and arrays of records work.
How to debug this? If I build it and it runs then I'm kind of sure it'll work, but in debugging my library I can only check if my init code does its job. (and for now, Firefox 3.6 doesn't seem to pick up my #mozilla.org/network/protocol;1?name=xxm contract)
The code I'm trying to port is here:
http://mxr.mozilla.org/mozilla-central/source/xpcom/components/Module.h
type
TConstructorProcPtr=function(aOuter:nsISupports;const aIID:TGUID;var aResult:pointer):nsresult;
TLoadFuncPrt=function:nsresult;
TUnloadFuncPrt=procedure;
TCIDEntry=record
cid:TGUID;
service:boolean;
getFactoryProc:pointer;//TGetFactoryProcPtr;
constructorProc:TConstructorProcPtr;
end;
TContractIDEntry=record
contractid:PChar;
cid:TGUID;//PGUID?
end;
TCategoryEntry=record
category,entry,value:PChar;
end;
TXPCOMModule=packed record
kVersion:integer;//=1;
mVersion:cardinal;//kModuleVersion
mCIDs:^TCIDEntry;//pointer to first in array, last should be nil
mContractIDs:^TContractIDEntry;//pointer to first in array, last should be nil
mCategoryEntries:^TCategoryEntry;//pointer to first in array, last should be nil
getFactoryProcPtr:pointer;//TGetFactoryProcPtr;
loadProc:TLoadFuncPrt;
unloadProd:TUnloadFuncPrt;
end;
You almost certainly need the cdecl calling convention on all your procedure- and function-pointer declarations:
TConstructorProcPtr = function(aOuter: nsISupports; const aIID: TGUID; var aResult: Pointer): nsresult; cdecl;
TLoadFuncPrt = function: nsresult; cdecl;
TUnloadFuncPrt = procedure; cdecl;
I assume you've declared nsISupports as a Delphi interface. Otherwise, you need to make sure the aOuter parameter above is a pointer as it is in the C++ code.
For TContractIDEntry, and all the other places where you use PChar, I advise you to use PAnsiChar instead. The size of Delphi's Char type changed a couple of years ago, but the C++ char is and always will be one byte, so use Delphi's one-byte character type explicitly. Also, your comment wondering whether to declare the cid field as a PGUID was correct; asterisk means pointer.
TContractIDEntry = record
contractid: PAnsiChar;
cid: PGUID;
end;
The kVersion field should not be a member of the record you declare. In C++, it's a static member, which means it occupies no space in the structure itself; it's shared by all instances of that type. It's equivalent to a class field in a Delphi class, but I don't think records offer that feature. Make it a unit-level variable instead of a field.