Jump to content

havrlisan

Members
  • Content Count

    31
  • Joined

  • Last visited

Posts posted by havrlisan


  1. 2 hours ago, @AT said:

    I'm wondering if anyone debugs non-windows application 🙂

    Many people do. I regularly debug my apps on Android and iOS, and less often on OS X. It's not as easy as debugging on Windows, but it's doable and most of the time helpful. I haven't attempted debugging on Linux, so cannot help you much there, although I can say that attaching the debugger may fail even when running the app through the IDE (especially on Android), so I presume attaching the debugger while the app is already running is even more painful.


  2. 4 minutes ago, Dalija Prasnikar said:

    Yes, it will call EnsurePoolToken, but if the global token already exists it will not lock PoolLock while it tries to create one.

    You're right, I see it now. Are there any downsides to that approach? And if not, is there a reason Embarcadero did not do that in initialization and finalization sections?

    Here's the link to the QP issue: https://quality.embarcadero.com/browse/RSP-44207


  3. 5 minutes ago, Dalija Prasnikar said:

    Possible workaround would be acquiring context at the application initialization (with KeepContext) and releasing it on shutdown. That way you would prevent EnsurePoolToken called from KeepContext to lock PoolLock. 

    I don't think acquiring the context would prevent the above call stacks from happening though:
    The first stack goes through System.Classes:BeginGlobalLoading, which directly calls EnsurePoolToken and is not avoidable.
    The second stack loads RTTI attributes with System.Rtti:ConstructAttributes method, which directly calls System.Rtti:Invoke method. In that method, TRttiContext is created right at the start.


  4. 8 hours ago, Vincent Parrett said:

    Both the VCL and FMX are both designed to operate in the main thread only, creating/updating UI elements in another thread is quite likely to cause you problems.

    Sorry, I obviously should have pointed out that Thread 1 is the main thread, but it shouldn't matter since the problem is related to Rtti.

    Update: I'm aware that one solution for this problem is to wait for one thread to finish using Rtti, then let the other continue. That is what I did, but I still want to hear your thoughts on this, and whether this is a bug that should be reported, or not.


  5. Hi. I ran across a deadlock situation in the System.Rtti on the OS X platform. Below is the call stack for both threads that lock each other:

    Thread 1:
    Fmx::Forms::TFrame::TFrame(System::Classes::TComponent*) + 299 (FMX.Forms.pas:7524,1 in SampleApp + 8570027) [0x1017c54ab] 1-25
    System::Classes::InitInheritedComponent(System::Classes::TComponent*, System::TMetaClass*) + 107 (System.Classes.pas:4859,1 in SampleApp + 1097787) [0x1010a503b] 1-25
    System::Classes::BeginGlobalLoading() + 13 (System.Classes.pas:4805,1 in SampleApp + 1096637) [0x1010a4bbd] 1-25
    System::Rtti::TRttiContext::KeepContext() + 123 (System.Rtti.pas:5665,1 in SampleApp + 1462715) [0x1010fe1bb] 1-25
    System::Rtti::EnsurePoolToken(System::DelphiInterface<System::IInterface>*) + 55 (System.Rtti.pas:5319,1 in SampleApp + 1607015) [0x101121567] 1-25
    System::Rtti::EnsurePoolToken(System::DelphiInterface<System::IInterface>*)::DoCreate(void*) + 72 (System.Rtti.pas:5306,1 in SampleApp + 1606392) [0x1011212f8] 1-25
    System::Rtti::TPoolToken::TPoolToken() + 197 (System.Rtti.pas:5253,1 in SampleApp + 1605765) [0x101121085] 1-25
    System::TMonitor::Enter
      
    Thread 2:
    System::Rtti::TRttiInstanceMethodEx::GetAttributes() + 41 (System.Rtti.pas:6452,1 in SampleApp + 1637625) [0x101128cf9] 1-25
    System::Rtti::TRttiObject::GetAttributes() + 86 (System.Rtti.pas:5788,1 in SampleApp + 1415254) [0x1010f2856] 1-25
    __stub_in48s__ZN6System4Rtti37LazyLoadAttributes_MakeClosure_ActRec7_0_BodyEv + 27 (SampleApp + 1622027) [0x10112500b] 1-25
    System::Rtti::LazyLoadAttributes_MakeClosure_ActRec::_0_Body() + 591 (System.Rtti.pas:5537,1 in SampleApp + 1622639) [0x10112526f] 1-25
    System::Rtti::ConstructAttributes(unsigned char*) + 176 (System.Rtti.pas:5496,1 in SampleApp + 1612512) [0x101122ae0] 1-25
    System::Rtti::ConstructAttributes(unsigned char*)::ConstructAttribute(void*, unsigned char*&) + 631 (System.Rtti.pas:5476,1 in SampleApp + 1611943) [0x1011228a7] 1-25
    System::Rtti::TRttiMethod::Invoke(System::TMetaClass*, System::Rtti::TValue const*, long long) + 234 (System.Rtti.pas:10149,1 in SampleApp + 1437914) [0x1010f80da] 1-25
    System::Rtti::TRttiInstanceMethodEx::DispatchInvoke(System::Rtti::TValue const&, System::Rtti::TValue const*, long long) + 3057 (System.Rtti.pas:6620,1 in SampleApp + 1641665) [0x101129cc1] 1-25
    System::Rtti::Invoke(void*, System::DynamicArray<System::Rtti::TValue>, System::Typinfo::TCallConv, System::Typinfo::TTypeInfo*, bool, bool) + 183 (System.Rtti.pas:9346,1 in SampleApp + 1634871) [0x101128237] 1-25
    System::Rtti::TRttiContext::Create() + 28 (System.Rtti.pas:5577,1 in SampleApp + 1462540) [0x1010fe10c] 1-25
    System::Rtti::EnsurePoolToken(System::DelphiInterface<System::IInterface>*) + 55 (System.Rtti.pas:5319,1 in SampleApp + 1607015) [0x101121567] 1-25
    System::Rtti::EnsurePoolToken(System::DelphiInterface<System::IInterface>*)::DoCreate(void*) + 29 (System.Rtti.pas:5304,1 in SampleApp + 1606349) [0x1011212cd] 1-25
    System::Rtti::TRttiContext::UseContext() + 119 (System.Rtti.pas:5687,1 in SampleApp + 1606663) [0x101121407] 1-25
    System::TMonitor::Enter(unsigned int) + 526 (System.pas:20052,1 in SampleApp + 131422) [0x100fb915e] 1-25

     

    The main issue is the order of entering the TMonitor locks, which I extracted here:

    Thread 1:
    TMonitor.Enter(GCTokenLock);       -- 5663:TRttiContext.KeepContext
    TMonitor.Enter(PoolLock);          -- 5253:TPoolToken.Create
    
    Thread 2:
    TMonitor.Enter(PoolLock);          -- 5524:LazyLoadAttributes
    TMonitor.Enter(GCTokenLock);       -- 5687:TRttiContext.UseContext

     

    Am I at fault for reproducing these two call stacks in multiple threads (creating a frame in one, calling TRttiMethod.GetAttributes in the other), or is this a bug in the System.Rtti unit?

     

    It's worth noting that the GCTokenLock object (and locking) is only present with the USE_MONITOR_FOR_GLOBALCONTEXT compiler directive, otherwise atomic operations are used:

    {$IF Defined(WIN32) or Defined(WIN64)}
      {$UNDEF USE_MONITOR_FOR_GLOBALCONTEXT}
    {$ELSE}
      {$DEFINE USE_MONITOR_FOR_GLOBALCONTEXT}
    {$ENDIF}

     


  6. 3 hours ago, Remy Lebeau said:

    FYI, the code shown is declaring two separate LPair variables. Get rid of the 1st one, its not being used.

    Thanks for your reply, Remy. The code was just a simplified example for demonstration, compiler would not even allow the same variable declaration twice. Come to think of it, such compiler behavior actually does confirm what Dalija said above. 

    2 hours ago, David Heffernan said:

    The question though is how many variables are there for an inline var inside a loop. Is there one variable? Or is the N variables where N is the number of times the loop body executes? 

    This is correct, thanks for clarifying.

    2 hours ago, David Heffernan said:

    The answer is that there is one variable. 

     

    Seems like this is the opposite behaviour from C#

     

    https://stackoverflow.com/q/271440/505088

     

    Personally I feel like Delphi has a poor design choice here. I'd expect a local variable that is defined inside a scope, to have a lifetime that was that scope. 

     

    With the C# design you can achieve both possible behaviours by defining the variable inside or outside the loop body. In Delphi you have to resort to putting the body in a separate procedure. 

     

    The we have C++ which allow capture by value or variable as specified by the programmer in code. That would also give the same flexibility but without requiring extraction to separate procedure. 

    I did not know this behavior existed in C#, but it is exactly what I wanted to achieve here. Unfortunately, I don't think we'll see such behavior anytime soon in Delphi.


  7. Sorry for reviving this thread, but I came across another usage issue with inline variables inside a for loop:

    var LPair: TPair<string, string>;
    for LPair in FDict do
      begin
        var LKey: string := LPair.Key;
        FList.Add(procedure
          begin
          	FDict.Remove(LKey);
          end);
      end;

     

    In this code I expected for LKey variable to stay on the stack as it is used in the anonymous procedure below, however, LKey will always end up with whichever value is in the last loop. Does this mean that declaring a variable inside a loop will only reinitialize it every time at the same address?

     

    Edit: to be clear, this is easily solvable by moving the block to a procedure with the key as a parameter. I'm just curious to find out the exact behavior of inline vars.


  8. On 6/9/2023 at 3:47 PM, Dalija Prasnikar said:

    The problem is that all variants of IGenericList<T> have same GUID so Supports function will succeed even though those different generic lists are not compatible.

    Come to think of it, a compiler directive for raising errors in such scenarios would be an awesome option. Something like overflow and range checking compiler directives.


  9. 14 minutes ago, Dalija Prasnikar said:

    There is not bug in compiler, but in your code.

     

    When you say 

    
    LCastedList.Add(TAncestorObject.Create)

    What will happen is equivalent of following code:

     

    
    var Tmp: IInterface := TAncestorObject.Create;
    LCastedList.Add(Tmp);

    This will be because LCastedList is declared as IGenericList<IInterface> and stores IInterface references.

     

    However when you retrieve stored reference you will call that on original list which is declared as IGenericList<IAncestorObject> and retrieved reference will be of type IAncestorObject and you can try to call LObj.ID on that retrieved reference. But in reality stored reference was IInterface, not IAncestorObject and it crashes when you call ID on it because that method (GetID) it simply does not exist there.

     

    Point of generics is that you have same type stored in the background, not to store incompatible types. even though there is same object behind those two interface references, those interfaces are completely different types represent different entry points to that object. Sometimes you can force "wrong" type to be stored at runtime, which is usually not what you should be doing, but if you do, you need to make sure that every time you retrieve something that might be "wrong" you cast it back to appropriate type.

     

    
        var
          LTmp := LList.First;
        var
          LObj: IAncestorObject;
        if Supports(LTmp, IAncestorObject, LObj) then
           var LID := LObj.ID ;

     

    You're right, thank you. I did think that was the issue as you've written, but I thought it would be implicitly cast into IAncestorObject when either Added to TList<T> or when retrieved via First().


  10. I've encountered a bug with generics which I believe is a mistake in the compiler. I'll try to be as detailed as possible throughout the code below. I'm only interested in opinions on what is causing this issue in the low-level code, and on possible workarounds that are not entirely different from the implementation I intended to do here. Note that this is a bare minimum of code implementation necessary to reproduce the bug.

     

    TL;DR skip to HERE to see where the bug occurs.

    program GenericsBugConsole;
    
    {$APPTYPE CONSOLE}
    {$R *.res}
    
    uses
      System.Classes,
      System.SysUtils,
      System.Generics.Collections;
    
    type
      //  Simple child class and interface inheriting from TInterfacedObject.
      //  Contains one property necessary to demonstrate the bug (by invokation).
      IAncestorObject = interface
        ['{8B57E255-F48D-4982-B9AF-71A6ABBCDBA4}']
        function GetID: TGUID;
        property ID: TGUID read GetID;
      end;
    
      TAncestorObject = class(TInterfacedObject, IAncestorObject)
      protected
        function GetID: TGUID;
      end;
    
      //  Generic list that acts as a wrapper for the generic TList<T>.
      //  Implementation is bare minimum for demonstration purpose.
      IGenericList<T: IInterface> = interface
        ['{B449EFCE-527D-4062-924E-A0E0421B8A16}']
        procedure Add(const AObj: T);
        function First: T;
      end;
    
      TGenericList<T: IInterface> = class(TInterfacedObject, IGenericList<T>)
      private
        FList: TList<T>;
      protected
        procedure Add(const AObj: T);
        function First: T;
      public
        constructor Create;
        destructor Destroy; override;
      end;
    
    // Classes implementation
      { TAncestorObject }
    
    function TAncestorObject.GetID: TGUID;
    begin
      // irrelevant
      Result := TGUID.NewGuid;
    end;
    
    { TGenericList<T> }
    
    constructor TGenericList<T>.Create;
    begin
      FList := TList<T>.Create;
    end;
    
    destructor TGenericList<T>.Destroy;
    begin
      FList.Free;
      inherited;
    end;
    
    procedure TGenericList<T>.Add(const AObj: T);
    begin
      FList.Add(AObj);
    end;
    
    function TGenericList<T>.First: T;
    begin
      Result := FList.First;
    end;
    
    begin
      try
        var
          LList: IGenericList<IAncestorObject> := TGenericList<IAncestorObject>.Create;
    
        //  HERE:
        //  This is the place where the compiler starts acting weird. The call to Supports() passes,
        //  and the debugger correctly shows that TCastedList is "TGenericList<IAncestorObject> as IGenericList<IInterface>".
        //  Calling LCastedList.Add() also passes, TAncestorObject.Create will be casted to IInterface (as per arg type).
        var
          LCastedList: IGenericList<IInterface>;
        if Supports(LList, IGenericList<IInterface>, LCastedList) then
          LCastedList.Add(TAncestorObject.Create);
    
        //  LObj is fetched from LList (IGenericList<IAncestorObject>),
        //  and the debugger also shows that correctly: "TAncestorObject as IAncestorObject".
        var
        LObj := LList.First;
    
        //  This is where the exception occurs. Throughout 3 different projects, I've received three different exceptions:
        //  1) 'c0000005 ACCESS_VIOLATION' -> Private console app, +300000 lines of code
        //  2) 'access violation at 0x00000001: access of address 0x00000001' -> Test FMX app, bare minimum code with TForm
        //  3) 'Privileged instruction' -> Not sure where I got this one, and I cannot get it again. Possibly on another test console app.
        var
        LID := LObj.ID;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    
    end.

     


  11. 26 minutes ago, Minox said:

    you're declaring the same variable 10 times, I don't think it's possible the way you mean, you could alternatively use a record type, and then call it in an array, like:
     

    
    type
      myRec = Record
              myVar : String[20];
              End;
    
    [...]
    
    var
      ArrRec : Array Of myRec;
    
    [...]
    
    procedure Sample(const AFixedArray: TSomeFixedArray);
    begin
      SetLength(ArrRec,10);
    
      for var I = 0 to 10 do
      begin
        ArrRec[I].myVar := 'xyz'+I.ToString;
        // do something
      end
    end

     

    That's just unnecessarily complicating things. I'd generally declare the variable before the loop (whether it be before begin or as an inline variable, it doesn't matter), I am just wondering if the compiler is able to interpret the variable declaration as it should (?) in loops.


  12. Will there be any issues with declaring inline variables inside loops? Here's an example:
     

    procedure Sample(const AFixedArray: TSomeFixedArray);
    begin
      for var I = 0 to 10 do
      begin
        var LInlineVariable := TSomeFixedArray[I];
        // do something
      end
    end

     

    Is it possible that the compiler will allocate more memory than needed, or something in that context? This seems to me like the only way the inline variable feature may produce issues.


  13. 5 minutes ago, TazKy said:

    Well, I got 33 installed.  One victory.  However, next problem is staring me in my face.  I do not have a TMapView component.  Google is of little help.

     

    image.thumb.png.7122709bc1d741a2eb852e1e15d39dac.png

    You should add the following in your manifest file:

    android:exported="true"

    Here's a pretty good explanation for that: https://www.cafonsomota.xyz/android-12-dont-forget-to-set-android-exported-on-yout-activities-services-and-receivers/


  14. When they say they support Android 13, I believe they mean that you can build an application for that version. However, that does not mean they actually have the latest SDK. You can build apps for Android 13 with older SDK versions, such as the one that Embarcadero provides with RAD studio installation, Android SDK 25.2.5. I highly doubt they'll upgrade their SDK anytime soon as that means they'll have to update all the java interfaces written in Delphi (Androidapi units) and add new ones. That's too much work for them, considering they focus on sales rather than fixing and updating the current code. After all, someone must catch the ChatGPT train! 🤮


  15. 3 hours ago, Lars Fosdal said:

    Not sure why the colors for this are not one single color entry, but two.

    Foreground - Sync edit highlight (it appears only the foreground color is used)

    Background - Sync edit background (it appears only the foreground color is used)

     

    Edit: But no - I don't know why the template "helper" goes away when you switch focus, but the behaviour is the same in 11.1.

    The Sync edit highlight always seems on top, even though the current selection should be on top. Here's a better screenshot to understand what I'm trying to say:

    image.png.51ced2591e786fb4cc3c15c047a2c01b.png
    The selected text is = Some text to

     


  16. This seems to be a similar issue, but starting from 11.3, initializing a code template (ie. for template) will now mark the "fields to fill" with another shade of blue, that overthrows the selection color. Try creating the template, write some text in the fill field, and try selecting it with ctrl+arrows.

    This is what I mean:

    image.png.15c7502ac25c5e7f78ea3a8f2cebc694.png

    As soon as RAD studio lost focus (I switched to my browser), the template fill fields disappeared, and this is left:

    image.png.b38918105caef41dd3bc5066c48f04c4.png

     

    This is a bug obviously, but did someone find a workaround?


  17. 6 minutes ago, Lars Fosdal said:

    TBitmap can be created and manipulated in a thread, but there are caveats.

    https://www.google.com/search?q=delphi+tbitmap+threadsafe

    If by caveats you're referring to forced canvas locking or unpredictable behavior, then "can be created" doesn't really mean anything.

    Creating TBitmap creates TBitmapImage, which calls CanvasClass.InitializeBitmap() method that initializes a bitmap handle that is specific to the graphics engine. In Windows case, the class is TD2DBitmapHandle, and wouldn't you know, this is in its constructor:

    FContextLostId := TMessageManager.DefaultManager.SubscribeToMessage(TContextLostMessage, ContextLostHandler);

    and it all goes down the drain.


  18. 2 hours ago, Dalija Prasnikar said:

    In theory when writing and GUI framework from scratch, it could be possible to make that framework thread-safe.

     

    But drawing is not the only part that needs to run in the main thread. Any interaction with the OS - creating OS handles (like windows, graphic objects...), mouse interaction, keyboard interaction, OS notifications, needs to run in the main thread. too. Also access to all resources would have to be protected by locks. That means every single field and property inside control. So while background threads would be running doing some work on those controls, UI thread would not be able to use them. Now it may look that this would still be solution if you want to just create and destroy controls in background threads, but again to do that safely ALL access ALL the time have to run through locks, That means even if you never use background threads, accessing and working with UI controls would be much slower ALL the time. Multithreading is not cost free. 

     

    I don't know what is your intent behind the question. Again, the fact that FMX is not thread-safe is not a flaw in its design, or anything similar. It is because thread-safe UI frameworks are basically non existent (I am saying basically, because like I said in theory they are possible, but I don't know about any). All major OS-es have thread-unsafe UI frameworks: Windows, Linux, Android, iOS, macOS - simply because making those thread-safe does not make sense. 

     

    Thread safety is not just about throwing locks around some lists, but also defining what interactions need to logically happen without any interruptions from other threads. That would mean locking would have to extend to much broader code besides just accessing lists, and the more complex the scenarios (and UI are extremely complex frameworks with complex code paths) the more chances are that there will be bugs and concurrency issues that cannot be easily resolved.

     

    Patching FMX to add thread-safety is impossible as it is not just adding few locks here and there, it would have to be thorough rewrite. If you want to write your own framework, you are always free to do so, but I would have to ask "You and what army?" 

    This is exactly what I was asking for, thank you very much for the explanation. I got some things mixed up in my head and started concluding that such implementation may be beneficial, hence why I asked about it. My initial thought was that TBitmap should be creatable and drawable in a background thread, but that doesn't seem possible because of global Canvas lock, and IIRC it also uses the TMessageManager (and that's where I expanded my opinion to all other components). That seems like a good idea, no?


  19. 26 minutes ago, Dalija Prasnikar said:

    Plenty.

     

    User interfaces in general (not just Delphi) are not thread-safe. Thread safety comes with hefty price. You would have to lock just about everything and it would significantly slow down the UI. Also, UI controls are complex and have plenty of interactions. Due to that complexity, making UI thread-safe would be complicated and error-prone. Not only that, but UI needs to interact with OS layer where UI controls are also not thread-safe as such where you cannot access the same control from different threads. That means if you would construct one control in background thread to be used in main thread afterward, not only you would have to make sure that interaction with Delphi UI side of controls have to be thread-safe, but control would have to avoid triggering any code that interacts with OS part which is almost impossible.

     

    UI is not thread-safe by design and that is something that will not change as it is not practical nor beneficial. 

     

    There are some parts around handling images that could be fixed to fully support processing images in background threads and then passing them to the UI for presentation.

    Thanks for the detailed explanation. So if I understood correctly, FMX is designed in such a way that it is nearly impossible to separate the rendering part (which is done in the main thread) from the rest of the code? If, for example, one would rewrite the framework from scratch, would it be possible to separate the whole control creation and manipulation from the actual canvas drawing? To my understanding, the only part that must be run in the main thread is the drawing on canvas (which directly uses graphics API).


  20. I'm aware that the creation and manipulation of FireMonkey controls are not thread-safe, but what does it take for Embarcadero to fix/reimplement to enable this behavior? I've gathered that the main issues most certainly arise from using the TList class without locks, such as TFmxObject.FChildren and TComponent.FComponents. Besides the properties, there's also the TMessageManager.Default which returns an instance of TFixedMessageManager, which is also not thread-safe (because it also uses TList without any locks).

    Besides these, what else could be an issue?

×