Jump to content

Remy Lebeau

Members
  • Content Count

    2685
  • Joined

  • Last visited

  • Days Won

    113

Everything posted by Remy Lebeau

  1. Remy Lebeau

    Updated Community Edition

    That is exactly what the Community Edition is.
  2. Remy Lebeau

    Updated Community Edition

    At my previous employer, we were stuck at v6.0, the PTBs would never let us upgrade beyond that. Personally, I never upgraded beyond XE2.
  3. Remy Lebeau

    IDGlobalProtocols GmtOffsetStrToDateTime

    I have added a new GetGMTOffsetStr() function to the IdGlobalProtocols unit.
  4. Remy Lebeau

    IPropertyStore MultiValue Strings

    Yes. And how to access the array of LPWSTR string pointers for any string-vector tag, like PKEY_Music_Artist. But that example makes the code very tag-specific, whereas my example should work for any tag generically.
  5. Remy Lebeau

    IPropertyStore MultiValue Strings

    Is there a reason why you seem to have completely ignored the code I gave you for this exact purpose?
  6. Remy Lebeau

    IDGlobalProtocols GmtOffsetStrToDateTime

    That is not likely to happen. Maybe add some overloads perhaps, but it would make more sense to simply make the declaration of RawStrInternetToDateTime() itself be public instead, and then you can call that. Or, to add a new function that parses a strong and just returns the GMT portion rather than the date portion. It is not as simple as you make it out to be. You are not just changing the declaration, you are changing the semantics of how the functions are to be called. That affects any code that calls the functions, not just in Indy itself, but in 3rd party code, too. And for that reason, I am not inclined to incorporate the change, at least in the way you have described it. If you submit a ticket to Indy’s issue tracker, alternatives may be considered.
  7. Remy Lebeau

    IPropertyStore MultiValue Strings

    PROPVARIANT has similarities to VARIANT, but they are actually separate types, so you should treat them that way. Why are you passing in MetaDataTagType as an input parameter? You should be looking at the vt member of the PROPVARIANT instead, which tells you the actual data type of the variant data, eg: procedure ExtractMediaTags(const FileName : String); var Store : IPropertyStore; function GetMetaDataValueAsString(const MetaDataTag : TPropertyKey): String; const VT_LPWSTR_VECTOR = VT_LPWSTR or VT_VECTOR; var v : PropVariant; i: UInt32; begin Result := ''; try OleCheck(store.GetValue(MetaDataTag, v)); try case v.vt of VT_BSTR: Result := v.bstrVal; VT_I2: Result := v.iVal.ToString; VT_I4: Result := v.lVal.ToString; VT_I8: Result := v.hVal.QuadPart.ToString; VT_FILETIME: Result := FileTimeToDateTimeStr(v.filetime); VT_LPWSTR: Result := v.pwszVal; // etc... VT_LPWSTR_VECTOR: begin if v.calpwstr.cElems > 0 then begin Result := v.calpwstr.pElems[0]; for i := 1 to v.calpwstr.cElems-1 do begin Result := ', ' + string(v.calpwstr.pElems[i]); end; end; end; // etc... end; finally PropVariantClear(v); end; except end; end; begin if SHGetPropertyStoreFromParsingName(PChar(FileName), nil, GPS_READWRITE, IPropertyStore, store) <> S_OK then Exit; AddMetaData(pd.DocProps, '70', GetMetaDataValueAsString(PKEY_Music_Artist)); AddMetaData(pd.DocProps, '74', GetMetaDataValueAsString(PKEY_Music_AlbumArtist)); end; Since you want to extract the PROPVARIANT data as a String, consider using functions like PropVariantToString() or PropVariantGetStringElem() instead, which handle these kind of details for you. See PROPVARIANT and VARIANT Functions for a more complete list of functions available. PKEY_MUSIC_ALBUMARTIST is the single primary artist. PKEY_MUSIC_ARTIST is a list of all of the artists. What is the actual numeric value of the PROPVARIANT.vt member? I'm guessing it is $101F (4127), which is an array of VT_LPWSTR strings. TVarType is meant for VARIANT, not for PROPVARIANT. Although they share many common types, PROPVARIANT has types that VARIANT does not, like VT_VECTOR (VARIANT uses VT_ARRAY instead).
  8. Remy Lebeau

    Office Assistant component..

    Simple - I never had a need for it in my production software. Oh sure, the technology was fun to work with, and all. I used it on my company's website for awhile to help new users navigate around the site, and in demo softwares to guide new users around screens and features, and in slideshows to explain to viewers what was being shown. But, in the end, it did not serve much practical use otherwise that couldn't be done in other simpler/more efficient ways. No. Microsoft itself used it in some of their released software. Look at the (negative) feedback they got from that. Not many people liked it. Of course, that was not the fault of the technology itself, but in the annoying ways in which Microsoft decided to use it.
  9. 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.
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. 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?
  16. 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.
  17. 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.
  18. 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?
  19. 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;
  20. 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.
  21. 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.
  22. 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?
  23. 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.
  24. 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.
  25. 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.
×