1

I have this declaration:

Type

  TmyObjA = class
  private
    FieldA: integer;
    FieldB: integer;
  public
    ...
  end;

  TmyObjB = class(TmyObjA)
  private
    FieldC: integer;
    FieldD: integer;
  public
    ...
  end;

  var MyObjB = TmyObjB 

I want now to access the private field FieldA of MyObjB. I do like this :

  TmyObjAPrivateAccess = class
  public
    FieldA: integer;
    FieldB: integer;
  end;

  TmyObjAPrivateAccess(MyObjB).FieldA := something;

Does it will work or do I must do like this :

  TmyObjBPrivateAccess = class
  public
    FieldA: integer;
    FieldB: integer;
    FieldC: integer;
    FieldD: integer;
  end;

  TmyObjBPrivateAccess(MyObjB).FieldA := something;

NB: I do test, it's work in both variants, but it is not a demonstration, its just seem to work


Edit/Note

About why I need to access private member: The class I want to access private member is TTexture:

  TTexture = class(TInterfacedPersistent, ITextureAccess)
  private
    FWidth: Integer;
    FHeight: Integer;
    FPixelFormat: TPixelFormat;
    FHandle: TTextureHandle;
    FStyle: TTextureStyles;
    FMagFilter: TTextureFilter;
    FMinFilter: TTextureFilter;
    FTextureScale: Single;
    FRequireInitializeAfterLost: Boolean;
    FBits: Pointer;
    FContextLostId: Integer;
    FContextResetId: Integer;
  protected
  public
    property BytesPerPixel: Integer read GetBytesPerPixel;
    property MinFilter: TTextureFilter read FMinFilter write SetMinFilter;
    property MagFilter: TTextureFilter read FMagFilter write SetMagFilter;
    property PixelFormat: TPixelFormat read FPixelFormat write SetPixelFormat;
    property TextureScale: Single read FTextureScale; // hi resolution mode
    property Style: TTextureStyles read FStyle write SetStyle;
    property Width: Integer read FWidth write SetWidth;
    property Height: Integer read FHeight write SetHeight;
    property Handle: TTextureHandle read FHandle;
  end;

This is my TmyObjA.

Then I update this class to

TALPlanarTexture = class(TTexture)
  private
    FSecondTexture: TTexture;
    FThirdTexture: TTexture;
    FFormat: TALTextureFormat;
  protected
  ....
  end;

This is my TmyObjB. But I have several other class like this one, like TALBiPlanarTexture = class(TALTexture), etc.. this why I want to have only one access class for all of them :

  {************************}
  {$IF CompilerVersion > 32} // tokyo
    {$MESSAGE WARN 'Check if FMX.Types3D.TTexture still has the exact same fields and adjust the IFDEF'}
  {$ENDIF}
  TALTextureAccessPrivate = class(TInterfacedPersistent)
  public
    FWidth: Integer;
    FHeight: Integer;
    FPixelFormat: TPixelFormat;
    FHandle: TTextureHandle;
    FStyle: TTextureStyles;
    FMagFilter: TTextureFilter;
    FMinFilter: TTextureFilter;
    FTextureScale: Single;  
    FRequireInitializeAfterLost: Boolean;
    FBits: Pointer;
    FContextLostId: Integer;
    FContextResetId: Integer;
  protected
  public
  end;

Now why I need to access private member of TTExture? TTexture is a quite simple class used by all OpenGL delphi framework. So replacing this class mean ... well mean redraw all the openGL firemonkey framework :) quite complicate job but I agree its a possibility for purist people who don't want to access private member.

I have another framework (video player) that on each video frame gave me one openGL texture ID with/height and scale. so on each new frame event, I need to update the Handle of my TTexture plus the With/height of it and send it to the canvas.drawTexture (that only accept TTexture)

Is there any way to update on each frame a TTexture object with a given Handle/width/height and scale? simple anwer: no! yes their is a way, but i found it much more hazardous than accessing directly private members. this way involve using ITextureAccess to reset the handle to zero then call setwith and setheight that will finalize the texture then only after set back the handle, etc ...

but anyway my question was not about is it good or bad to access private members :)

zeus
  • 12,173
  • 9
  • 63
  • 184
  • 7
    Why not do it correctly, and move the private property to `protected*? Then descendants can access it direclty without all of the kludgy hacks. – Ken White Dec 22 '18 at 22:34
  • It will work in the above trivial scenario. It will stop working in a scenario where TmyObjB inherits from class Z, and that from Y, and that from X, and that from W, etc., with each implementing a number of interfaces and having a number of private members plus a number of public, protected etc. members, each in their own units. Then it gets a lot harder to mimic the class structure. If you control all these classes, you should do what @Ken says: make protected what you may need to access. – Rudy Velthuis Dec 22 '18 at 22:46
  • 1
    Fix the real problem. – David Heffernan Dec 22 '18 at 22:51
  • 2
    @KenWhite: because class A and class B are original Delphi class! I can not change their codes. And yes between redraw everything and add 5000 more lines of code and slow the program I prefer instead to access private members !! – zeus Dec 23 '18 at 07:16
  • @DavidHeffernan: What the real problem ? that i dont want to add 5000 more lines of code and slow my program and instead I prefer to access private members? – zeus Dec 23 '18 at 07:17
  • @RudyVelthuis: Thanks! Yes i make some warning if TmyObjA or TmyObjB will change, so for now they are exactly like this and I don't think embarcadero will change them soon – zeus Dec 23 '18 at 07:19
  • What 5000 lines of code and what slowdown? Of course that was exaggerated, but it isn't even true. Instead of asking this theoretical question, simply ask what **the real problem** is, i.e. what you want to do but can't. And if you, despite being told otherwise, are so confident your method works, why do you ask here? – Rudy Velthuis Dec 23 '18 at 10:56
  • David asked for the **real problem**, because it is pretty clear you don't have classes named TMyObjA and TMyObjB but obviously think you need to have access to some private fields in existing classes. The real problem is **why**, i.e. what do you really want to do? This looks a lot like an [XY problem](http://xyproblem.info/). So tell us the **real problem**. You ask about Y but we want to know about X. – Rudy Velthuis Dec 23 '18 at 11:02
  • Oh come on people. You act like you never used Delphi before. He is asking about real problem. How to access private field of a class not under his control. Maybe that the class is not under his control is not obvious from the question, but seriously.... – Dalija Prasnikar Dec 23 '18 at 11:02
  • Oh come on @Dalija. If people ask about that, it is often because they want to do something but can't and then *think* they need to access someone's private parts. Add to that that he only posts a theoretical trivial case, where in the real world things are a lot more difficult and complex. So he should at least post his real problem, not some theortical one. Often, there are solutions he didn't think about, e.g. intercepting functions or messages that go one level deeper. There is no universal solution to accessing private members either. It depends on the structure of the class. – Rudy Velthuis Dec 23 '18 at 11:05
  • I seriously don't know why would somebody want to fix bugs, change default (most often very limited) behavior of core Delphi frameworks. If only we would have simpler and safer method for accessing private members - you know something like class helpers... Oh, wait... we no longer can use that. Yes, this approach is hacky, it can break easily but so does copy pasting 5000 lines of code. @loki yes, you can use this approach... – Dalija Prasnikar Dec 23 '18 at 11:06
  • @Dalija: that is not the problem. He should mention which class(es) and which problem he wants to fix, instead of posting some trivial theoretical problem that is far away from the real world. – Rudy Velthuis Dec 23 '18 at 11:08
  • @Loki: despite what Dalija says: yes it will work for the extremely simple non-inherited classes you posted. It may not work for the real problem you are facing. There may even be a better solution (not necessarily copy/pasting 5000 lines of code), but who can tell if you don't post the **actual** problem, and not some hypothetical one. – Rudy Velthuis Dec 23 '18 at 11:10
  • @RudyVelthuis I would normally agree that asking real problem is preferred because there might be better solutions. On the other hand hacking the class via dummy class is not something out of the ordinary, something that people don't do. So it is perfectly fine asking how to do this particular thing and whether in this abstract scenario this is safe thing to do. This approach is widely applicable, and just because in some narrow scenario there might be other approach, this is legitimate question to ask. And unless it is a duplicate question, it may help others facing same problem. – Dalija Prasnikar Dec 23 '18 at 11:19
  • @Dalija: no, it is not something out of the ordinary. But there may be other things he did not consider (hidden fields, e.g. for interfaces etc.). We can only know if we know the real problem, not his hypothetical one. But hey, if I tell him about the risks, he says he is confident nothing will change and all works fine. If so, then he didn't have to ask at all, right? – Rudy Velthuis Dec 23 '18 at 11:43
  • 1
    @MartynA: LOL! We only bickered about ARC, and later on we more or less agreed. Dalija and I generally get along very well, thanks. – Rudy Velthuis Dec 23 '18 at 12:25
  • 1
    You don't need to care for descendants. In fact you couldn't even if you wanted to, there may be sibling descendants with different additional fields. What matters is the class that declared the field that you want to access. First solution is what you need. – Sertac Akyuz Dec 23 '18 at 15:54
  • @RudyVelthuis I Update the question to show you the real problem i m facing – zeus Dec 23 '18 at 20:53
  • @Loki: good. FWIW, "but anyway my question was not about is it good or bad to access private members :" Neiher was my comment. It was merely to show that you can have hidden fields you don't directly see in the source code so your method may not work or not work reliably for each class.. – Rudy Velthuis Dec 24 '18 at 05:55

2 Answers2

5

If you are in control of all the classes, it is a bad design choice. However, it seems you are trying to access some VCL internals. If you are sure there is no clean way to obtain the information required, what you did is correct.

You are basically defining a new class with the same layout as the original one, and then, you cast without type checking: due to the fact the that the field you are going to access is at the same offset in both the classes, you'll end up on accessing the desired memory.

Your second example works because user fields are aligned correctly. If you want to access to A's private fields, it is better to have an accessor with the minimal amount of padding just to reach the desired offset, this way you'll have fewer fields to maintain. You should document what field(s) you want to access for future reference, along with original class definition so you can spot layout changes over time.

Keep in mind that this dirty trick will no longer work as expected when the original class changes its own layout. Depending on how the class is refactored, your code could simply read an incorrect value, or crash with an access violation.

Update At fist the OP was asking to access a private field in a plain class. Interfaces are not dealt with this answer as they are laid out as described by @rudy and clearly it gets hard to access those fields.

Yennefer
  • 5,704
  • 7
  • 31
  • 44
  • Sorry, but this is bad advice. There may be hidden fields that, if not reproduced, can cause wrong offsets for the members of the fake class. **The layout of the fake class is not necessarily aligned correctly**, even in the example he gave. – Rudy Velthuis Dec 24 '18 at 06:28
  • 1
    @Rudy there are no hidden fields in the example in the question. Besides this answer talks about duplicating the memory layout, that includes anything that effect the layout. – Sertac Akyuz Dec 24 '18 at 09:55
  • 1
    *there are no hidden fields*, I mean at the time of the answer was posted. – Sertac Akyuz Dec 24 '18 at 10:53
  • @Sertac: the example loki first showed was too simple and certainly not his real code, so that can be disregarded. I warned about such problems in general, especially for slightly more complex scenarios. So in general, this answer is not ok. For this specific case, it is. Note that I wrote **"not necessarily"**. That is what I call: "you have been lucky". One of the main reasons certain errors don't get discovered for years. – Rudy Velthuis Dec 24 '18 at 15:45
  • @Rudy - This answer is always ok, once you duplicate the memory layout, nothing will make it a non-duplicate - no complex scenario can change that. Unless what you duplicated changes, which this answer does warn about. – Sertac Akyuz Dec 24 '18 at 15:55
  • @Sertac: I am saying that while you **think** you may be duplicating the memory layout, you may actually not be doing that, because of hidden fields. -- The fake class does not duplicate the hidden interface field, but fortunately that comes *after* the field he wants to access, so **in this case**, it doesn't matter. It could just as well have been otherwise. – Rudy Velthuis Dec 24 '18 at 15:58
  • @Rudy - Duplicating the memory layout is not something challenging, you just ask the debugger of the field address. Then insert the required padding before the field you want to access, just like this answer tells. [Here](https://stackoverflow.com/a/20484122/243614) is an example. – Sertac Akyuz Dec 24 '18 at 16:03
  • I never claimed it was challenging. I am merely saying that one must be aware of the fact that simply copying the source code will not always suffice, since hidden fields may (or may not) mess up the layout of the members. If you're lucky, they don't. And I generally use a method of mine that gives field offsets using RTTI. – Rudy Velthuis Dec 24 '18 at 17:42
  • @Rudy - You said, various times, hidden fields can change the memory layout hence the method is unsafe. It is your voting reason on this answer. It is your reason for posting another answer. Fields don't matter. No one is talking about fields, not this answer for instance. That's because you don't need fields to duplicate your memory layout. As for the particular case in the question, it is already given that the memory address is correct - the question states that. – Sertac Akyuz Dec 24 '18 at 18:32
  • @Sertac: I have no idea what your problem is. Fields are very important, since loki wants access to certain fields, like FWidth, etc. That only works if the memory layout of the fake class **is the same** as that of the original class. That may not be true if there are hidden fields in te original class but not in the fake class, i.e. if you simply copy the source code and change private to public. **What is so hard to understand about that, or what is wrong about it?** The question says that it works here and asks if that is a correct way to handle this. No, it is not always. – Rudy Velthuis Dec 24 '18 at 18:41
  • @Rudy - The fields that are not important are the ones that you don't need to access. Hidden or not, you don't care for that. What matters is the address of the field that you want to access. Obviously I failed to explain my problem though... – Sertac Akyuz Dec 24 '18 at 18:44
  • @Sertac: an example. Say the original class adds the interface pointers (say, ther eare three like in the diagram) directly **after** the inherited fields. Then the first new field will have an offset of (ancestorClass.InstanceSize + 3 * sizeof(pointer)). But if you simply copy the source code but **don't declare that the class implements 3 interfaces**, the fake copy will not have these 3 pointers **before** the first field. **So the memory layout of the fake is wrong**. The first field of the fake class will be at offset (ancestorClass.InstanceSize). con'td --> – Rudy Velthuis Dec 24 '18 at 18:54
  • ... In this particular case, the three pointers are added **after** the new fields, but that means that if you would have to fake a descendant too, you would be off by 3 pointers. And it is not guaranteed interface pointers are actually added at the end. That is a compiler detail. I'm not sure if that has always been like that (ISTR it was not), or if it will remain as it currently is. – Rudy Velthuis Dec 24 '18 at 18:58
2

Be careful. This may work, but I see that TTexture implements an interface. That means it will have a hidden instance field pointing at the interface VMT, like the IPrintable, IEDitable and IComparable fields in this diagram of mine:

enter image description here

Now if that hidden ITextureAccess field is inserted before the private members (before the more fields part of the class in the diagram), it is very likely that the members of your fake class (the more fields, but without the hidden field) will have the wrong offset, so your trick might not work at all.

So you should at least test for such a situation. If you did, and you get the expected results, you have a valid hack.

Your use of a warning message is commendable.

Update

I checked the offsets for TTexture. The interface pointer for ITextureAccess is inserted after the FContextResetId member field. Any offsets before that will be valid.


But TTexture has public properties for the attributes that you want to set: Handle, Width, Height, and the interface ITextureAccess allows you to set the scale, so you may not need this.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • The question already mentions that the hack has been tested and produced the expected result. – Sertac Akyuz Dec 24 '18 at 10:55
  • What I mean is, what remains is a warning about a non-existing danger. – Sertac Akyuz Dec 24 '18 at 12:04
  • @Sertac: **To him**, it may **currently** be a non-existent danger. It may still be there for others attempting something similar. And I didn't test this in older versions. Fact is that the hack is dangerous without regarding this. – Rudy Velthuis Dec 24 '18 at 15:40
  • *"That means it will have a hidden field pointing at the interface VMT"* - Why is that, what is an instance have to do with an interface pointer? Isn't the interface table of the class VMT responsible for that? – Sertac Akyuz Dec 24 '18 at 16:01
  • @Sertac: All class instances will have a hidden **instance** field for each interface the class implements. That is how things work. See the diagram or [read my article](http://rvelthuis.de/articles/articles-pointers.html#interfaces). Now it is not documented where these hidden fields will be inserted. The VMT of the class does not contain that info, the VMT of the interface does. Note that you can change implementations of interfaces (e.g. using `implements` syntax) at runtime, so each instance must have a pointer to the interface VMT. This is per-instance, not per-class. – Rudy Velthuis Dec 24 '18 at 17:51
  • That's quite interesting. In fact the class [VMT](http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Internal_Data_Formats_(Delphi)#Class_Types) has the information. A [function](http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.TObject.GetInterfaceTable) to retrieve it. A [function](http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.TObject.GetInterfaceEntry) to retrieve an individual entry - which also includes the [VMT](http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.TInterfaceEntry) of the interface. Honestly I have no idea how "implements" works though. – Sertac Akyuz Dec 24 '18 at 18:38
  • @Sertac: the interface variable *must* point to the class instance or to an offset inside it (so different instances as well as different interface types are distinguishable). Take a look at how interfaces really work. I have no idea what you mean with "a function to retrieve it". – Rudy Velthuis Dec 24 '18 at 18:46
  • With all the links the comment didn't fit and what I removed crippled it. Nothing special, I meant to say there is a function that retrieves the interface table. – Sertac Akyuz Dec 24 '18 at 18:48
  • @sertac: it retrieves the table of interface pointers. but these pointers do actually point to the VMT for the interface. With interfaces, there is a lot of indirection. See diagram. – Rudy Velthuis Dec 24 '18 at 19:01