Due to an interesting code example, behind which Kim Madsen first suspected a possible bug in the Delphi compiler, I took a closer look at the calling behavior of overloaded methods that also contain generic type parameters. I was surprised by the possible bug since I use overloads with combinations of generic and non-generic methods in current projects without any obvious errors.
Here is a simplified version of Kim’s code:
TFoo = class(TObject)
function DoSomething(SomeObject: TValue): string; overload;
function DoSomething<T>(SomeObject: T): string; overload;
TBar = class(TObject)
var Foo := TFoo.Create;
var Bar := TBar.Create;
Which of the two possible variants of DoSomething should be called by Foo.DoSomething(Bar)?
At a first glance, I was inclined to say that the non-generic variant, i.e. function DoSomething(SomeObject: TValue): string; should be called, since after all no type parameter is specified and therefore the first variant matches best.
But in fact, the generic, second variant is called. I initially suspected “TValue” of “blocking” the overload resolution here, since TValue is actually an “artificial” RTTI.driven type.
Stefan Glienke came up with the correct hint:
Generic parameters are actually optional and the type is automatically inferred by the type of the variable that is passed. This “type inference” takes precedence over overload resolution and thus produces a better match than the “TValue” method.
Type-inference of generic methods is documented here btw.
So the following code actually calls a generic implementation, although no type parameter is specified in the call:
function DoSomething<T>(SomeObject: T): string;
Foo.DoSomething(Bar); //<T> is inferred from "Bar"
Here is a Gist with full code to try:
If your Xcode version updates (manually or by Apple’s updating mechanism) then make sure to re-import the iOS/macOS SDK from within Delphi.
To build and deploy iOS/macOS apps with Delphi, you need Xcode for the final steps, even though the actual binary is compiled by Delphi. Apple frequently delivers minor updates to Xcode. Current version as of the writing of this article is 8.3.2. This version number of the Xcode build used for preparing apps is apparently checked by Apple, when uploading an IPA file to App Store/iTunes Connect. They don’t do any spooky things, they just check the DTXcodeBuild key in your app’s info.plist file:
This info.plist file is generated by Delphi, and contains various essential settings, such as version number, device requirements etc. Many of these settings can directly be configured, by modifying the values in Delphi – Project – Options – Version Information
DTXCodeBuild is filled in by Delphi automatically though. When you import iOS/macOS SDK, then PAServer obviously issues this command:
/usr/bin/xcodebuild -version -sdk
That returns the available SDK versions and the Xcode build number:
iPhoneOS10.3.sdk - iOS 10.3 (iphoneos10.3)
Build version 8E2002
This build version number is apparently stored and used to fill the DTXcodeBuild key’s value.
To bring this number in Delphi in sync with the actual Xcode version, after Xcode was updated (or switched with xcode-select), you have to delete the SDK from Delphi and re-import it using Delphi – Tools – Options – SDK Manager.
This is especially important if you imported the 10.3 SDK while having Xcode 8.3.0 installed. In that case your Delphi iOS apps would be tagged with being built with exactly that version – no matter if you downgraded to Xcode 8.2.1 or applied my “Package Application fix” – which is required due to a tool chain change that Apple imposed with Xcode 8.3 and up. Any iOS app tagged with being built with Xcode 8.3.0 will be refused by Apple, as that version has been deprecated.
Embarcadero just made a hotfix for Delphi/RADStudio 10.2 Tokyo available, which fixes several debugger issues and – important for you C++ guys – it fixes a string related security issue, which could potentially overwrite memory with affected scanf and scanf_s methods.
If you need to “pump” records from one data source to an other, then TFDBatchmove is for you.
Using FireDAC’s BatchMove component allows you to easily map the fields of one query to an other, regardless if they belong to the same database/connection or two totally different databases. You can actually even copy records from text files to “real” databases and vice versa.
Just build a BatchMove chain like this:
SourceData and DataTarget are regular TFDQueries, Reader and Writer are TFDBatchmoveDatasetReader/Writer components, that just connect the queries with the TFDBatchmove component in the middle.
The actual magic is done by adding field mappings, from source to target:
In this case source and target fieldnames are mostly the same, which wouldn’t even require adding mappings. There are a couple of calculated fields in the source query too. They are used to format/modify the data, so that it would match the format requirements in the destiantion query.
These fields are calculated in QOPCQuery.OnCalcFields like this:
procedure TDMMain.QOPCSourceDataCalcFields(DataSet: TDataSet);
if QOPCSourceDataoSchnittgruppe.IsNull then
DataSet.FieldByName('FANr').AsString := ''
DataSet.FieldByName('FANr').AsString := GenFANr(QOPCSourceDataoSchnittgruppe.AsInteger);
The calculated fields work fine, but when calling BatchMove.execute it will raise an exception. The reason is simple:
FireDAC’s internal logic depends on TDataSet.FieldDefs, but calclulated fields won’t show up there. You have to use “InternalCalc” instead of “Calculated”, to push your calculated fields to its datataset’s FieldDefs collection.
When you add lines to a TMemo in Delphi Applications, then you might want to scroll to the very end of that particular Memo, so that the newest lines are kept in view.
In FireMonkey it’s as easy as just calling GoToTextEnd:
procedure TFormMain.AddLog(AMessage: string);
You might notice though, that when you clear the Memo at certain times, the scrollbars may get out of sync. To avoid that, you need to call ProcessMessages right after “clear”. Apparently the scrollbars get confused, when you add lines directly after clear command, because the actual position needs to be rendered first (which the ProcessMessages does).
procedure TFormMain.AddLog(AMessage: string);
if Memo1.Lines.Count > 100 then
Application.ProcessMessages; //without this, scrollbars will show wrong size/position
When you start implementing cross-platform applications in Delphi’s FireMonkey framework, then you might consider reusing TDataMdoule instances from your existing VCL applications. If your code is clean enough, i.e. your DataModules are separated from any UI code, that is contain no references to WinApi, Forms, Controls or other UI relevant units, then this is actually a reasonable approach.
You might run into a weird problem though: The IDE will sometimes insist on adding VCL-dependant units to your DataModul, even if you actually compile for FireMonkey. One example is FireDAC.VCL.Wait which will get added automatically under certain conditions (and which won’t compile under FMX).
There is a property that controls this behaviour in shared TDataModule instances:
ClassGroup is basically a “Pseudo Property”, which adds some magic to the project so that the IDE will behave differently depending on the properties value. To make your DataModule really platform independent set it to: System.Classes.TPersistent
That way, the IDE stops inserting references to either FMX or VCL units in the DataModule’s uses clause. In the case of FireDAC applications, it will just add FireDAC.ConsoleUI.Wait, which will compile just fine on all platforms. You would then add the actual Wait unit in the app’s main form (or in the DPR).