Jump to content

Remy Lebeau

Members
  • Content Count

    2349
  • Joined

  • Last visited

  • Days Won

    95

Everything posted by Remy Lebeau

  1. Remy Lebeau

    IDGlobalProtocols GmtOffsetStrToDateTime

    Yes. It strips off the UTC offset portion, and converts the remaining string as-is to TDateTime form. Yes. It is intended to convert UTC offset strings into TDateTime form, which are then added to/subtracted from TDateTime values returned by Raw/StrInternetToDateTime(). You can thank popular Internet protocols for that, with their long history of using non-standardized date/time formats. Much of which went away with standardized ISO 8601 formats. In your case, I would probably suggest simply parsing the date/time string backwards, token by token. If a token is in "(<timezone>)" format, you would have to manually convert it to the corresponding UTC offset (which sadly changes frequently in many timezones). Indy has a TimeZoneToGmtOffsetStr() function that does this, but it is private to the IdGlobalProtocols unit, it is used internally by GmtOffsetStrToDateTime(). If a token is in "±HHMM" format, then it is trivial to convert it as-is to a UTC offset. Sounds like it may be useful to add a new function to Indy, which takes in a date/time string as input and just splits it up into its various components as output. Maybe as a helper for RawStrInternetToDateTime() to call internally. Indy has TimeZoneBias() and OffsetFromUTC() functions, which return the current UTC offset in TDateTime form. Indy also has functions it is not a bug. As it should be.. Operating systems are not very good about keeping accurate history of past time zones. And in any case, date/time strings in this format as supposed to be self-contained, not relying on PC clock data at all. A string like "Sun, 8 Jul 2020 06:55:38 -0800 (PST)" literally claims to represent July 8 2020 at 6:55:38 AM in a timezone that is -8 hours from UTC, Period. Regardless of the fact that on July 8 2020 the Pacific time zone was actually operating in PDT and not in PST. So any competent software producing this date/time string at that time would have produced "Sun, 8 Jul 2020 06:55:38 -0700 (PDT)" instead, which is the correct string for that date/time. By lying to the parser, you are not going to get the result you want. And it is not fair to ask Indy to go digging through the OS or the Internet trying to verify if the date/time string is accurate or not. The string is taken at face value. Right, because your PC would be operating in the PDT time zone needed for July 8 2020, not in the PST time zone needed for Nov 8 2020. You would have needed to adjust the timezone info in the date/time string accordingly, not just the date. Yes, RawStrInternetToDateTime() converts and strips off the date/time portion of the string, modifying the string to leave the UTC offset portion, which can then be passed to GmtOffsetStrToDateTime(). You would have to parse the date/time string manually, none of Indy's existing public functions are designed for the specific task you are trying to accomplish. The private functions that would accomplish it are, well, private. And for one simple reason - because nobody ever asked for them to be made public before.
  2. Remy Lebeau

    Last ways to contact Embarcadero

    You can contact various higher-up individuals directly, like Marco Cantu, David Millington, Jim McKeeth, etc. They are generally good about helping users.
  3. Remy Lebeau

    Office Assistant component..

    Same here. Even though I was a Microsoft MVP for the MSAgent technology, I never actually used it in any of my production applications, only in things like guided tutorials and trade show presentations.
  4. Remy Lebeau

    Office Assistant component..

    Office Assistant was the predecessor to Microsoft Agent. Assistant started life in Microsoft Office 97, where 'Clippy' was just one of several animated characters you could choose from. The technology evolved into a standalone API that any application could utilize. Eventually the old Assistant tech was replaced with the newer MSAgent tech in Microsoft Office 2000. Though some features of Assistant didn't make it into MSAgent and remained specific to Office, and Office didn't utilize everything that MSAgent offered. Microsoft completely removed MSAgent from Windows in Win7. There is a separate install pack for MSAgent that you can download from Microsoft just for Win7, But from Win8 onwards, MSAgent is dead tech, you can't install it anymore, so if you want to use MSAgent in Win8 onwards then you have to use Double Agent instead. You mean, other than it relying on outdated technology? Or that creating custom characters for it is a PITA? Although, at its peak popularity, there were a lot of 3rd party characters you could download for MSAgent. I'm sure many of those downloads have dried up over the years, but there are quite a few still floating around. Back in the day, one of the main competitors to MSAgent was Living Actor. It had a similar concept as MSAgent, but with better graphics (ie, 3D vectored models instead of 2D bitmaps). It has continued to evolve over the years into something that is completely different than what it started out as. I'm sure there are plenty of other assistent-like alternatives nowadays, if you search around.
  5. Remy Lebeau

    Office Assistant component..

    LMAO 🤣 Never thought I would ever see the topic of MSAgent come up again. It has been so long since Microsoft killed it off (a decade!). And my earlier efforts to port MSAgent to other platforms was put on hold years ago (I still want to see Merlin flying around on my Android phone and iPad tablet ) Well, you don't NEED a component, but it can certain make things a little easier to work with. I wrote a full VCL wrapper for MSAgent: http://msagent.lebeausoftware.org, though it was written for C++Builder only at the time, I never got around to porting it to Delphi (I think I started that a long time ago, but I never finished it). Documentation for the MSAgent interfaces is on MSDN: https://docs.microsoft.com/en-us/windows/win32/lwef/microsoft-agent Microsoft Agent is actually broken up into two APIs - the MSAgent ActiveX Control, and the MSAgent Server Interface. The Control is primarily meant for scripting environments, whereas the Server Interface is meant for applications. So make sure you use the Server Interface for a better experience in Delphi.
  6. Note that this approach will only work if your app is compiled with the same compiler and RTL/VCL version as the target app, since you are reading the RTTI of your app, not the RTTI of the target app.
  7. Remy Lebeau

    Listener for TIdTcpClient

    Setting AByteCount=-1 will read however many bytes are currently available on the connection at that moment, waiting up to the ReadTimeout if the InputBuffer is empty. Setting AAppend=False will fill the TIdBytes with the requested number of bytes (for AByteCount=-1, that is the InputBuffer.Size), expanding the TIdBytes only if its length is less than the requested byte count. So, it makes sense that the length of the TIdBytes would not change if its current length (1+) is >= the requested byte count (0) so memory doesn't need to be reallocated. When using AAppend=False, you should either: pre-size the TIdBytes and then request a specific number of bytes (AByteCount > 0) set the TIdBytes to nil and let the read size it for you (AByteCount = -1) In your situation, you are mixing both together. To remedy that, just reset the TIdBytes to nil when you are done using it, before the next read. However, that being said, reading an arbitrary number of bytes will not work well with UTF-8 if there are non-ASCII characters present in the data, since the resulting bytes could straddle the middle of a multi-byte character, causing decoding to throw an error or corrupt the text. Is there a reason why you are using AByteCount=-1 in this manner? Do you not know the actual length of the UTF-8 text ahead of reading it? Why not? What protocol are you implementing that doesn't let you discover that length before reading+decoding the bytes? If the text is variable-length, the protocol needs to either specify the text length before the text bytes, or else terminate the text with a unique delimiter. Either way, you probably shouldn't be using ReadBytes() in this situation to begin with, there are other ways to handle these conditions, depending on the design of the protocol. Can you provide more information about the layout of the messages you are trying to read?
  8. That has nothing to do with the framework being used by the UI control. And certainly nothing to do with accessing offsets within that framework, unless you are trying to access the UI control's internal data members over the process boundary, which you should not be doing, especially just for using WM_GETTEXT. There are other, safer, ways to obtain a UI control's HWND from within another process, such as via UI Automation APIs. And once you do have a viable HWND, the only way to get an AV from using WM_GETTEXT is if you are passing in bad parameter values - either a bad buffer pointer, or an incorrect buffer length that allows a buffer overflow to happen. You don't need to worry about that just to fix an AV in your own code's use of WM_GETTEXT. Something else is going on. But that is what you have to resort to, if you are trying to access some other process's UI control internals. You can't access VCL objects across process boundaries.
  9. Thanks, I missed that part. In which case, this issue just got a whole lot more dangerous. Manipulating the RTL/VCL's data members directly, behind the RTL/VCL's back, is a HUGE risk. Without intimate and explicit knowledge of the exact version of Delphi the other process is using, you can't hope to access its private data SAFELY, for the very reason I stated earlier - data members can and do change between versions.
  10. Enhanced RTTI provides that information. See Working with RTTI, TRttiType.GetFields(), and TRttiField.Offset. For example: var Ctx: TRttiContext; Offset: Integer; Offset := Ctx.GetType(TControl).GetField('FParent').Offset; But why do you need to access the FParent and FWidth member directly? Why can't you use the TControl's public Parent and Width properties instead?
  11. Remy Lebeau

    Listener for TIdTcpClient

    Pretty close. Though, I do question the decision to derive TTsClient from TIdTCPClientCustom, rather than creating a TIdTCPClient data member internally. But either way will work. Yes. You could simply raise your own exception to see how the code will react. Either raise it in SendData() itself, or maybe in the client's OnWork... events. An exception handler around Socket.Write() and Socket.ReadLn() will work, provided an exception is eventually raised. If the connection is closed gracefully, a subsequent Write()/ReadLn() call will raise an exception immediately. But, if the connection is lost abnormally, it may (likely will) take awhile for the OS to notice so it can invalidate the connection. Until that time, no errors will be reported by the socket, and this no exceptions raised. ReadLn() will happily block the caller waiting for more data that will not arrive, and Write() will happily keep sending data to the socket's internal buffer until it eventually fills up, then Write() will start blocking the caller until the connection is closed/invalidated. In any case, I would suggest not handling EIdSocketError alone, but SysUtils.Exception generally, or at least EIdException. There are other types of Indy exceptions that could possibly be raised which are not derived from EIdSocketError, for example EIdConnClosedGracefully. Otherwise, simply don't catch any exception at all. If SendData() fails, let the caller handle the exception (you should, however, close the client connection if Socket.Write() fails, in case Socket.ReadLn() is blocked). If ReadLn() fails, TThread will terminate itself if a raised exception escapes from Execute() (the TThread.FatalException property will get set in that case, but the TThread.Terminated property will not). There is no such property in Indy. But you might be tempted to use the client's Connected() method, but don't. Internally it performs a read operation, which may corrupt the listener thread's reading if Connected() is called from outside of the thread. In your IsAlive() method, consider using TThread.Finished instead of TThread.Terminated (unless you override TThread.DoTerminate() to set TThread.Terminated=True if TThread.Execute() exits from an uncaught exception). I see that you are not terminating the listening thread if the client is disconnected by your application. Since your TTsClient class is derived from TIdTCPClientCustom, I would suggest creating the listening thread in an overridden DoOnConnected() method, and terminating and freeing the thread in an overridden DoOnDisconnected() method, eg: type TTsClientThread = class(TThread) strict private FClient : TIdTCPClientCustom; FMessages : IBSQueue<IBSXmlElement>; FPartMsg : String; strict protected procedure Execute; override; public constructor Create(AClient: TIdTCPClientCustom; const AMessages: IBSQueue<IBSXmlElement>); end; TTsClient = class(TIdTCPClientCustom) strict private FListener : TTsClientThread; FMessages : IBSQueue<IBSXmlElement>; strict protected procedure DoOnConnected; override; procedure DoOnDisconnected; override; public constructor Create(const AIpAddress: String; APort: Integer); reintroduce; destructor Destroy; override; function IsAlive: Boolean; function Pop(var AMessage: IBSXmlElement): Boolean; procedure SendData(const AMessage: IBSXmlElement); end; { TTsClientThread } constructor TTsClientThread.Create(AClient: TIdTCPClientCustom; const AMessages: IBSQueue<IBSXmlElement>); begin FClient := AClient; FMessages := AMessages; inherited Create(False); end; procedure TTsClientThread.Execute; var s : String; begin while not Terminated do begin s := FClient.Socket.ReadLn(Char.EOT, IndyTextEncoding_UTF8).Substring(1); if not s.IsEmpty then FMessages.Append(NewBSXmlFromString(s)); end; end; { TTsClient } constructor TTsClient.Create(const AIpAddress: String; APort: Integer); begin inherited Create(nil); FMessages := TBSGenerics.GenQueue<IBSXmlElement>; Host := AIpAddress; Port := APort; Connect; end; destructor TIdTCPConnection.Destroy; begin Disconnect; inherited; end; procedure TIdTCPConnection.DoOnConnected; begin FListener := TTsClientThread.Create(Self, FMessages); inherited; end; procedure TIdTCPConnection.DoOnDisconnected; begin if FListener <> nil then begin FListener.Terminate; FListener.WaitFor; FreeAndNil(FListener); end; inherited; end; function TTsClient.IsAlive: Boolean; begin Result := (FListener <> nil) and (not FListener.Finished{FListener.Terminated}); end; function TTsClient.Pop(var AMessage: IBSXmlElement): Boolean; begin Result := FMessages.Pop(AMessage); end; procedure TTsClient.SendData(const AMessage: IBSXmlElement); begin try Socket.Write(Char.SOH + AMessage.AsXmlDataString([]) + Char.EOT, IndyTextEncoding_UTF8); except Disconnect; // optional: re-raise... end; end;
  12. You don't need direct access to an object's raw memory in order to do that. Use RTTI instead, that is what it is meant for. Why? That is certainly a risk when you assume a certain memory layout, and then that layout changes between compiler/RTL versions. That is quite likely. As new features get added over time, new members are added, existing members get moved around, etc.
  13. Remy Lebeau

    Can you restart the LSP or do you have to restart the whole IDE?

    You would have to ask Embarcadero to confirm, but AFAIK no. The IDE creates the LSP processes and expects them to stay running, so if they die unexpectedly behind the IDE's back, a restart of the IDE is likely needed.
  14. The offsets of members should not matter, unless you are doing something low-level to access the underlying raw memory data directly. You are going to have to provide more detailed information about what you are really trying to accomplish. Right now, it is very confusing to understand without seeing exactly what you are working with. Is it a 3rd party framework that we can see online, or is it something custom you developed for yourself? What issues is it having in 10.4 that it doesn't have in previous versions?
  15. Remy Lebeau

    TIdHttpServer response timeout

    You are just going to have to debug your TIdHTTPServer service and find where the blockage is actually occurring. You say you can reproduce the issue locally, so it should be fairly easy to debug locally.
  16. Remy Lebeau

    Prefix unit local variable names

    Case doesn't really matter, especially in a case-insensitive language like Delphi. Hungarian Notation in general is very old-school.
  17. Remy Lebeau

    Prefix unit local variable names

    V is more commonly used for method arguments that are declared as var, while A is for arguments that are not var. L is more commonly used for method local variables, and G is more commonly used for unit local variables that are not consts.
  18. You do know that the RTL has its own native streaming framework for that exact purpose, don't you? It is called a DFM. It is not just for designing Forms at design-time. It can be used by anyone to stream any TPersistent-derived class, which includes UI controls. Look at the TStream.WriteComponent() and TStream.ReadComponent() methods. This implies to me that you are probably using overlay classes in C++ (C doesn't have classes) to access private members of those UI classes, by aligning members of the overlay class to coincide with the members of the UI classes. You should not be doing that. But even if you were, there are better ways to access private members, such as this template trick (and this follow-up). Yes, it does. It just doesn't report the size you are thinking of. It only reports the size of the class itself (same as sizeof() does in C/C++), but it does not account for any additional memory that members of that class allocate dynamically for themselves internally (strings, lists, buffers, etc). That only works if you are serializing a class into a stream. You are getting the total flat size of the data the class wrote to the stream, not the size of the class itself.
  19. Remy Lebeau

    TIDMessage Terminator Sequence

    I have never exported EML files from Gmail before, but it is not common for email to contain lines starting with periods, so it may be difficult to see at first whether dot transparency is actually being used or not. So, as a test, I just sent an email containing such lines to my Gmail, and sure enough the resulting EML file DOES NOT use dot transparency. Which makes sense, it shouldn't be. That will be true for any emails that do not contain lines starting with periods. But try loading such EMLs using just TIdMessage by itself and you will see the periods get chopped off. Hence the need for the TIdMessageHelper unit. 99.999999% of EML files will likely NOT be using dot transparency. Just the ones created by TIdMessage, or any other similarly broken email clients (which hopefully is rare). The problem with that approach is that you won't know when the load fails in step #2. There is no error reported, lines containing leading periods will simply have those periods chopped off. I would say instead to use TIdMessageHelper always with AUsesDotTransparency=False, unless you KNOW the EML file came from TIdMessage. And even then, you can still use TIdMessageHelper, just set AUsesDotTransparency=True instead in that case. I don't know where you read that, but it is not true.
  20. Remy Lebeau

    tlsv1 alert protocol version

    That is fine, Indy supports TLS 1.2. Just make sure you are using an up-to-date version of Indy so that TLS 1.2 is being supported properly - ie, by enabling the SNI extension, which most TLS 1.2 servers require, etc. Yes, it does. You can use a tool like SysInternals' Process Monitor or Process Explorer to see exactly which DLL files your app is actually using. Or, using Indy's GetSSLLibHandle() and GetCryptLibHandle() functions in the IdSSLOpenSSLHeaders unit, you can pass those handles to the Win32 GetModuleFileName() function. Then they should not be colliding with each other, unless those paths are in the OS's global search path. That is the first place the OS should be looking for them. But, if needed, you can use Indy's IdOpenSSLSetLibPath() function in the IdSSLOpenSSLHeaders unit to force it. You said earlier that you downloaded DLLs for 1.0.2u, you should be using those instead. The IdOpenSSLSetLibPath() function should be called only 1 time, preferably at program startup. That has no effect whatsoever on OpenSSL. That only affects Microsoft's own implementation of TLS, such as in the WinInet and WinHTTP APIs. Which OpenSSL does not use (and neither does Indy). Your previous code was just fine. The above line enables ONLY TLS 1.2. Not all servers have migrated to TLS 1.2 solely. Most still support at least TLS 1.1, and maybe TLS 1.0. So you should leave all three enabled. Unless you know for a fact that the target server supports only TLS 1.2.
  21. Remy Lebeau

    tlsv1 alert protocol version

    Which version of Delphi are you using? Which version of Indy? The code looks fine, so the issue has to be with the OpenSSL DLLs themselves, Do you know what version of TLS the server requires? Does it even support TLS v1.0..v1.2 nowadays? Maybe it requires TLS v1.3 instead? If so, you won't be able to use TIdSSLIOHandlerSocketOpenSSL for TLS v1.3, you will have to use this new SSLIOHandler instead (work in progress), which uses OpenSSL 1.1.x instead of 1.0.2. They don't belong there. Put them in your application's folder instead. Just FYI: https://www.indyproject.org/2020/06/16/openssl-binaries-moved-to-github/ Do you know what the old version was? Are you SURE about that? At runtime, what does Indy's OpenSSLVersion() function in the IdSSLOpenSSL unit report after OpenSSL has been loaded? How about the IsOpenSSL_TLSv(1_0|1_1|1_2)_Available() functions in the IdSSLOpenSSLHeaders unit? Have you tried looking at the TLS handshake in a packet sniffer, such as Wireshark? What TLS version is your app claiming to use?
  22. Remy Lebeau

    TIDMessage Terminator Sequence

    The help file (and the documentation in general) is old. It hasn't been updated in a very long time. It depends on how the EML file is formatted. EML files created outside of Indy typically DO NOT escape leading periods on lines in the message body, which TIdMessage.LoadFromFile() requires, because it expects the EML file to have been created by TIdMessage.SaveToFile(), which does escape leading periods. To address this, you can add the IdMessageHelper.pas unit to your 'uses' clause. It introduces new SaveTo...() and LoadFrom...() methods into TIdMessage (in versions of Delphi that support class helpers), as well as standalone TIdMessageHelper_SaveTo...() and TIdMessageHelper_LoadFrom...() procedures, which have an additional AUsesDotTransparency parameter added that can be set to False when needed. For example: uses ..., IdMessageHelper; //IDMessage1.LoadFromFile(FileName, False); IDMessage1.LoadFromFile(FileName, False, False); // or: TIdMessageHelper_LoadFromFile(IDMessage1, FileName, False, False); In what way does it fail? No. No. It is a bit more complicated than that, because TIdMessage currently uses TIdMessageClient internally to parse the EML, but TIdMessageClient is not designed for parsing emails from files, it is designed for parsing emails over protocols like SMTP, which escape the email data during transmission. See https://github.com/IndySockets/Indy/issues/135
  23. Remy Lebeau

    Enumeration Type as Parameter

    You should use the intrinsic GetTypeKind() function instead, eg: constructor TBSCatalogBuilder<T>.Create(ACatalogId: Integer; const AName: String); begin if GetTypeKind(T) <> tkEnumeration then begin raise Exception.Create('Passed type is not an enumeration.'); end else begin FTypeData := GetTypeData(TypeInfo(T)); ... end; end; This way, if T is an enumeration type, the compiler will omit the raise statement from the final executable, and if T is not an enumeration then it will omit the rest of the constructor code leaving just the raise, eg: constructor TBSCatalogBuilder<AnEnumType>.Create(ACatalogId: Integer; const AName: String); begin FTypeData := GetTypeData(TypeInfo(AnEnumType)); ... end; constructor TBSCatalogBuilder<ANonEnumType>.Create(ACatalogId: Integer; const AName: String); begin raise Exception.Create('Passed type is not an enumeration.'); end; The way you wrote the code originally using Assert() does not allow the compiler to make that same optimization.
  24. Remy Lebeau

    How to extend CE licence?

    Yes, because a 10.4 CE version has not been released yet.
  25. Remy Lebeau

    Resetting TIdHTTP before reuse

    TIdHTTP should already be handling that for you. You don't typically need to do anything extra when sending multiple requests over a single connection. Look at TIdHTTP.Request.Clear() and TIdHTTP.Response.Clear() Have you actually TRIED to send multiple requests over a single connection and are running in a REAL PROBLEM with it? If so, what? Or, are you just SPECULATING?
×