Jump to content

Remy Lebeau

Members
  • Content Count

    2684
  • Joined

  • Last visited

  • Days Won

    113

Everything posted by Remy Lebeau

  1. Not really, but we did have multi-byte and multi-codepoint character sequences, which MSDN claims CharNextA() and CharNextW() do handle. But not well enough, in this case. As that article describes, you need .NET 5, which was just released 4 1/2 months ago, to handle Grapheme clusters correctly in things like enumeration, etc.
  2. There is no such thing as a "normal graphic character" in Unicode. What you are thinking of as a "character" is officially referred to as a "grapheme", which consists of 1 or more Unicode codepoints linked together to make up 1 human-readable glyph. Individual Unicode codepoints are encoded as 1 or 2 codeunits in UTF-16, which is what each 2-byte Char represent. When a codepoint is encoded into 2 UTF-16 codeunits, that is also known as a "surrogate pair". That emoji is 1 Unicode codepoint: U+1F64F (Folded Hands) Which is encoded as 2 codeunits in UTF-16: D83D DE4F That will only allow you to determine the value of the 1st Unicode codepoint in the grapheme. But then you need to look at and decode subsequent codepoints to determine if they "combine" in sequence with that 1st codepoint. That emoji takes up 8 bytes, consisting of 4 UTF-16 codeunits: D83D DE4F DB3C DFFB Which decode as 2 Unicode codepoints: U+1F64F (Folded Hands) U+1F3FB (Emoji Modifier Type-1-2) And it gets even more complicated especially for Emoji, because 1) modern Emoji support skin tones and genders, which are handled using 1+ modifier codepoints, and 2) multiple unrelated Emoji can be grouped together with codepoint U+200D to create even new Emoji. For example: U+1F469 (Woman) U+1F3FD (Emoji Modifier Type-4) U+200D (Zero Width Joiner) U+1F4BB (Personal Computer) Which is treated as 1 single Emoji of a light-skined woman sitting behind a PC. Or, how about: U+1F468 (Man) U+200D (ZWJ) U+1F469 (Woman) U+200D (ZWJ) U+1F467 (Girl) U+200D (ZWJ) U+1F466 (Boy) Which is treated as 1 single Emoji of a Family with a dad, mom, and 2 children. See https://eng.getwisdom.io/emoji-modifiers-and-sequence-combinations/ for more details. Delphi itself only concerns itself with the encoding/decoding of UTF-16 itself (especially when converting that data to other encodings, like ANSI, UTF-8, etc). Delphi does not care what the UTF-16 data represents. Graphemes are handled only by application code that needs to be do text processing, glyph rendering, etc. Things that are outside of Delphi's scope as a general programming language. Most of the time, you should just let the OS deal with them. Unless you are writing your own engines that need to be Grapheme-aware. By using a Unicode library that understands the rules of Graphemes, Emojis, etc. Delphi has no such library built-in, but there are several 3rd party Unicode libraries that do understand those things.
  3. Remy Lebeau

    Twsocket udp how to get the exact buffersize that received ? ?

    Even so, each read operation will only receive 1 datagram at a time. And as you said yourself, if you leave unread data in the socket when exiting the OnDataAvailable event, it will just be fired again. So, you can read 1 datagram per event triggering, no need for a loop inside the event (although you can certainly do that, too - unless the socket is operating in blocking mode, in which case DON'T use a loop!). If application data spans multiple send() calls, then it would be treated as separate datagrams, and each datagram is independent of other datagrams, regardless of how the network fragments them into packets during transmission. The socket provider handles fragmentation, so 1 send() = 1 read() as far as applications are concerned. And in that vein, it makes no sense to use delimiters across UDP datagrams, since each datagram is self-contained. Record data should not span across datagram boundaries. I would not treat UDP as a stream, because it is not a stream. UDP is message-oriented, not stream-oriented. Each datagram is meant to be treated on its own, regardless of other datagrams. For example, when sending records over UDP, only 1 send per record should be used, not multiple sends per record, that simply does not work over UDP.
  4. Remy Lebeau

    TIdHTTP to TNetHTTPRequest

    I have no idea. I don't use T(Net)HttpClient, and don't even have the RTL source code for them to look at.
  5. Remy Lebeau

    TIdHTTP to TNetHTTPRequest

    The preferred way to specify authentication credentials in THttpClient is to use its CredentialStorage property, eg: httpClient.CredentialStorage.AddCredential(TCredential.Create(TAuthTargetType.Server, '', '', 'username', 'password')); Or its AuthEvent event: procedure TMyForm.HTTPClientAuthEvent(const Sender: TObject; AnAuthTarget: TAuthTargetType; const ARealm, AURL: string; var AUserName, APassword: string; var AbortAuth: Boolean; var Persistence: TAuthPersistenceType); begin if AnAuthTarget = TAuthTargetType.Server then begin AUserName := 'username'; APassword := 'password'; end; end; httpClient.AuthEvent := HTTPClientAuthEvent; See Using an HTTP Client: Handling Authentication and Certificates in Embarcadero's DocWiki.
  6. Remy Lebeau

    Twsocket udp how to get the exact buffersize that received ? ?

    That is true for TCP, where there is no 1:1 relationship between sends and reads, it is just a stream of bytes. But that is not true for UDP, where there is a 1:1 relationship, since sends/reads deal only in whole datagrams. A datagram can't span multiple read events, a read operation must read the whole datagram in one go, or else the unread portions will be lost.
  7. Remy Lebeau

    Returning a dynamic array from DLL

    The 1st parameter of VarArrayCreate() does not take an index and a count, as you are suggesting. It takes lower/upper bounds (indexes) in pairs, one for each dimension of the array. So, specifying [0, 1] will create a 1-dimensional array with indexes from 0 to 1 (thus 2 elements). Whereas [0, 0] will create a 1-dimensional array with indexes from 0 to 0 (thus 1 element). An empty array would be [0, -1], indexes from 0 to -1 (thus 0 elements). Alternatively, if you know all of the array values up front, you can use VarArrayOf() instead of VarArrayCreate(). I have updated my earlier example to show that.
  8. Remy Lebeau

    Returning a dynamic array from DLL

    Using interfaces is fine, as long as compilers agree to a common ABI for them. Which typically means you need to use COM interfaces if you want non-Delphi/C++Builder compilers to interact with your code. The main reason your GetCategories() function crashed was two-fold: - it did not allocate any memory for its local TArray, thus when it tried to save an interface to the array's 1st element, it was going out of bounds. - it was returning a raw pointer to the interface stored in the local array's 1st element, not the array itself. But either way, that array and all of its elements were getting finalized when the function exited, thus you were returning a dangling pointer to invalid memory. To persist the array behind the function exit, you need to allocate it dynamically, and you need to increment the refcounts of the individual interfaces in the array. Using COM interfaces, you can let (Ole)Variant handle all of that for you, eg: type IProjectItemCategory = interface(IUnknown) ['{840DD036-8F0F-4B0F-97D0-AB76CCC2157B}'] function GetIdString: WideString; ... property IdString: WideString read GetIdString; ... end; IProjectItemWizard = interface(IUnknown) ['{6C65F413-F2E2-4554-8828-1FF10613855B}'] function GetCategory: IProjectItemCategory; safecall; function GetCategories: OleVariant; safecall; ... property Category: IProjectItemCategory read GetCategory; property Categories: OleVariant read GetCategories; ... end; ... function TTestWizard.GetCategory: IProjectItemCategory; safecall; begin Result := ...; end; function TTestWizard.GetCategories: OleVariant; safecall; var LManager: IProjectItemCategoryServices; LCategory: IProjectItemCategory; LArray: Variant; begin LManager := Services as IProjectItemCategoryServices; if LManager <> nil then begin LCategory := LManager.FindCategory('New'); if LCategory <> nil then begin LArray := VarArrayCreate([0..0], varUnknown); LArray[0] := IUnknown(LCategory); // alternatively: // LArray := VarArrayOf([IUnknown(LCategory)]); end; end; Result := LArray; end; procedure TProjectItemDialog.TreeViewFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); function IsGalleryCategory(const ACategory: IProjectItemCategory; const AIdString: string): Boolean; begin Result := (ACategory <> nil) and (ACategory.IdString = AIdString); end; function IsInGalleryCategories(const ACategories: OleVariant; const AIdString: string): Boolean; var I: Integer; begin Result := False; if VarIsArray(ACategories) then begin for I := VarArrayLowBound(ACategories, 1) to VarArrayHighBound(ACategories, 1) do begin Result := IsGalleryCategory(IUnknown(ACategories[I]) as IProjectItemCategory, AIdString); if Result then Exit; end; end; end; var LData: PCategoryNodeData; LServices: IWizardServices; LCount, I: Integer; LCategoryCount: Integer; LWizard: IProjectItemWizard; LCategory: IProjectItemCategory; LCategories: OleVariant; begin WizardControlList.ItemCount := 0; try FWizardList.Clear; LData := GetNodeData(Node); if LData <> nil then begin LServices := Services as IWizardServices; if LServices <> nil then begin for LCount := 0 to LServices.WizardCount - 1 do begin if Supports(LServices.Wizard[LCount], IProjectItemWizard, LWizard) then begin LCategory := LWizard.Category; LCategories := LWizard.GetCategories; if IsGalleryCategory(LCategory, LData.Id) or IsInGalleryCategories(LCategories, LData.Id) then begin FWizardList.Add(LWizard); end; end; end; end; end; finally WizardControlList.ItemCount := FWizardList.Count; end; end;
  9. Remy Lebeau

    Twsocket udp how to get the exact buffersize that received ? ?

    Another option would be to peek the socket first to query the size of the next available datagram without reading it, then allocate the buffer to that size, then read the datagram into the buffer. But a dynamic array of raw bytes is trivial to shrink in size, so over-allocating a little is not a bad thing (unless you are running in an embedded system).
  10. Probably due to this feature: https://www.indyproject.org/2014/12/22/new-https-functionality-for-tidhttp/ You MUST use an SSLIOHandler if you want to access an HTTPS url. Whether you create your own SSLIOHandler object, or let TIdHTTP create one implicitly, is a separate matter.
  11. Again, there is really nothing I cn do to help you without seeing you actual TCP server code. You clearly have a logic issue somewhere, but I can't see what you are doing with it. That workaround only minimizes the issue, but does not eliminate it. There could still be clients connected when the service is being shut down, and it only takes 1 misbehaving client/thread to cause the problem. Very little, since Embarcadero had incorporated the latest GitHub version (at the time) shortly before Sydney's release. There have been a number of checkins to GitHub since that time, but nothing that would address this particular issue.
  12. The TIdHTTP version would look like this: uses ..., IdHTTP, IdSSLOpenSSL, IdMultipartFormDataStream; var HTTP: TIdHTTP; SSL: TIdSSLIOHandlerSocketOpenSSL; Params: TIdMultipartFormDataStream; begin HTTP := TIdHTTP.Create; try SSL := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); SSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2]; // other options as needed... HTTP.IOHandler := SSL; HTTP.Request.BasicAuthentication := False; HTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer U4314taadsffhjjjjykllFVissffdfssdfsdfsgfgz'; // or: HTTP.Request.CustomHeaders.AddValue('Authorization', 'Bearer U4314taadsffhjjjjykllFVissffdfssdfsdfsgfgz'); Params := TIdMultipartFormDataStream.Create; try Params.AddFormField('message', 'abcTest'); Params.AddFormField('stickerPackageId', '1'); Params.AddFormField('stickerId', '113'); Params.AddFile('imageFile', 'D:\mypic.jpg'); HTTP.Post('https://notify-api.line.me/api/notify', Params); finally Params.Free; end; finally HTTP.Free; end; end;
  13. Remy Lebeau

    How to "dim" a TabSheet?

    A LONG time ago (9 years!), I posted a demo on StackOverflow for displaying an alpha-blended TForm as a dimming shadow over top of another TForm, while allowing individual controls to remain uncovered and interactive through the shadow: How do I put a semi transparent layer on my form Basically, the shadow TForm uses its AlphaBlend/Value properties to provide the dimming effect, and its TransparentColor/Value properties to mask out holes in the shadow where individual controls want to peek through. I'm sure a similar technique can be adapted for this situation.
  14. Remy Lebeau

    How to "dim" a TabSheet?

    Do note that solution is applying the WS_EX_LAYERED window style to a child window, which is supported only in Windows 8 onwards, and only if the app manifest specifies Windows 8 as a <supportedOS>.
  15. Remy Lebeau

    changing inherited control

    Makes sense. You didn't put TCustDBGrid into a package and install it into the IDE, so you can't use TCustDBGrid in a DFM resource, unless you call RegisterClass(TCustDBGrid) before the DFM is loaded when the TForm object is created. Because you are trying to actually call TForm1's inherited MouseDown() method and then assign its result to the event. Use this instead: unit Unit1; interface uses ..., Vcl.DBGrid, ...; type TDBGrid = class(Vcl.DBGrid.TDBGrid) protected procedure MouseDown(Sender: TObject; Button: TMouseButton; ShiftState: TShiftState; X, Y: Integer); override; end; TForm1 = class(TForm) ... recentinfo: TDBGrid; .. end; implementation procedure TDBGrid.MouseDown(Sender: TObject; Button: TMouseButton; ShiftState: TShiftState; X, Y: integer); begin if Button = mbRight then ShowMessage("testing"); inherited; end; No, because you are not registering the TCustDBGrid class with the DFM streaming system.
  16. Remy Lebeau

    Range Check Error ERangeError

    32bit vs 64bit? You really should not be casting to Integer at all. Cast to WPARAM instead, which is what PostMessage() is expecting procedure TEqlistFrm.VenueEditMainKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin PostMessage(Handle, CM_SEARCH, WPARAM(Sender), LPARAM(Key)); end;
  17. Remy Lebeau

    QueryPerformanceCounter precision

    See https://hero.handmade.network/forums/code-discussion/t/7485-queryperformancefrequency_returning_10mhz_bug Also of interest, see MSDN: https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
  18. It can be passed in as a normal parameter, and even used in ClassType comparisons, but it can't be used for type-casts, eg: function CreateAnObjectByClassName(const ClassName: string; BaseClass: TPersistentClass): TPersistent; var Cls: TPersistentClass; begin Cls := GetClass(ClassName); if not Cls.InheritsFrom(BaseClass) then raise Exception.Create('...'); Result := BaseClass(Cls).Create; // <-- doesn't work end; The compiler needs to know the actual class type that exposes the necessary constructor in order to call it directly. Otherwise, you have to resort to using RTTI to call the constructor, as demonstrated in the code Stefan linked to. There are alternative ways to accomplish that, just not as clean as what you are thinking.
  19. That is because neither TObject, TPersistent, nor TInterfacedObject have a virtual constructor that your descendants can override. The lowest RTL class that introduces a virtual constructor is TComponent. You need to add a virtual constructor to TMyClass and have desccendants override it as needed. And then declare your own metaclass type for your creation function to use. For example: type TMyClass = class(TInterfacedPersistent, IMyInterface) public constructor Create; virtual; end; TMyClassType = class of TMyClass; function CreateAnObjectByClassName(const AClassName: string): TPersistent; begin Result := TMyClassType(GetClass(AClassName)).Create; end; ... type TMyClass1 = class(TMyClass) public constructor Create; override; end; TMyClass2 = class(TMyClass) public constructor Create; override; end; RegisterClasses([TMyClass1, TMyClass2, ...]);
  20. Nope. There is no method of TRttiContext that takes an object instance as input. FindType() takes in a class type as a string, and GetType() takes in a class type as a TClass or PTypeInfo.
  21. Remy Lebeau

    TIdHTTP to TNetHTTPRequest

    In what way exactly? Rather than someone translating your TIdHTTP code, can you show your existing TNetHTTPRequest code that is not working for you?
  22. Remy Lebeau

    Where I can find the SSL DLLs for Indy?

    https://github.com/IndySockets/OpenSSL-Binaries https://www.indyproject.org/2020/06/16/openssl-binaries-moved-to-github/
  23. Remy Lebeau

    TMemo and unicode

    The code shown is not the correct logic to parse an RFC2047-encoded string. Try something more like the instead (though this could be simplified using IdGlobal.Fetch(), for instance): uses IdGlobal, IdGobalProtocols, IdCoderMIME, idCoderQuotedPrintable; procedure TForm1.convertbuttonclick(Sender: TObject); var s, s2, charset, encoding, data : string; i, j: Integer; begin s := Memo1.Lines[0]; s2 := s; i := Pos('=?', s); if i > 0 then begin Inc(i, 2); j := Pos('?', s, i); if j > 0 then begin charset := Copy(s, i, j-i); i := j+1; j := Pos('?', s, i); if j > 0 then begin encoding := Copy(s, i, j-i); i := j + 1; j := Pos('?=', s, i); if j > 0 then begin data := Copy(s, i, j-i); if TextIsSame(encoding, 'B') then s2 := idDecoderMIME.DecodeString(data, CharsetToEncoding(charset)) else if TextIsSame(encoding, 'Q') then s2 := idDecoderQuotedPrintable1.DecodeString(data, CharsetToEncoding(charset)); end; end; end; end; Memo2.Lines.Clear; Memo2.Lines.Add(s2); end; That being said, Indy already implements a decoder for RFC2047-encoded strings, in the DecodeHeader() function of the IdCoderHeader unit, eg: uses IdCoderHeader; procedure TForm1.convertbuttonclick(Sender: TObject); var s, s2 : string; begin s := Memo1.Lines[0]; // '=?UTF-8?B?5aaC5L2V6K6TIGFydC1tYXRlIOaIkOeCug==?=' s2 := DecodeHeader(s); Memo2.Text := s2; end;
  24. Remy Lebeau

    Smtp avoid spam classification

    It is hard to diagnose your issue without seeing the raw email data, and/or your code that is creating the email, and even knowing which version of which Pascal compiler you are using (Delphi vs FreePascal), since string handling varies by compiler and even by version. I can't answer that with the limited details you have provided so far. For instance, if you are using a Unicode version of Delphi (2009+ or later) or FreePascal (3.0+ with UnicodeStrings enabled), then yes, it should be converting to UTF-8 property. But if you are using a pre-Unicode version of Delphi/FreePasscal, then no, it will not convert to UTF-8 automatically, so you are responsible for encoding your own strings to UTF-8 before giving them to Indy. What does your actual text look like? This score implies that you have a lot of script changes happening inside of individual words. Which might just be a side effect of UTF-8 not being encoded properly. Hard to say without seeing the raw data. What do you have the TIdMessage.Encoding property set to? It is set to meDefault by default, which will be auto-updated to either mePlainText or meMIME depending on whether the TIdMessage.MessageParts collection is empty or not, respectively. A 'MIME-Version' header is generated only when TIdMessage.Encoding is meMIME. In general, you don't need to use MIME if your email contains only plain text, but it sounds like the provider may be requiring MIME. So, you may need to force the TIdMessage.Encoding to meMIME. UTF-8 is an 8-bit encoding, so it requires a transfer encoding that supports 8-bit data. '7bit' will truncate/zero the high bit of each byte, thus corrupting non-ASCII characters. So, you need to set the TIdMessage.ContentTransferEncoding property (and the TIdMessagePart.ContentTransfer property in entries of the TIdMessage.MessageParts collection) to either '8bit', 'base64', or 'quoted-printable' when using UTF-8 text to avoid any potential data loss. Note that '8bit' emails do require additional support from an SMTP server (features which Indy does not currently implement), so use 'base64' or 'quoted-printable', which are compatible with 7-bit systems. If the text is mostly ASCII characters, I would use 'quoted-printable'. If the text is mostly non-ASCII characters, I would use 'base64' instead.
  25. Remy Lebeau

    problem with file attributes

    Then you likely did not create the file where you are expecting. Are you using relative paths, for instance? What is the actual value of your fn variable? The filesystem does not lie about file existence. If it says a file does not exist, then it really does not exist. Double-check your input. With the same ERROR_FILE_NOT_FOUND error? I suggest you use SysInternals Process Monitor to see EXACTLY which file path your app is REALLY trying to access, and make sure it matches your expectation.
×