Is it always safe to remove a published empty section? - delphi

I'm working on an old legacy project which have several classes in which the published section is always declared without anything inside of it, i.e:
TMyClass = class
public
procedure DoSomething();
published
end;
On compiling, I get the following warning message:
[DCC Warning] uMyUnit.pas(141): W1055 PUBLISHED caused RTTI ($M+) to
be added to type 'TMyClass'
I don't know if the predecessor developer has declared these published sections for some valid reason.
Is it always safe to remove an empty published section or could it cause some changes in the application's behavior?

The difference for the class itself is none - however the important thing is that the default visibility of any class that inherits from a class with {$M+} then changes from public to published!
See this example code:
uses
TypInfo;
type
TMyClass = class
private
fName: string;
property Name: string read fName;
published
end;
TMyOtherClass = class(TMyClass)
property Name;
end;
var
propCount, i: Integer;
props: PPropList;
begin
propCount := GetPropList(TypeInfo(TMyOtherClass), props);
for i := 0 to propcount - 1 do
Writeln(props^[i].Name);
Readln;
end.
You can see that it lists the Name property but when you remove the published from TMyClass it will not - that is because once TMyClass got {$M+} added any member declared without explicitly stating the visibility it will be published opposed to public.
Also other members declared without visibility like fields will be published. This is being used in the streaming system Delphi uses for forms and such.
You can for example then call TObject.FieldAddress or TObject.MethodAddress passing in the name of a field or method and get back pointers to the field or method. It only works with published fields and methods.
This is how loading from a dfm sets up all those IDE generated fields like Button1 or connects the Button1Click method to the Button1.OnClick - they are without explicit visibility at the top of your form which inherits from TComponent that has {$M+} declared.

It depends
Does the rest of the code actually require access to RTTI for the class?
Only TPersistent-derived classes have the {$M+} directlive applied to them by default without needing a published section.
A published section is used for DFM streaming, which requires RTTI. Non-persistent classes are not steamed in DFMs, but there are other uses for RTTI.
So, without knowing what the rest of the code does, it is not really known whether removing empty published sections is safe or not.

Related

How Delphi object inspector grey some properties?

Recently I found that Delphi object inspector displays some properties in grey. Here is an example:
I wonder what does it mean? How such properties are defined? I did not find any differences in definition of let's say DSHostname and ProxyHost. But as you can see DSHostname is displayed normally and ProxyHost in grey.
Here is a relevant declaration of properties in question:
/// <summary>The host to proxy requests through, or empty string to not use a proxy.</summary>
property ProxyHost: string read FProxyHost write FProxyHost;
/// <summary>The port on the proxy host to proxy requests through. Ignored if DSProxyHost isn't set.
/// </summary>
[Default(8888)]
property ProxyPort: Integer read FProxyPort write FProxyPort default 8888;
/// <summary>The user name for authentication with the specified proxy.</summary>
property ProxyUsername: string read FProxyUsername write FProxyUsername;
/// <summary>The password for authentication with the specified proxy.</summary>
property ProxyPassword: string read FProxyPassword write FProxyPassword;
Finally I got a proof that Remy Lebeau was right in his guess. I made a descendant of TDSClientCallbackChannelManager which has published property TestProxyHost. This property does nothing but mirroring ProxyHost in Get and Set. Here is the full code for the component:
unit uTestCallbackChannelManager;
interface
uses
System.SysUtils, System.Classes, Datasnap.DSCommon;
type
TTestCallbackChannelManager = class(TDSClientCallbackChannelManager)
private
function GetTestProxyHost: string;
procedure SetTestProxyHost(const Value: string);
published
property TestProxyHost: string read GetTestProxyHost write SetTestProxyHost;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TTestCallbackChannelManager]);
end;
{ TTestCallbackChannelManager }
function TTestCallbackChannelManager.GetTestProxyHost: string;
begin
Result := ProxyHost;
end;
procedure TTestCallbackChannelManager.SetTestProxyHost(const Value: string);
begin
ProxyHost := Value;
end;
end.
After I installed TTestCallbackChannelManager into component palette I dropped in on a form in a test project.
In Object Inspector the ProxyHost property is displayed in grey and TestProxyHost as normal. Now if I change TestProxyHost then ProxyHost is changed as well. Here is a screenshot:
This means:
RTTI information of ProxyHost property was not altered in any way and it is indeed a read/write property in both design- and run-time.
The only way to achieve such behavior is on the Property Editor level. The Property Editor registered for this particular property name in this component type "tells" Object Inspector "Hey, I can not set this property for you" (but an other code can do it directly).
This also explains why if I uncheck "Show read only properties" flag in Object Inspector options ProxyHost (and 3 related properties) are still shown in the Object Inspector. It is because of Object Inspector reads the properties from dfm as read/write and then creates Property Editors for them and once Property Editor says it can't write the property they are shaded in grey (but still shown as Property Editor is already created).
The only problem is what logic behind the Property Editor? When the properties become available and how to use them? It looks like the properties are introduced very recently in xe10 or a bit earlier. And Embarcadero provides no documentation about these properties (at least for now I could not find any). But this is a subject of separate question. I suspect that support for these properties has not been tested (or may be not implemented) yet and so they are intended for use in future releases.

Does interface delegation of an inherited interface require a wrapper class?

Delphi allows for interface delegation using the implements keyword.
For example
IIndep1 = interface
function foo2: integer;
end;
IIndep2 = interface
function goo2: integer;
end;
TIndep1And2 = class(TInterfacedObject, IIndep1, IIndep2)
private
FNested : IIndep1; //e.g. passed via constructor or internally created (not shown here)
public
Constructor Create(AIndep1: IIndep1);
function goo2: integer;
property AsIndep1 : IIndep1 read FNested implements IIndep1;
end;
That works well, but not for inherited interfaces. (Error message "Missing implementation of interface method ILev1.foo")
ILev1 = interface
function foo: Integer;
end;
ILev2 = interface(ILev1)
function goo: Integer;
end;
TLev2Fails = class(TInterfacedObject, ILev1, ILev2) //Also fails with ILev2 alone (Error: "ILev1 not mentioned in interface list")
private
FNested : ILev1; //passed via constructor or internally created
public
Constructor Create(AILev1: ILev1);
function goo: Integer;
property AsLev1 : ILev1 read FNested implements ILev1;
end;
The workaround is to add an extra ancestor class
TLev1Wrapper = class(TInterfacedObject, ILev1)
private
FNested : ILev1; //passed via constructor or internally created
public
Constructor Create(AILev1: ILev1);
property AsLev1 : ILev1 read FNested implements ILev1;
end;
TLev2Works = class(TLev1Wrapper, ILev2)
public
function goo: Integer;
end;
Is there a way to avoid the wrapper class ancestor?
[EDIT]
Just a note on interface delegation, the purpose of using implements is to avoid satisfying the interface directly, but passing that requirement to an aggregated or composed member. Providing the full interface and manually delegating to a composed member defeats the benefits gained from using implements to direct the interface. In fact, in that case the implements keyword and property may be removed.
This looks like the compiler is attempting to enforce the expectations (read: requirements) of IUnknown.QueryInterface:
For any one object, a specific query for the IUnknown interface on any
of the object's interfaces must always return the same pointer value.
If you were able to delegate the implementation of a base interface whilst implementing a derived interface yourself then:
obj := TLev2Fails.Create(otherLev1); // Assuming your class could compile
lev1 := obj as ILev1; // yields reference to otherLev1 implementor
lev2 := obj as ILev2; // yields reference to TLev2Fails instance
unk1 := lev1 as IUnknown; // returns IUnknown of otherLev1 implementor
unk2 := lev2 as IUnknown; // returns IUnknown of obj TLev2fails instance
This would not be the case if your nested object were correctly implemented as a TAggregatedObject derived class, but the compiler has no way of knowing whether this is the case let alone enforcing it so instead it appears it simply requires that if you implement a derived interface then you must also directly implement any interfaces that interface itself inherits.
The compiler error in this situation isn't being very helpful, though it could be read as telling you what you need to do, just not why you need to do it in this case, which is not that unusual for compiler errors.
If you wish to delegate in this case then you must "delegate manually".
Whilst this loses the benefit of the implements facility, it does at least retain the benefits of re-use, just not quite as conveniently.
NOTE: Even if your delegated implementation is based on a TAggregatedObject, the compiler still cannot determine that these implementation details satisfies the requirements of QueryInterface so you will still get this error (even if using a class reference for the delegated interface).
You must still delegate manually.
Having said all that, I cannot currently see how this is any different for the case when interfaces are involved with no inheritance relationship, but it is quite possible that this is valid and I just haven't worked through all the necessary 'thought experiments' to prove it to myself. :)
It may be that the compiler is just being extra cautious in situations when it thinks it can/should be and the documentation simply fails to mention this resulting limitation of implements.
Or it may be a bug in the compiler, though I think there are sufficiently apparent reasons for the behaviour and the behaviour is itself so well established and consistent (having reproduced the exact same behaviour in Delphi 7) that an omission in relevant documentation is the more likely explanation.
Is there a way to avoid the wrapper class ancestor?
No, the inherited interface has to be bound to a an ancestor class. Delphi does not implicitly bind inherited interfaces, because of reasons explained here. Also, it cannot bind to the current class, unless it manually delegates all calls to the composed item.
We are left with only one option. An ancestor wrapper class, to which we can bind ILev1

Delphi component property class depending on the component's Owner class

I'm using RAD Studio XE5 to build my application.
I saw that is was not very practical to try tu publish properties on a TForm. It then has to be registered and installed as a package, then it's not practical for heavy development.
So, I decided I would create a non visual component (TFormPropertiesEditor) that would be used to fill up form properties. A way of standardising my forms.
The component would be dropped on the base form, a form of which every other form inherits (let's call it TBaseForm). So, the component would be dropped only once on the 'base' form, then with inheritance, every other form would have it too.
The created component would detect the class of its Owner (BaseForm or its descendents) and create an object accessible through the 'Properties' property, whose class would be conditional to the owner class.
This way, when inspecting the component on a TBaseForm, I would have access to the TBaseFormProperties only. When inspecting the component on a TSecondForm, I would also have access to the TSecondFormProperties. Only, the component would be intelligent enough to detect which PropertyClass it should expose as the Properties property.
The component would inspect the form, through the GetPropertiesClass, defined as :
function TBaseForm.GetPropertiesClass : TPropertiesClass;
begin
Result := TBaseFormProperties;
end;
function TSecondForm.GetPropertiesClass : TPropertiesClass;
begin
Result := TSecondFormProperties;
end;
Each form has a corresponding TProperties descendent, like so :
TBaseForm ------------ TSecondForm ------------- ...
|
TBaseFormProperties -- TSecondFormProperties --- ...
For example :
If the Form on which the component is placed is TBaseForm, FProperties would be a TBaseFormProperties. If the form is a TSecondForm, FProperties would be TSecondFormProperties. Naturally, TSecondFormProperties would inherit from TBaseFormProperties.
Though, when I place the component on the form, it seems to be unable to detect of which class the component is.
function TFormPropertiesEditor.GetPropertiesClass: TFormPropertiesClass;
begin
Result := TBaseForm(Owner).GetPropertiesClass;
end;
It looks like the TBaseForm(Owner) part is causing the problem. The interpreter is stuck on the TBaseForm, and will not consider if Owner is of type TSecondForm or TThirdForm.
Interfaces
So, to get around the TBaseForm(Owner) typecasting, I decided to use an interface. So if I use an interface that declares the GetPropertiesClass:
IMasterForm = interface(IInterface)
['{B6122F34-65C4-4701-8A5E-50C8DABF5516}']
function GetPropertiesClass : TFormPropertiesClass;
end;
type
TBaseForm = class(TForm, IMasterForm)
MyFormPropertiesEditor1: TMyFormPropertiesEditor;
private
{ Déclarations privées }
public
function GetPropertiesClass : UCommon.TFormPropertiesClass;
end;
The following :
function TFormPropertiesEditor.GetPropertiesClass : TFormPropertiesClass;
begin
Result := (Owner as IMasterForm).GetPropertiesClass;
end;
Results into an Interface not supported error.
Abstract Ancestor Method
Then, I decided to add an extra layer of ancestry. I've added a class, TMasterForm, from which TBaseForm inherits. This TMasterForm declares GetPropertiesClass as abstract and virtual :
TMasterForm = class(TForm, IMasterForm)
public
function GetPropertiesClass : TFormPropertiesClass; virtual; abstract;
end;
type
TBaseForm = class(TMasterForm)
private
{ Déclarations privées }
public
function GetPropertiesClass : UCommon.TFormPropertiesClass; override;
end;
But then, I get an AV because I think the IDE tries to access TMasterClass.GetPropertiesClass, which is of course not implemented.
How can this TypeCasting be accomplished? Any idea how could I proceed ?
Thank you very much in advance
Download Sample Project https://www.wetransfer.com/downloads/b524438609fc04257af803a8e3dd2eca20141225161239/764d108d335b9d296c3004dfea04a54620141225161240/9c8cc0
The basic problem here is that the IDE does not instantiate your form at designtime. So, no matter what code you put in the form class, it won't be executed by the IDE. This is because you did not register the form with the IDE.
If you wish the IDE to have knowledge of your forms then you need to register them with the IDE. And at that point all your code becomes needless because you are back to doing what you are attempting to avoid. Namely registering the forms with the IDE. You are stuck in a Catch 22 situation. If you need the IDE to know about the forms then you need to register them. At which point you may as well just surface the properties directly in the Object Inspector.
The problem in your code is that you are not inheriting your GetPropertiesClass method properly.
Infact you are not inheriting it across the class family.
In your code each class type has its own version of GetPropertiesClass method and therefore since you are typecasting the Owner to a TBaseForm class the method from TBaseForm is being used even if Owner is of TSecondForm class.
So you need to make sure that GetPropertiesClass in TBaseForm class is virtual and that merhod GetPropertiesClass in TSecondForm is overrided.
This will ensure that TSecondForm.GetProperties method will be called even when you are typecasting Owner to TBaseClass when the owner is of TSeconfForm class.

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

Are GUIDs necessary to use interfaces in Delphi?

The official documentation says they are optional. I know COM interop requires a unique identifier for each interface but every interface example I see has a GUID whether it's used with COM or not? Is there any benefit to including a GUID if its not going to be used with COM?
I've noticed that some methods such as Supports (to determine if a class conforms to a specific interface) require that you define a GUID before you can use them.
This page confirms it with the following information:
Note: The SysUtils unit provides an
overloaded function called Supports
that returns true or false when class
types and instances support a
particular interface represented by a
GUID. The Supports function is used in
the manner of the Delphi is and as
operators. The significant difference
is that the Supports function can take
as the right operand either a GUID or
an interface type associated with a
GUID, whereas is and as take the name
of a type. For more information about
is and as, see Class References.
Here's some interesting information about interfaces, which states:
Why does an interface need to be
uniquely identifiable? The answer is
simple: because Delphi classes can
implement multiple interfaces. When an
application is running, there has to
be a mechanism that will get pointer
to an appropriate interface from an
implementation. The only way to find
out if an object implements an
interface and to get a pointer to
implementation of that interface is
through GUIDs.
Emphasis added in both quotes.
Reading this entire article also makes you realize that QueryInterface (which requires a GUID) is used behind the scenes for reasons such as reference counting.
Only if you need your interface to be compatible with COM.
Unfortunately, that also includes using is, as operators and QueryInterface, Supports functions - the lack of which is rather limiting. So, while not strictly required, it's probably easier to use a GUID. Otherwise, you are left with rather simplistic usage only:
type
ITest = interface
procedure Test;
end;
ITest2 = interface(ITest)
procedure Test2;
end;
TTest = class(TInterfacedObject, ITest, ITest2)
public
procedure Test;
procedure Test2;
end;
procedure TTest.Test;
begin
Writeln('Test');
end;
procedure TTest.Test2;
begin
Writeln('Test2');
end;
procedure DoTest(const Test: ITest);
begin
Test.Test;
end;
procedure DoTest2(const Test: ITest2);
begin
Test.Test;
Test.Test2;
end;
procedure Main;
var
Test: ITest;
Test2: ITest2;
begin
Test := TTest.Create;
DoTest(Test);
Test := nil;
Test2 := TTest.Create;
DoTest(Test2);
DoTest2(Test2);
end;

Resources