Jump to content

Remy Lebeau

Members
  • Content Count

    2684
  • Joined

  • Last visited

  • Days Won

    113

Everything posted by Remy Lebeau

  1. Remy Lebeau

    Recursive anonymous functions

    Inside MakeFib_Impl(), the object reference should be an interface reference instead, to better match what the compiler actually produces: function MakeFib_Impl: TFunc<integer,int64>; var fib: TFunc<integer,int64>; begin fib := MakeFibAnonClass.Create; // refcnt is 1 obj.fib := fib; // refcnt is now 2 Result := fib; end; Although this is a gross oversimplification, as the actual compiler implementation is much more complex (see Variable Binding Mechanism for details). The local fib variable inside of the calling function, and the captured fib variable inside the anonymous method, are actually the same variable in memory. But yes, the self-referencing issue still applies (just on a separate frame object that holds the fib variable). This self-referencing issue is even mentioned in the documentation:
  2. There is no way for us to diagnose the problem with just the log files alone. Clearly there is a problem inside of your Log() or SendFileToClient() methods, but you didn't show any of that code. The Access Violation is telling you the exactly memory address of the code that crashed, 0047C27C. Run the server inside the debugger and jump to that address and see what code is actually running there. On a side note, your OnCommandGet handler is catching and swallowing all exceptions. Don't do that. Indy uses exceptions for error handling, and certain exceptions should be handled by the server itself. For instance, so it knows when to shut down the calling thread when the client disconnects. So, at the very least, you should rethrow any caught exception that derives from EIdException.
  3. Remy Lebeau

    TreeView Grid lines

    It would help if you would show what you are looking at and explain exactly what you are trying to change in it.
  4. Here is a hint: you can save a PNG to a TMemoryStream using TPngImage.SaveToStream(), and then attach it to the email using the TStream overload of the TIdMessageBuilderHtml.HtmlFiles.Add() method. If you read the code I just gave you, it does exactly that. It is the same logic you posted earlier, I just reorganized and simplified it.
  5. TValue is designed to mimic only conversions that the compiler itself can perform implicitly. Custom variants don't fall into that area. There is no implicit conversions between TBcd and Variant, you have to use explicit function calls (ie VarFMTBcdCreate() and VarToBcd()), which TValue can't call internally for you. That is why you get the 'Invalid class typecast' error. .
  6. Try something more like the following (I will leave sending PNGs from memory as an exercise for you): uses ..., IdGlobal, IdGlobalProtocols; function Tdm_Image.DoFixMailTextBody(MAIL_TextBody : String; Attach_PNGs : TStrings) : String; var StartFrom, EndTo : Integer; TmpDir : String; function FindNextDataUrl(var DataUrl: String): Boolean; begin Result := False; // you can use StrUtils.PosEx() if System.Pos() doesn't have an Offset parameter... StartFrom := Pos('src="data:', MAIL_TextBody, StartFrom); if StartFrom = 0 then Exit; Inc(StartFrom, 5); EndFrom := Pos('"', MAIL_TextBody, StartFrom); if EndFrom = 0 then Exit; DataUrl := Copy(MAIL_TextBody, StartFrom, EndFrom - StartFrom); Result := True; end; function ExtractImageBase64FromDataUrl(DataUrl : String; var ImgType, Base64Str : String): Boolean; begin Result := False; Fetch(DataUrl, ':'); ImgType := Fetch(DataUrl, ','); Result := TextEndsWith(ImgType, ';base64'); if not Result then Exit; SetLength(ImgType, Length(ImgType)-7); Result := IsHeaderMediaType(ImgType, 'image'); if not Result then Exit; ImgType := ExtractHeaderMediaSubType(ImgType); Base64Str := DataUrl; Result := True; end; function Base64ToPngFile(const ImgType, Base64Str: String; var UniqueNo: Integer): Boolean; var DibGraphic : TDibGraphic; PngGraphic : TPngGraphic; FileExtn : String; PngFileName : String; begin Result := False; case PosInStrArray(ImgType, ['png', 'apng', 'jpeg', 'jpg', 'svg+xml', 'bmp', 'gif', 'tiff', 'wmf', 'emf'], False) of 0, 1: FileExtn := 'png'; 2, 3: FileExtn := 'jpg'; 4: FileExtn := 'svg'; 5: FileExtn := 'bmp'; 6: FileExtn := 'gif'; 7: FileExtn := 'tif'; 8: FileExtn := 'wmf'; 9: FileExtn := 'emf'; else Exit; end; // take the Base64 String And Convert to png file PngGraphic := TPngGraphic.Create; try DibGraphic := TDibGraphic.Create; try Result := Base64ToImg(Base64Str, DibGraphic, FileExtn); if not Result then Exit; PngGraphic.Assign(DibGraphic); finally DibGraphic.Free; end; if PngGraphic.Empty then Exit; UniqueNo := GetUniqueNumber; {This will be the cid Number} PngFileName := TmpDir + IntToStr(UniqueNo) + '.png'; PngGraphic.SaveToFile(PngFileName); finally PngGraphic.Free; end; Attach_PNGs.Add(PngFileName); Result := True; end; procedure ConvertDataUrlToCidUrl(UniqueNo : Integer); var NewImgSrc : String; begin Delete(MAIL_TextBody, StartFrom, EndTo - StartFrom); NewImgSrc := 'cid:' + IntToStr(UniqueNo) + '.png'; Insert(NewImgSrc, MAIL_TextBody, StartFrom); EndFrom := StartFrom + Length(NewImgSrc); end; procedure ReplaceDataUrlsWithCidUrls; var DataUrl, ImgType, Base64Str : String; UniqueNo: Integer; begin StartFrom := 1; while FindNextDataUrl(DataUrl) do begin if ExtractImageBase64FromDataUrl(DataUrl, ImgType, Base64Str) then if Base64ToPngFile(ImgType, Base64Str, UniqueNo) then ConvertDataUrlToCidUrl(UniqueNo); StartFrom := EndFrom + 1; end; end; begin Attach_PNGs.Clear; TmpDir := IncludeTrailingPathDelimiter(RemoveBackSlashChar(RemoveSlashChar(UniServerModule.LocalCachePath))); ReplaceDataUrlsWithCidUrls; Result := MAIL_TextBody; end; function SendMailBySMTP(SMTP_Host : String; SMTP_User : String; SMTP_Password : String; SMTP_Port : Integer; Send_FromAddress : String; Send_FromName : String; Send_ToAddress : String; Send_ToName : String; Send_Subject : String; Send_TextBody : String; Attach_List : TStrings; UseCid : Boolean) : Boolean; var SMTP : TIdSMTP; AMsg : TIdMessage; CurAttch : Integer; Attach_PNGs : TStringList; begin Result := False; Try Attach_PNGs := nil; try if UseCid then begin Attach_PNGs := TStringList.Create; Send_TextBody := DoFixMailTextBody(Send_TextBody, Attach_PNGs); end; SMTP := TIdSMTP.Create(nil); try SMTP.Host := SMTP_Host; SMTP.Username := SMTP_User; SMTP.Password := SMTP_Password; SMTP.Port := SMTP_Port; AMsg := TIdMessage.Create(SMTP); AMsg.Subject := Send_Subject; AMsg.From.Name := Send_FromName; AMsg.From.Address := Send_FromAddress; with AMsg.Recipients.Add do begin Name := Send_ToName; Address := Send_ToAddress; end; with TIdMessageBuilderHtml.Create do try HtmlCharSet := 'utf-8'; Html.Text := Send_TextBody; if (Attach_List <> nil) and (Attach_List.Count > 0) then begin for CurAttch := 0 To Attach_List.Count - 1 do Attachments.Add(Attach_List.Strings[CurAttch]); end; if (Attach_PNGs <> nil) and (Attach_PNGs.Count > 0) then begin for CurAttch := 0 To Attach_PNGs.Count - 1 do HtmlFiles.Add(Attach_PNGs.Strings[CurAttch]); end; FillMessage(AMsg); finally Free; end; SMTP.Connect; try SMTP.Send(AMsg); Result := True; finally SMTP.Disconnect; end; finally SMTP.Free; end; finally Attach_PNGs.Free; end; except on E: Exception do begin AddMsgToEventLog('', J_FirstFileName(ParamStr(0)), 'Fail send SMPT Mail, Error :' + e.Message, EVENTLOG_ERROR_TYPE, 4, 1); Result := False; end; end; end;
  7. Wow, that is some really overcomplicated and inefficient code for such a simple task. That code can be greatly simplified. I'm not at a computer right now, otherwise I would whip up something smaller for you. Maybe I can later tonight or tomorrow... Also, why are most of your "data:" media types prefixed with "image/image/" instead of just "image/"? That is just plain wrong, media types do not have such long hierarchies. Also, TIdMessageBuilderHtml (and TIdMessage) supports attaching TStream objects, so you can avoid creating temp files on disk if you save your PNGs to memory instead.
  8. Remy Lebeau

    How do I return to the 'Constructor'??

    Nothing wrong with having a dialog for that. But displaying it directly in the constructor of a Form while it's being created is not a good choice, especially if you want to cancel that creation. Better to have the caller display the dialog first, and if it is not canceled then create the Form, passing it the dialog's selected option. If you really want to be able to reset the Form with a new table without closing and recreating the Form,, then you should move that logic into a separate method of the Form, and then call it when needed, ie when the Form is shown, and in a button/menu handler on the Form. I wouldn't call it directly in the constructor, though, but the constructor can trigger it asynchrously, such as with PostMessage() or TThread.ForceQueue(), so the Form can be constructed normally. Or use the Form's OnShow event.
  9. Outlook and Gmail use the same core SMTP and email standards. However, embedding images using "data:" urls may be common practice for web browsers, but it is not common practice for email. According to: https://stackoverflow.com/questions/6070196/what-is-data-uri-support-like-in-major-email-client-software https://stackoverflow.com/questions/8580355/can-you-send-images-in-datauri-format-to-gmail Gmail dropped support for "data:" urls over a decade ago. I strongly suggest you stick to using "cid:" urls with file attachments in emails. All major html-aware email readers know how to handle "cid:" attachments. And TIdMessageBuilderHtml is designed with "cid:" in mind. That is the same component you used to send an email to Outlook. Because you don't embed it directly. You would need to instead attach the png file to the email, assign the attachment an ID in its "Content-ID" header, and then refer to that ID in a "cid:" url in the HTML <img> src. Have a look at the following articles on Indy's blog: https://www.indyproject.org/2005/08/17/html-messages/ https://www.indyproject.org/2008/01/16/new-html-message-builder-class/ Your PNGs would be "related attachments" for the HTML, and can be attached using the TIdMessageBuilerHtml.HtmlFiles property, and identified with the TIdMessageBuilderAttachment.ContentID proeprty.
  10. Remy Lebeau

    How do I return to the 'Constructor'??

    Then I'm even more confused about the code you showed earlier. Please explain exactly what this one Form is actually doing, what it looks like, and what you are trying to accomplish with it. Also, what the JSDialog is and what it is doing in relation to this Form. There is just too many unknowns to adequately help you.
  11. Remy Lebeau

    How do I return to the 'Constructor'??

    To answer your question - you can't "return to the constructor" once it has exited. You would have to destroy the Form and recreate it. Maybe put the 'case' block inside a loop, and then exit the loop when you are done looking at tables? Hard to say since you did not explain what TChangesLogForm is actually doing and what exactly its relationship to JSDialog1 is. That being said, why is this dialog code in the Form's constructor? This does not look like good UI class design. This kind of logic should probably not be in the Form's constructor, but rather should be invoked in the calling code before the Form is created to begin with. Again, hard to say for sure without knowing more about your design.
  12. That is an error message for a compiler bug. All you can do with it is report it to Embarcadero at https://qp.embarcadero.com. Start taking code out of your project until the error goes away, and then slowly reintroduce the removed code until the error returns. Try to isolate the most minimal code change that causes the bug, then include that detail in your bug report.
  13. The style attribute on the <img> element is formatted correctly: <img src="data:image/png;base64,..." style="height:150px; width:125px" /> Multiple CSS styles in a single "style" attribute ARE supposed to be separated by semicolons.
  14. Then the problem is not with Indy itself. It is entirely a problem with the email reader. It likely just doesn't support CSS, or doesn't handle it correctly. My point is, a .msg file is a binary file that is useless to people who don't use Outlook and can't open it (like me). Whereas an .eml file is plain text instead. You are using a Unicode version of Delphi, and Indy handles Unicode strings for you. You don't need to encode the strings manually, nor is it beneficial to do so because you are assigning UTF8String's where UnicodeString's are expected, thus the Delphi compiler+RTL is simply going to implicitly decode your UTF-8 back to UTF-16 before Indy even sees the data. So just use plain Delphi strings, don't encode them manually. Indy defaults to UTF-8 in Unicode versions of Delphi when encoding the email data. Look for yourself. You have a <meta charset> that states "utf-8", followed by a <meta http-equiv> that states "windows-1255". Doubtful.
  15. You can't modify or replace the IDE's built-in editors. If you don't like the way the editors look/behave, file a report at https://qp.embarcadero.com
  16. What versions of Delphi and Indy are you using? Did you compare the HTML that Outlook has to the HTML that you sent? .msg files are binary files that Outlook creates for itself after converting the original data. It is not "received" from the sender. So, it is hard to diagnose them. Can you export an .eml file instead? Or at least view the source data for the .msg file? How does its HTML compare to the original HTML? Not related to your problem, but why are you UTF-8 encoding the strings you give to Indy? Don't do that. Use plain strings instead, and let Indy encode them for you. Also, you should not be messing with the TIdMessage properties after you have called TIdMessageBuilderHtml.FillMessage(). It has already set the properties accordingly for you. Your HTML has conflicting charsets in its <meta> elements. Also, it is unusual to use a "data:" url to display an image in an HTML email. Why are you not attaching the image file to the email and then using a "cid:" url to display it? Granted, TIdMessageBuilderHtml doesn't care what HTML you give it, but it is designed for "cid:" urls in mind.
  17. Remy Lebeau

    Buggy auto-completion

    Agreed. I'm constantly having to fight with the auto-complete, taking time to edit/undo what it generates. Embarcadero should really focus more effort on this area.
  18. Remy Lebeau

    Searching Edit component with autocomplete for Directory/Files

    This is the complete code. No crashes or leaks: program WideStringTest; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Winapi.ActiveX; type TEnumStringTestBase = class(TInterfacedObject, IEnumString) function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; virtual; stdcall; abstract; function Skip(celt: Longint): HResult; stdcall; function Reset: HResult; stdcall; function Clone(out enm: IEnumString): HResult; stdcall; end; TEnumStringTest1 = class(TEnumStringTestBase) function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; override; end; TEnumStringTest2 = class(TEnumStringTestBase) function Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; override; end; function TEnumStringTestBase.Skip(celt: Longint): HResult; stdcall; begin Result := S_FALSE; end; function TEnumStringTestBase.Reset: HResult; stdcall; begin Result := S_OK; end; function TEnumStringTestBase.Clone(out enm: IEnumString): HResult; stdcall; begin Result := E_NOTIMPL; end; function TEnumStringTest1.Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; type TPointerList = array[0..0] of Pointer; var i: Integer; begin i := 0; while (i < celt) do begin {$RANGECHECKS OFF} TPointerList(elt)[I] := nil; WideString(TPointerList(elt)[I]) := 'test'+IntToStr(I); Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; function TEnumStringTest2.Next(celt: Longint; out elt; pceltFetched: PLongint): HResult; type TWideStringList = array[0..0] of WideString; var i: Integer; begin i := 0; while (i < celt) do begin {$RANGECHECKS OFF} Pointer(TWideStringList(elt)[I]) := nil; TWideStringList(elt)[I] := 'test'+IntToStr(I); Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; var Enum: IEnumString; arr1: array[0..4] of TBstr; arr2: array[0..4] of WideString; I: Integer; begin Enum := TEnumStringTest1.Create; Enum.Next(Length(arr1), arr1, nil); for I := Low(arr1) to High(arr1) do WriteLn(arr1[i]); for I := Low(arr1) to High(arr1) do SysFreeString(arr1[i]); WriteLn; Enum := TEnumStringTest2.Create; Enum.Next(Length(arr2), arr2, nil); for I := Low(arr2) to High(arr2) do WriteLn(arr2[i]); ReadLn; end.
  19. Remy Lebeau

    THttpClient vs TIdHttp in Linux

    Capturing the traffic is easy with TIdHTTP. You can simply assign any of Indy's TIdLog... components to the TIdHTTP.Intercept property, such as TIdLogFile. I don't know if THTTPClient has any similar feature to capture its traffic without using an external sniffer. If Wireshark is too much for you, maybe Fiddler or similar logging proxy with work for you?
  20. Remy Lebeau

    Searching Edit component with autocomplete for Directory/Files

    I tested the code I showed with Delphi 12.1 before I posted it, and it worked fine. And it should also work fine in earlier versions, as handling of WideString has not changed over the years. Which makes me wonder if there is another problem with your larger project and .Next is just a victim of circumstance? This feels like a classic symptom of "undefined behavior" being invoked in earlier code.
  21. Quite possibly. Keep in mind that TCP is resilient to temporary network outages, so as long as the WiFi maintains the same IP when it comes back on, previous TCP connections may be recoverable by the OS if it hasn't invalidated them yet. On the other hand, if your code is not reading/writing data with the connection during the time that the WiFi is turned off, then it makes sense that you won't see a disconnect. It can take time for a lost connection to be detected. If the OS's internal timeout is not to your liking, you might consider enabling TCP keep-alives on the connection.
  22. Remy Lebeau

    Searching Edit component with autocomplete for Directory/Files

    WideString is not reference counted, and setting a WideString to nil via a Pointer typecast works just fine to release ownership of the BSTR without freeing its memory. Always has. And that has not changed. Looking at your changes, I think you are simply misusing WideString to begin with. TEnumStringHelper.Next() should look more like this instead: function TEnumStringHelper.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; type TPointerList = array[0..0] of Pointer; var i: Integer; s: string; begin i := 0; while (i < celt) and TryGetNext(s) do begin // this directive is needed because the array is declared as having only // 1 element but celt may be > 1! The alternative is to either declare // TPointerList with a rediculous high number of elements (like the RTL // does in some places), eg: // // type // TPointerList = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer; // // or else just use pointer arithmetic on elt directly... {$RANGECHECKS OFF} // Ensure the WideString is nil before assiging a String to it... TPointerList(elt)[i] := nil; WideString(TPointerList(elt)[i]) := s; Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; Alternatively: function TEnumStringHelper.Next(celt: Integer; out elt; pceltFetched: PLongint): HResult; type TWideStringList = array[0..0] of WideString; var i: Integer; s: string; begin i := 0; while (i < celt) and TryGetNext(s) do begin // see notes above... {$RANGECHECKS OFF} Pointer(TWideStringList(elt)[i]) := nil; TWideStringList(elt)[i] := s; Inc(i); end; if pceltFetched <> nil then pceltFetched^ := i; if i = celt then Result := S_OK else Result := S_FALSE; end; Also, your TEnumStringHelper.Skip() is just plain wrong. If celt is <= 1 then you get stuck in an endless loop retrieving strings until the enum is exhausted, and if celt is > 1 then you exit prematurely after 1 string is retrieved. It should look more like this instead: function TEnumStringHelper.Skip(celt: Integer): HResult; var i: Integer; s: string; begin i := 0; while (i < celt) and TryGetNext(s) do Inc(i); if i = celt then Result := S_OK else Result := S_FALSE; end;
  23. No. The connection has to be disconnected and reconnected, it can't be moved from one network to another. Android is doing the right thing. https://android.stackexchange.com/questions/83830/how-can-android-keep-a-tcp-connection-alive-when-changing-network-provider I would question why Windows is not doing the same thing, as it should be. Maybe your code is just not doing anything that triggers a disconnect during a network change? On both platforms, you might consider having your app register with the OS to receive network change notifications.
  24. Remy Lebeau

    Dropping component on form

    Yes, TSelectionEditor is the way to go. When a unit is saved or compiled, the IDE first invokes any registered TSelectionEditor classes relevant to that unit, calling their RequiredUnits() methods to update the 'uses' clause. Indy registers several TSelectionEditor classes for a few specific cases. But, I want to point out 1 editor in particular: TIdBaseComponentSelectionEditor. It is registered for TIdBaseComponent, which all Indy components are derived from. TIdBaseComponentSelectionEditor iterates all Indy components on a Form, using RTTI to look for any event handlers that are assigned to those components, and then to determine the types of any parameters and return values of those event handlers, and the unit names that those types belong to. It then reports those units to the IDE to add to the 'uses' clause. Now, one would think this is something that the IDE itself should handle automatically for all components, but it doesn't, or at least not completely. Which is why TIdBaseComponentSelectionEditor was created, instead of having to define a separate TSelectionEditor for each individual component that needs it. I mention this because Indy has a LOT of components, so it makes sense to handle something like this in a central place. So, if you don't want to write a bunch of smaller editors for multiple components in your library, you might consider a similar approach (although handling RTTI differences across multiple IDE versions can be a bit of a task).
  25. Remy Lebeau

    WideString.c_bstr() operation in 11.2

    The ticket is still open. There is no solution presented in this thread. It is a regression bug somewhere in the RTL.
×