Jump to content

Remy Lebeau

Members
  • Content Count

    3001
  • Joined

  • Last visited

  • Days Won

    135

Posts posted by Remy Lebeau


  1. TIdSNTP.SyncTime() uses the Win32 SetLocalTime() function, which requires the calling process to have the SE_SYSTEMTIME_NAME privilege present in its user token (even if it is not enabled, it just needs to be present - SetLocalTime() will enable it for the duration of the call).  By default, only an elevated admin has that privilege present in its token.  So, you will have to either run your whole app as an elevated user, or at least split out your sync code into a separate process or service that runs as an elevated admin or the SYSTEM account.


  2. 3 hours ago, Mike Torrettinni said:

    I was thinking of wrapping this control into 32 bit dll with Form/Frame and then I can move the project to 64 bit and integrate the Form/Frame from the dll.

    Right idea, but wrong solution.  A DLL Surrogate can be registered to act as a proxy, allowing a 32bit DLL server to be accessible to a 64bit process.  Not sure if this will work for your ActiveX control since it uses a UI, but it might be worth investigating.

    3 hours ago, Mike Torrettinni said:

    I have no experience with dll that can show visual controls, Forms/Frames.

    For that to work correctly, you would need to make a BPL package, not a plain DLL.  But the same 32bit/64bit restriction applies to packages, too.

    3 hours ago, Mike Torrettinni said:

    Is this worth exploring or 32 bit dll with form/frame can't be used in 64bit project?

    No, a 32bit DLL/BPL cannot be used directly in a 64bit process.  If the above doesn't work for your control, you may have to separate out your UI into its own 32bit program, and have your main 64bit program communicate with it using an IPC mechanism.

    • Thanks 1

  3. I doubt that Delphi has natively imported the MediaStore.Images.Media class, but you should be able to import it manually using Java2OP, then you can access the EXTERNAL_CONTENT_URI field like this, eg:

    var
      pickIntent: JIntent;
    
    // replace TJMediaStore_Images_Media with whatever name Java2OP imports for it...
    pickIntent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_PICK, TJMediaStore_Images_Media.JavaClass.EXTERNAL_CONTENT_URI);

     


  4. TIdMessage is designed to parse RFC822-style emails only (EML files, etc).  Attempting to load anything else is basically undefined behavior, there is no guarantee if/how errors will be reported back into user code.  There is no single point of query where you can discover the result of a failed parse.  So, your best option is to just filter out non-RFC822 files before you try to load them into TIdMessage.  Basically, analyze a handful of bytes at the beginning of a given file, and if they don't appear to resemble RFC822-style headers then simply don't load the file at all (all of the non-EML file formats you have mentioned are well-documented and easily identifiable).  This is something I may consider adding to TIdMessage itself in a future release, but that is not going to happen anytime soon, so I suggest you add it to your own code.

    • Thanks 1

  5. In non-virtual mode, each list item has its own physical TListItem object in memory, and their respective data is stored directly in the ListView control itself (well, in its HWND, anyway).

     

    In virtual mode, all data is held in the user's code instead.  There is only 1 physical TListItem created, used internally by the TListView, where its content is reset each time an operation is performed that requires a physical TListItem object.  For instance, when reading the TListView.Items[index] property, or calling method that takes a TListItem as input or returns a TListItem as output.

     

    IN MOST CASES, this design allows non-virtual code to continue working the same way as in non-virtual mode, just with the addition of the OnData... event handlers.  But there are some corner cases to be aware of where this design does not work as expected, for instance doing tasks that require access to more than 1 TListItem at the same time.  Your example demonstrates this when by causing an operation that resets the TListItem that is being observed.


  6. 6 hours ago, Mark Williams said:

    TIDMessage's date property shows the UTC time

    The TIdMessage.Date property is expressed in LOCAL TIME, not in UTC.  The only way the TIdMessage.Date property would be in UTC is if either the calling machine is actually in the UTC timezone (which you say is the case here), or if Indy can't determine the local machine's timezone correctly.

     

    If you want to read an email's original timestamp (which may or may not be in UTC to begin with), you can read the raw 'Date' header via the TIdMessage.Headers.Values['Date] property.  But then you will have to parse the string yourself.

    • Thanks 1

  7. 7 hours ago, david_navigator said:

    Is my simplest solution to write an app that it can connect to to send any emails, (acting as an SMTP server) and then send the email on (acting as an SMTP client) using modern connection techniques ?

    If you can't update the app to use SSL/TLS, then yes.

    7 hours ago, david_navigator said:

    I was thinking of Indy (SMTP Server -> SMTP Client)

    That is an option, yes.  Though, using a full SMTP server (ie TIdSMTPServer) might be a bit excessive, a simpler connection proxy (such as TIdMappedPortTCP) might suffice, since you just need to activate SSL/TLS on the outgoing connection.  It really depends on whether you intend to utilize the SMTP STARTTLS command or not, since that will dictate WHEN the SSL/TLS handshake needs to be performed.


  8. 5 hours ago, Mark Williams said:

    So presumably I would have to reinstall INDY every time I install a new version of Delphi?

    Only if you are planning on compiling the IdCoderTNEF unit into the IndyProtocols package each time.  Which you don't NEED to do, you could simply drop IdCoderTNEF.pas into your project folder, or Indy source folder, and have your project(s) refer to that unit as-is.

    5 hours ago, Mark Williams said:

    Is there anything wrong with my alternative solution

    No.


  9. 6 hours ago, A.M. Hoornweg said:

    "tag" can fit a simple pointer-sized reference, sure, but you still need to handle ownership of the object. Also, "tag" has no way of knowing if the object it points to is still valid, you may need to clear the tag if the object is freed. That means writing boilerplate code.

    All of that applies to T(Object)Dictionary, as well.  If the TComponent or associated object is freed, you have to manually remove them from the dictionary to avoid dangling pointers.

    6 hours ago, A.M. Hoornweg said:

    TDictionary and tObjectdictionary are "better" because they can handle object ownership.

    TDictionary doesn't support object management at all.  TObjectDictionary, on the other hand, simply lets you specify whether the dictionary owns the objects stored as Keys/Values, in that if an entry is removed/altered in the dictionary, the associate Key/Value objects are freed automatically as needed.  But, if those same objects are freed external to the dictionary, the dictionary entry that refers to them is not removed automatically.  You still have to handle that manually.

    6 hours ago, A.M. Hoornweg said:

    If tDictionary manages the lifetime of the objects, there can't possibly be an invalid association

    The likelihood is reduced, but not eliminated.  It is still possible to have invalid references, if you are not careful.

    6 hours ago, A.M. Hoornweg said:

    and it reduces boilerplate code.

    In your code, perhaps, but not the overhead needed to manage the dictionary elements, hash tables, etc.  You are actually adding runtime overhead to the program just to gain writing simpler code.

     

    But whatever.  It is up to each user to decide whatever they are comfortable using.


  10. 23 hours ago, A.M. Hoornweg said:

    Please don't misuse "tag" for pointers.

    There is nothing wrong with using the Tag property for pointers.  It is specifically intended to hold user-defined data, and intentionally pointer-sized because storing pointers in it is very common practice.  It is even documented that way 

    Quote

    Tag has no predefined meaning. The Tag property can store any additional integer value for the convenience of developers. Often, Tag stores a pointer. A Tag value can be typecast to the appropriate pointer type. Notice that on 64-bit platforms, all pointer types are 8 bytes in size, while on 32-bit platforms, pointer types are 4 bytes. These pointer sizes correspond to sizes of NativeInt integral values on 64-bit and 32-bit platforms.

    So, there is no misuse here.

    23 hours ago, A.M. Hoornweg said:

    I'd rather use a tDictionary<tcomponent, tSomethingelse> to store associations between components and objects.

    That is your choice, and it will certainly work.  I prefer to use the Tag instead, it offers a 1:1 relationship without any overhead of a lookup table.

     


  11. 23 hours ago, Mark Williams said:

    I tried that route. Added to the protocols folder

    It is not enough to just add the file to the folder, you would have to actually edit the IndyProtocols package to reference the file.

    23 hours ago, Mark Williams said:

    and then tried to compile IndyProtocols270.bpl. Got an error "Cannot compile package 'IndyProtocols270' which is currently required by Delphi 10.4"

    Because it is still being used by the IDE.  You would have to remove Indy from the IDE first.  Have you read the installation instructions yet?


  12. 3 hours ago, Mark Williams said:

    Adding TIDCoderNef.pas to the protocols folder installed with Delphi doesn't work. Compiling fails to locate the unit.

    Then you don't have your project setup to include that folder in its search paths.  Simply adding the unit to the Protocols folder won't magically add it to Indy, since it is already compile.  That would require adding the unit to Indy's Protocols package, and then recompiling and reinstalling Indy.


  13. 21 hours ago, Alexander Sviridenkov said:

    TBytes and TIdBytes are binary compatible, both are just "array of byte".

    Binary compatible, in that they have the same memory layout, yes.  But you can't pass a TBytes to a 'var TIdBytes' function parameter, and vice versa, unless you use a type-cast, eg:

    type
      PIdBytes = ^TIdBytes;
    
    var
      Bytes: TBytes;
    
    SetLength(Bytes, 6 + Buffersize);
    CopyTIdString('Audio1', PIdBytes(@Bytes)^, 0);
    CopyTIdBytes(RawToBytes(Buffer^, Buffersize), 0, PIdBytes(@Bytes)^, 6, Buffersize);

    Or maybe just:

    var
      Bytes: TBytes;
    
    SetLength(Bytes, 6 + Buffersize);
    CopyTIdString('Audio1', TIdBytes(Bytes), 0);
    CopyTIdBytes(RawToBytes(Buffer^, Buffersize), 0, TIdBytes(Bytes), 6, Buffersize);

     

    • Thanks 1

  14. CopyTIdBytes() is just a Move() from one TIdBytes to another, using starting source/target indexes and a byte length:

    procedure CopyTIdBytes(const ASource: TIdBytes; const ASourceIndex: Integer;
      var VDest: TIdBytes; const ADestIndex: Integer; const ALength: Integer);
    {$IFDEF USE_INLINE}inline;{$ENDIF}
    begin
      {$IFDEF DOTNET}
      System.array.Copy(ASource, ASourceIndex, VDest, ADestIndex, ALength);
      {$ELSE}
      //if these asserts fail, then it indicates an attempted buffer overrun.
      Assert(ASourceIndex >= 0);
      Assert((ASourceIndex+ALength) <= Length(ASource));
      Move(ASource[ASourceIndex], VDest[ADestIndex], ALength);
      {$ENDIF}
    end;

    CopyTIdString(), on the other hand, is a little more involved, as it first has to convert the input String to bytes in a specified encoding, and then it moves those bytes into the target TIdBytes where specified:

    procedure CopyTIdString(const ASource: String; var VDest: TIdBytes;
      const ADestIndex: Integer; const ALength: Integer = -1;
      ADestEncoding: IIdTextEncoding = nil
      {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF}
      ); overload;
    {$IFDEF USE_INLINE}inline;{$ENDIF}
    begin
      CopyTIdString(ASource, 1, VDest, ADestIndex, ALength, ADestEncoding
        {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF}
        );
    end;
    
    procedure CopyTIdString(const ASource: String; const ASourceIndex: Integer;
      var VDest: TIdBytes; const ADestIndex: Integer; const ALength: Integer = -1;
      ADestEncoding: IIdTextEncoding = nil
      {$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF}
      ); overload;
    {$IFDEF USE_INLINE}inline;{$ENDIF}
    var
      LLength: Integer;
      {$IFDEF STRING_IS_ANSI}
      LTmp: TIdWideChars;
      {$ENDIF}
    begin
      {$IFDEF STRING_IS_ANSI}
      LTmp := nil; // keep the compiler happy
      {$ENDIF}
      LLength := IndyLength(ASource, ALength, ASourceIndex);
      if LLength > 0 then begin
        EnsureEncoding(ADestEncoding);
        {$IFDEF STRING_IS_UNICODE}
        ADestEncoding.GetBytes(ASource, ASourceIndex, LLength, VDest, ADestIndex);
        {$ELSE}
        EnsureEncoding(ASrcEncoding, encOSDefault);
        LTmp := ASrcEncoding.GetChars(RawToBytes(ASource[ASourceIndex], LLength)); // convert to Unicode
        ADestEncoding.GetBytes(LTmp, 0, Length(LTmp), VDest, ADestIndex);
        {$ENDIF}
      end;
    end;

    So, given your example, the simplest translation to TBytes would look like this:

    var
      Bytes: TBytes;
    
    SetLength(Bytes, 6 + Buffersize);
    TEncoding.Default.GetBytes('Audio1', 1, 6, Bytes, 0);
    Move(Buffer^, Bytes[6], Buffersize);

     

    • Thanks 1

  15. 5 hours ago, Mark Williams said:

    No. Definitely version 10. I have a host of IDCoder... units in the protocols folder, but no TIDCoderTNEF.

     

    Indy was installed same time as I installed Delphi 10.4. Just been back over the last couple of installs 18.0, 19.0, 20.0 and latest is 21.0. IDCoderTNef is not in the protocols folder of any of these installs.

    Like I said, the IdCoderTNEF unit has been in the Protocols folder for years.  But if you are using the version of Indy that shipped pre-installed with each IDE, and they don't have the unit present, then Embarcadero must have chosen not to distribute it.  The unit is not actually referenced by the IndyProtocols package, as it is not part of the main Indy packages, just a standalone utility unit, so Embarcadero may have just skipped it as a non-dependency.  In which case, you will have to download it from Indy's GitHub repo instead (https://github.com/IndySockets/Indy/blob/master/Lib/Protocols/IdCoderTNEF.pas).

    • Thanks 1

  16. On 11/8/2020 at 6:48 AM, Mark Williams said:

    @Remy Lebeau I have found reference to TIDCoderTNef, but I can't this component nor can I find an IDCoderTNef unit.

    Which version of Indy are you looking at?  TIdCoderTNEF was introduced in Indy 10, are you looking at Indy 9, perhaps?

    Quote

    I can't find anything in the help files either.

    TIdCoderTNEF is not documented in the help files.

    Quote

    Has this been replaced?

    No, TIdCoderTNEF still exists in Indy 10.

    Quote

    If not, where can I find it please

    TIdCoderTNEF has been in Indy 10 for over a decade.  It is in the IndyProtocols package.

    Quote

    is there any help on or any examples of how to use it that you are aware of?

    There is no documentation available for TIdCoderTNEF, other than comments in the unit itself.  I think it was created after the last time the documentation was updated, which was sadly a very long time ago.

     

    In a nutshell, you would simply extract a TNEF attachment from a normal email, and pass that attachment to TIdCoderTNEF.Parse(), which will then populate a TIdMessage with the relevant message data (headers, body, attachments, etc) from the TNEF.

    • Thanks 1

  17. 1 hour ago, Mark Williams said:

    If TIDMessage could handle MSG files I'd do it the same way. However, it doesn't

    Since the MSG format is publicly documented, it would be feasible to add support for it in Indy, in a manner that does not depend on Outlook APIs.  Similar to how Indy already does for TNEF (winmail.dat) attachments via the TIdCoderTNEF class.  But having the TIME to make such an implementation is an issue, for me at least.

     


  18. 14 minutes ago, Anders Melander said:

    Generally speaking, the API of most Windows common controls goes through the windows message queue. So my guess is that for each item in the listbox there's a lot of messages going back and forth

    Messages are used, yes (LB_ADDSTRING, etc).  But not the message queue, no.  The messages in question would be SENT directly to the ListBox's window procedure for immediate processing, not POSTED to the message queue of the thread that owns the ListBox awaiting dispatching to the ListBox's window procedure.

     

    • Like 1

  19. 40 minutes ago, AndrewHoward said:

    Why do these two statemets give different results ?

    '#$02' (w/ quotes) is a string literal consisting of 4 distinct characters: '#', '$', '0', '2'.  The string you are searching, 'bla,bla,#$02,bla', contains that 4-char substring in it, hence the result is > 0.

     

    #$02 (w/o quotes) is a single Char whose numeric value is 2.  The string you are searching does not contain that character in it, hence the result is 0.

    • Like 2
    • Thanks 1

  20. 3 hours ago, Fr0sT.Brutal said:

    What's so terrible in IPv6?

    Subnet broadcasting is supported only in IPv4, not in IPv6, you have to use multicasting instead.

    • Thanks 1
×