Jump to content

Remy Lebeau

Members
  • Content Count

    2337
  • Joined

  • Last visited

  • Days Won

    95

Everything posted by Remy Lebeau

  1. Remy Lebeau

    Where I can find the SSL DLLs for Indy?

    What does Indy's WhichFailedToLoad() function report after the error occurs? Are you using 1.0.2u for BOTH DLLs? Do you have other versions of OpenSSL on your system? Try using SysInternals Process Monitor to make sure your app is actually attempting to load the correct DLLs you are expecting, and not some other DLLs. It should work, yes.
  2. Remy Lebeau

    GMail Inbox

    Have you tried using \Inbox instead, as shown in GMail's IMAP Extensions documentation? IdImap4.SendCmd('UID STORE ' + rMess.UID + ' -X-GM-LABELS (\Inbox)', ['OK','BAD','NO'], true); That being said, why are you using the SendCmd() method manually? TIdIMAP4 has a UIDStoreValue() method for sending a UID STORE command: IdImap4.UIDStoreValue(rMess.UID, sdRemove, 'X-GM-LABELS', '\Inbox');
  3. Remy Lebeau

    Build managed dll in Delphi

    That is what I would suggest, too. Granted, writing PInvoke wrappers is not always the easiest task, but it does work well if you get it right. I've written several DLLs (in C++) that are used in C# via PInvoke.
  4. Remy Lebeau

    caFree & ,Free??

    I don't need to read the help on it, I already know exactly what it is and how it works, thank you. That is written simpler as (frmSecondForm <> nil). But either way, checking for nil STILL won't solve the issue, because the frmSecondForm pointer is NOT being set to nil when the TfrmSecondForm object is destroyed, so ANY check for nil after destruction will NOT work in your example. The code you have shown earlier will STILL have undefined behavior even with the nil check added: procedure TfrmFormMain.btn_Calls_SecondFormClick(Sender: TObject); begin frmFormSecond := TfrmFormSecond.Create(nil); try frmFormSecond.ShowModal; Application.ProcessMessages; // <-- object destroyed here if TfrmFormSecond.rdgrpCloseOptions.ItemIndex = 0 finally // if object is destroyed above, UB happens below because frmFormSecond is a dangling pointer! if frmFormSecond.HandleAllocated then FreeAndNil(frmFormSecond); // or if (frmFormSecond <> nil) and frmFormSecond.HandleAllocated then FreeAndNil(frmFormSecond); end; end; And before you say it, yes you could fix that specific issue by setting the global frmSecondForm pointer to nil during destruction, like in the Form's OnDestroy event, eg: procedure TfrmSecondForm.FormDestroy(Sender: TObject); begin frmSecondForm := nil; end; procedure TfrmFormMain.btn_Calls_SecondFormClick(Sender: TObject); begin frmSecondForm := TfrmFormSecond.Create(nil); try frmSecondForm.ShowModal; Application.ProcessMessages; // <-- object destroyed here if TfrmFormSecond.rdgrpCloseOptions.ItemIndex = 0 finally if frmSecondForm.HandleAllocated then // <-- if object is destroyed above, UB here because frmSecondForm is now nil! FreeAndNil(frmSecondForm); // or if (frmSecondForm <> nil) and frmSecondForm.HandleAllocated then // <-- no more UB here! FreeAndNil(frmSecondForm); end; end; But, that won't solve the issue if someone decides NOT to use the global pointer to begin with, eg: procedure TfrmFormMain.btn_Calls_SecondFormClick(Sender: TObject); var frm: TfrmFormSecond; begin frm := TfrmFormSecond.Create(nil); try frm.ShowModal; Application.ProcessMessages; // <-- object destroyed here if TfrmFormSecond.rdgrpCloseOptions.ItemIndex = 0 finally // if object is destroyed above, UB happens below because frm is a dangling pointer! if frm.HandleAllocated then FreeAndNil(frm); // or if (frm <> nil) and frm.HandleAllocated then FreeAndNil(frm); end; end; To solve that, you would have to do something radical along the lines of the following: procedure TfrmSecondForm.FormDestroy(Sender: TObject); type PTfrmSecondForm = ^TfrmSecondForm; begin PTfrmSecondForm(Tag)^ := nil; end; procedure TfrmFormMain.btn_Calls_SecondFormClick(Sender: TObject); var frm: TfrmFormSecond; begin frm := TfrmFormSecond.Create(nil); try frm.Tag := NativeInt(@frm); frm.ShowModal; Application.ProcessMessages; // <-- object destroyed here if TfrmFormSecond.rdgrpCloseOptions.ItemIndex = 0 finally if frm.HandleAllocated then // <-- if object destroyed above, UB here because frm is nil! FreeAndNil(frm); // or if (frm <> nil) and frm.HandleAllocated then // <-- no more UB here! FreeAndNil(frm); end; end; But, no matter what, using HandleAllocated() to determine whether the Form object needs to be destroyed or not is just plain WRONG. Object lifetime and window lifetime are two completely different things. If you Create() a Form (or any object), you need to Free() it when you are done using it, if that is not already being done elsewhere (by an Owner, by an OnClose event, etc). No, because I don't need to run it (and besides, I can't run it, as I don't have a working compiler installed at the moment). Just by looking at the code, my many years of experience and deep understanding of the VCL's inner workings tell me EXACTLY how this code will behave.
  5. You likely copied an incompatible version of the OpenSSL DLLs. For instance, if you copied OpenSSL 1.1.x, as TIdSSLIOHandlerSocketOpenSSL supports only up to OpenSSL 1.0.2 (use this SSLIOHandler instead for 1.1.x). Or, if you copied 32bit DLLs for a 64bit app, or vice versa. What do Indy's OpenSSLVersion() and WhichFailToLoad() functions report?
  6. Remy Lebeau

    caFree & ,Free??

    This example is ABSOLUTELY WRONG and MUST NOT be used. Yes, you can use HandleAllocated() to check if a UI control's HWND exists without creating a new HWND, but you can't use HandleAllocated() to determine if the UI object itself exists. If a Form uses Action=caFree in its OnClose event to request itself be destroyed, and the resulting CM_RELEASE message gets processed (as this example is doing), the Form object will be destroyed, and any subsequent attempt to access its members, including HandleAllocated() (as this code is doing) will be undefined behavior, and likely will result in a crash.
  7. Remy Lebeau

    caFree & ,Free??

    This is only dangerous if the code is using BOTH Action=caFree with Free() together, which makes no sense to do. Use only one or the other, not both.
  8. Remy Lebeau

    caFree & ,Free??

    Delphi 10.4+ uses traditional object lifetime management on all platforms (no more ARC on Android/iOS). So, any object you Create(), that does not have an Owner assigned, must be Free()'d.
  9. Remy Lebeau

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

    Those other libraries are likely either reading the UDP data for you and then giving you each datagram's actual data in the event, or they have peeked the socket to determine the available bytes and then telling you that size, ICS doesn't do either of those, it is just notifying you that datagrams have arrived, but is not doing anything to gleam information about them to present to you. For comparison, Indy's TIdUDPClient (which is not event-driven) requires you to provide it with a pre-allocated buffer for it to receive bytes into. But TIdUDPServer (which is event driven) will read each datagram into an internal buffer and then fire an event giving you the actual bytes that were read into that buffer. So, there is room for different mentalities, depending on your needs.
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. 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.
  15. 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.
  16. 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.
  17. 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;
  18. 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).
  19. 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.
  20. 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.
  21. 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;
  22. 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.
  23. 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>.
  24. 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.
  25. 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;
×