Jump to content

Remy Lebeau

Members
  • Content Count

    2322
  • Joined

  • Last visited

  • Days Won

    94

Everything posted by Remy Lebeau

  1. Remy Lebeau

    GetWindowHandle + Ctrl V

    Do not use keybd_event(), use SendInput() instead. Only if you have permission to access the other app. Into a simple app like Notepad, yes. But given that Kaspersky is a security app, I doubt they want people to automate it like this, so the approach you are attempting will likely never work with it.
  2. Remy Lebeau

    indy https error 10061

    Are the client and server on the same LAN? If not, then there could be other AVs/Firewalls present in between the client and server as the connection jumps from one network to another. The only thing that makes sense given what you have described is if there is a local AV/Firewall running on the client machine that is blocking the connections from TIdHTTP directly, but has an exception which allows Fiddler to make connections. You say your TIdHTTP code is in a DLL, what app is loading that DLL?
  3. Remy Lebeau

    indy https error 10061

    Rather than using the ProxyParams (which affects TIdHTTP's internal logic in how it handles HTTP/S requests), I would suggest using a packet sniffer like Wireshark to see the actual IP/Port that TIdHTTP is trying to connect to for a normal non-proxy request, and see whether that connection is actually going out over the network. The main causes of error 10061 are: the target IP is reachable by the client, but there is no TCP server running on the target Port there is a TCP server running on the target IP/Port, but it has too many pending connections in its backlog the connection is being blocking by an AV/firewall that is located anywhere on the network(s) between your client and target server. Unfortunately, there is no way to know on the client side what the root cause of the error actually is, so the only thing the client can do is catch the error and retry the request, preferably after a short delay, in case the error is temporary.
  4. Remy Lebeau

    How do I execute FFMPEG from inside a VCL App?

    I would not suggest putting it all on the OS to run that entire command in one go. You should break it up into steps and then use appropriate APIs to run each step directly in your code, eg: uses ..., System.SysUtils; procedure TForm1.act_MUSIC_CONVERSION_FLACStoMP3Execute(Sender: TObject); var dir: string; sr: TSearchRec; begin dir := IncludeTrailingPathDelimiter(FileListBox1.Directory); if FindFirst(dir + '*.flac', faAnyFile, sr) <> 0 then try repeat if (sr.Attr and faDirectory) = 0 then begin ShellExecute(0, nil, 'ffmpeg.exe', PChar('-i "' + sr.Name + '" -ab 320k -map_metadata 0 -id3v2_version 3 "' + sr.Name + '.mp3"'), PChar(dir), SW_HIDE); end; until FindNext(sr) <> 0; finally FindClose(sr); end; end; Alternatively: uses ..., System.SysUtils, System.IOUtils; procedure TForm1.act_MUSIC_CONVERSION_FLACStoMP3Execute(Sender: TObject); var dir, fileName: string; begin dir := IncludeTrailingPathDelimiter(FileListBox1.Directory); for fileName in TDirectory.GetFiles(dir, '*.flac') do ShellExecute(0, nil, 'ffmpeg.exe', PChar('-i "' + fileName + '" -ab 320k -map_metadata 0 -id3v2_version 3 "' + fileName + '.mp3"'), PChar(dir), SW_HIDE); end; Yes, its' more work, but it gives you much more control.
  5. There is nowhere you can "put the app" that will make the OS run your app during sleeps/wakeups. Your app needs to already be running in order to catch those events in real-time in your app's code. As for Startup specifically, you could use the "Startup" group in the Start Menu, or the "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" key in the Registry, to run your app during startup/logon. There are also startup/shutdown and logon/logoff policy scripts that you can edit to run your app: Working with startup, shutdown, logon, and logoff scripts using the Local Group Policy Editor
  6. Also, because SetDialogParams() is taking a TTaskDialogParams by value, a copy is made, which will have its own TStringList object, so you must implement the Assign() operator as well to copy the source record's TStringList data to the copied record after it is Initialized: class operator TTaskDialogParams.Assign(var Dest: TTaskDialogParams; const [ref] Src: TTaskDialogParams); begin Dest.CustomButtons.Assign(Src.CustomButtons); end; class operator TTaskDialogParams.Finalize(var Dest: TTaskDialogParams); begin Dest.CustomButtons.Free; end; class operator TTaskDialogParams.Initialize(out Dest: TTaskDialogParams); begin Dest.CustomButtons := TStringList.Create; end; Otherwise, if you don't do this, the default assignment behavior will copy the object pointer instead, causing the copied record to lose its own TStringList object (leaking it) and end up pointing to the original TStringList object, so when the copied record is Finalized then the original TStringList object gets freed, corrupting the original record that was copied from. That being said, there is no good reason to pass the record by value in the code shown. SetDialogParams() should take the record by const reference instead, then no copy is made. Read the documentation, especially the section on "Passing Managed Records as Parameters" : https://docwiki.embarcadero.com/RADStudio/en/Custom_Managed_Records
  7. You did not setup your C++Builder code correctly to match the functionality of the Delphi code. Here is what your C++ code would need to look like instead to directly translate the Delphi code: //--------------------------------------------------------------------------- #ifndef examplecomponentcppH #define examplecomponentcppH //--------------------------------------------------------------------------- #include <System.Classes.hpp> #include <Vcl.Controls.hpp> #include <Winapi.Messages.hpp> class PACKAGE TExampleCpp : public TCustomControl { protected: MESSAGE void __fastcall WMMouseMove(TWMMouseMove& Message); public: __fastcall TExampleCpp(TComponent *Owner); __fastcall virtual ~TExampleCpp(); BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER(WM_MOUSEMOVE, TWMMouseMove, WMMouseMove) END_MESSAGE_MAP(TCustomControl) }; #endif //--------------------------------------------------------------------------- #pragma hdrstop #include "examplecomponentcpp.h" //--------------------------------------------------------------------------- #pragma package(smart_init) __fastcall TExampleCpp::TExampleCpp(TComponent *Owner) : TCustomControl(Owner) { } __fastcall TExampleCpp::~TExampleCpp() { } void __fastcall TExampleCpp::WMMouseMove(TWMMouseMove& Message) { //... TCustomControl::Dispatch(&Message); } namespace Examplecomponentcpp { void __fastcall PACKAGE Register() { TMetaClass* classes[1] = {__classid(TExampleCpp)}; RegisterComponents("Samples", classes, 0); } } See Declaring a New Message-handling Method in Embarcadero's documentation. Note the inclusion of the MESSAGE_MAP, that is what actually hooks up the WM_MOUSEMOVE message to the WMMouseMove() method. As I mentioned earlier, an alternative would be to override the virtual TWinControl::WndProc() method instead of using a MESSAGE_MAP, eg: //--------------------------------------------------------------------------- #ifndef examplecomponentcppH #define examplecomponentcppH //--------------------------------------------------------------------------- #include <System.Classes.hpp> #include <Vcl.Controls.hpp> #include <Winapi.Messages.hpp> class PACKAGE TExampleCpp : public TCustomControl { protected: virtual void __fastcall WndProc(TMessage& Message); public: __fastcall TExampleCpp(TComponent *Owner); __fastcall virtual ~TExampleCpp(); }; #endif //--------------------------------------------------------------------------- #pragma hdrstop #include "examplecomponentcpp.h" //--------------------------------------------------------------------------- #pragma package(smart_init) __fastcall TExampleCpp::TExampleCpp(TComponent *Owner) : TCustomControl(Owner) { } __fastcall TExampleCpp::~TExampleCpp() { } void __fastcall TExampleCpp::WndProc(TMessage& Message) { if (Message.Msg == WM_MOUSEMOVE) { //... } TCustomControl::WndProc(Message); } namespace Examplecomponentcpp { void __fastcall PACKAGE Register() { TMetaClass* classes[1] = {__classid(TExampleCpp)}; RegisterComponents("Samples", classes, 0); } } But, that being said, in this particular situation you really should not be handling the WM_MOUSEMOVE message directly at all. Override the virtual TControl::MouseMove() method instead, eg: //--------------------------------------------------------------------------- #ifndef examplecomponentcppH #define examplecomponentcppH //--------------------------------------------------------------------------- #include <System.Classes.hpp> #include <Vcl.Controls.hpp> class PACKAGE TExampleCpp : public TCustomControl { protected: DYNAMIC void __fastcall MouseMove(TShiftState Shift, int X, int Y); public: __fastcall TExampleCpp(TComponent *Owner); __fastcall virtual ~TExampleCpp(); }; #endif //--------------------------------------------------------------------------- #pragma hdrstop #include "examplecomponentcpp.h" //--------------------------------------------------------------------------- #pragma package(smart_init) __fastcall TExampleCpp::TExampleCpp(TComponent *Owner) : TCustomControl(Owner) { } __fastcall TExampleCpp::~TExampleCpp() { } void __fastcall TExampleCpp::MouseMove(TShiftState Shift, int X, int Y) { //... TCustomControl::MouseMove(Shift, X, Y); } namespace Examplecomponentcpp { void __fastcall PACKAGE Register() { TMetaClass* classes[1] = {__classid(TExampleCpp)}; RegisterComponents("Samples", classes, 0); } } As I stated earlier, there is no equivalent to "inherited" in C++Builder. And there is no __super in C++Builder, either. Read that post again, it does not mention __super in relation to Borland/Embarcadero compilers at all, only in relation to Microsoft compilers. your only option in C++ is to specify the base class method directly. "inherited" in a Delphi message handler has no concept of public/private at all. Note that both the base class WMMouseMove() method in TControl and the derived class WMMouseMove() method in TExample are marked with the "message" directive (see Message Methods in Embarcadero's documentation). That means they are both stored in a message dispatch table that the compiler creates for every class that has "message" handlers. Calling "inherited" in a message handler will simply call the corresponding base class method in the dispatch table. It doesn't matter whether the methods are public or private. The table is just a bunch of method pointers. As it should be, because you are doing something COMPLETELY DIFFERENT than what the Delphi code is doing. There is no message dispatch table in C++. You are trying to directly call a base class method, and such a method call is subject to the standard public/private restrictions outlined in the C++ language. In the Delphi code, that is not the case. Because it can't be. C++ and Delphi are completely different languages, with their own set of rules and restrictions. Not everything translates across the language boundary, and "inherited" is one of them. There is nothing like "inherited" in C++. No, of course not. Plenty of components are written in C++Builder. I've written several myself. For the most part, it can. But certain methodologies may differ due to language differences. For instance, in this case the message dispatch happens to be different between Delphi and C++. Notice the MESSAGE_MAP in my example? That overrides the virtual TObject::Dispatch() method, and is why you have to call the base class Dispatch() method if you want to pass the message to a base class handler in C++. You are comparing apples and oranges here. You can't rationalize why one language doesn't behave the same as another language.
  8. Your declaration of WTSQuerySessionInformation() is wrong. It should be like this instead: function WTSQuerySessionInformation( hServer: THandle; SessionId: DWORD; WTSInfoClass: WTS_INFO_CLASS; var ppBuffer: LPWSTR; var pBytesReturned: DWORD): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSQuerySessionInformationW'{$ELSE}'WTSQuerySessionInformationA'{$ENDIF}; And technically, both WTSEnumerateSessions() and WTSQuerySessionInformation() should be using 'out' parameters instead of 'var' parameters. But that is not important here. You are also leaking the memory that WTSQuerySessionInformation() returns. Try this instead: function GetSessionInfo(id: DWORD; var codeErr: Integer): TSessionInfo; var wpSessionInfo: PWTS_INFO; SessionInfo: LPWSTR; BytesReturned: DWORD; idErr: Integer; begin Result.ID := -1; try if WTSQuerySessionInformation ( WTS_CURRENT_SERVER_HANDLE, id, WTS_INFO_CLASS.WTSSessionInfo, SessionInfo, BytesReturned) then begin try Result.ID := id; wpSessionInfo := PWTS_INFO(SessionInfo); //user name Result.user := wpSessionInfo.UserName; //station name Result.StationName := wpSessionInfo.WinStationName; //domain name Result.domain := wpSessionInfo.Domain; //connection status Result.ConnectState := wpSessionInfo.State; finally WTSFreeMemory(SessionInfo); end; end else begin idErr := GetLastError; GV_TxtFileLog.Log('[GetSessionInfo] id ' + IntToStr(id) + ' ERR : ' + intToStr(idErr) + ' ' + SysErrorMessage(idErr), True); end; except on e: Exception do GV_TxtFileLog.Log('[GetSessionInfo] id ' + IntToStr(id) + ' EX : ' + e.Message, True); end; end; function SF_GetOpenedSessions(var codeErr: Integer): TSessionsInfo; var Sessions, Session: PWTS_SESSION_INFO; NumSessions, I: DWORD; SessionInfo: TSessionInfo; SessionCount: Integer; begin Result := nil; codeErr := 0; try if WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, Sessions, NumSessions) then begin try if NumSessions > 0 then begin SessionCount := 0; SetLength(Result, NumSessions); try Session := Sessions; for i := 0 to NumSessions-1 do begin if Session.SessionId <> 0 then begin GV_TxtFileLog.Log('[SF_GetOpenedSessions] GetSessionInfo '+ intToStr(Session.SessionId)); SessionInfo := GetSessionInfo(Session.SessionId); if SessionInfo.ID <> -1 then begin Result[SessionCount] := SessionInfo; Inc(SessionCount); end; end; Inc(Session); end; finally SetLength(Result, SessionCount); end; end; finally WTSFreeMemory(Sessions); end; end else begin codeErr := GetLastError; GV_TxtFileLog.Log('[SF_GetOpenedSessions] WTSEnumerateSessions err : ' + IntToStr(codeErr) + ' ' + SysErrorMessage(codeErr), True); end; except on e: Exception do GV_TxtFileLog.Log('[SF_GetOpenedSessions] EX : ' + e.Message, True); end; end;
  9. There is no "inherited" in C++. You need to call the base class Dispatch() method. Or override the virtual WndProc() method instead of using a MESSAGE_MAP.
  10. Remy Lebeau

    How to keep hdc alive

    This is an English forum, please. I'm not familiar with the ScriptStringAnalyze API or how it works, so I can't help you further with this. I don't understand what you are trying to accomplish. Good luck with your journey.
  11. There is a simpler way that doesn't involve keeping a second list. You can simply read in the INI values into a temporary unsorted TStringList first, and then you can loop through that list adding each entry to the live MRU normally, where the MRU insertion function looks for a duplicate in the live list and if found then removes it before then adding the new entry to the top of the list. I have updated my earlier example to demonstrate this.
  12. It is commonly recommended that you should obtain a Network Named User license instead, so that you can then manage your own licensing locally without having to contact Embarcadero. You would just have to download and install the License Server onto a machine in your local network, and then you can issue/revoke your own licenses as needed.
  13. Remy Lebeau

    Help with Query at run time

    You should not use TDirectory.GetCurrentDirectory() to get the EXE path, as the CWD is dynamic. It is subject to whatever path the launcher of the EXE wants to use, and it can also change value during the EXE's runtime. Instead, use TApplication.ExeName or ParamStr(0) to get the full path to the EXE file, and then strip off the filename with ExtractFilePath()/ExtractFileDir() or TPath.GetDirectoryName(). Though, you really should not be storing ANY mutable application data in the EXE's folder to begin with.
  14. I suppose so. For example: procedure TMyForm.FormCreate(Sender: TObject); begin LoadMRUFromINI; end; procedure TMyForm.LoadMRUFromINI; var Ini: TIniFile; tempMRU: TStringList; I: Integer; begin Ini := TIniFile.Create('C:\path\settings.ini'); try tempMRU := TStringList.Create; try Ini.ReadSectionValues('mru' tempMRU); for I := 0 to tempMRU.Count-1 do begin AddPathToMRU(tempMRU[I], False); end; finally tempMRU.Free; end; finally Ini.Free; end; end; procedure TMyForm.SaveMRUToINI; var Ini: TIniFile; I: Integer; begin Ini := TIniFile.Create('C:\path\settings.ini'); try Ini.EraseSection('mru'); for I := 0 to PathsListBox.Items.Count-1 do begin Ini.WriteString('mru', 'path'+IntToStr(I+1), PathsListBox.Items[I]); end; Ini.UpdateFile; finally Ini.Free; end; end; procedure TMyForm.AddPathToMRU(const APath: string; AUpdateINI: Boolean = True); const MaxMRUItems = 5; var Idx: Integer; begin while PathsListBox.Items.Count >= MaxMRUItems do begin PathsListBox.Items.Delete(PathsListBox.Items.Count-1); end; Idx := PathsListBox.Items.IndexOf(APath); if Idx <> -1 then begin PathsListBox.Items.Delete(Idx); end; PathsListBox.Items.Insert(0, APath); if AUpdateINI then SaveMRUToINI; end; procedure TMyForm.DoSomething; var LPath: string; ... begin LPath := ...; ... AddPathToMRU(LPath); ... end;
  15. I would do something like this: [mru] count=N path1=... path2=... ... pathN=... Keep adding new paths to the front of the list until the desired count is reached, then remove the oldest path from the end of the list each time a new path is added.
  16. Remy Lebeau

    TCP ping

    There is no such as a "TCP ping". Ping (ICMP) operates at a different protocol level than TCP. A ping can only tell you whether a machine/device is reachable on the LAN (assuming it even implements ICMP, and that ICMP is not being blocked by routers/firewalls, etc). But it cannot tell you whether there is an app actually listening on any TCP port on that machine/device. You would have to actually connect to a port to verify connectivity. That is what port scanners do.
  17. Did you verify that DateTimeToStr() is actually producing a String that uses U+202F instead of U+0020? I can't imagine the RTL ever using U+202F on purpose, so it is likely coming from the OS configuration when the RTL reads in the OS's locale info. All the more reason NOT to rely on OS locales when saving your data. Use a more reliable format, like ISO-8601.
  18. When I look at that .ini file, there is a Unicode character U+202F (Narrow No-Break Space) in between "11:15:12" and "AM". I think TryStrToDateTime() only supports the ASCII space character U+0020, so likely can't handle that other type of space character and so fails the parse.
  19. The problem with that approach is that you may not get a decoding error, so you wouldn't really know which encoding "works" right away. You might just end up with garbage when reading data. And what do you consider to be "valid"? Because different formats can produce results without errors, but just won't be what you are expecting. Can you parse the string first to make an educated guess about its likely format before you then try to decode it as a TDateTime? Yes, and more than that, its the BOM for UTF-16LE specifically. So you can use TEncoding.Unicode when loading the file. Actually, internally TMemIniFile reads the file into a TStringList first before parsing it, and TStringList is smart enough to look for a BOM when no TEncoding is specified, so in theory TMemIniFile should be able to load your file in the correct encoding provided a BOM is always present and TMemIniFile.Encoding is nil. However... The file you have provided has a UTF-8 BOM, not a UTF-16 BOM. So your example code would need to use TEncoding.UTF8 instead of TEncoding.Unicode (or, don't specify the encoding at all and let TMemIniFile figure it out for you).
  20. Remy Lebeau

    Delphi 12 IDE, auto-formatter mutilates generics

    Ah! Stupid me, that joke went right over my head, I didn't even notice the 13 🤯
  21. Remy Lebeau

    Create a new instance of a generic class

    Where is System._CreateClass defined? I cannot find it in any RTL source file, and when I do a test creating a class instance from a metaclass, there is no such function call.
  22. Remy Lebeau

    Create a new instance of a generic class

    Even if you could get this to compile, you need to make the constructor virtual in order to call a derived constructor from a base metaclass type.
  23. Remy Lebeau

    How to keep hdc alive

    But you didn't explain earlier WHY you need to preserve the HDC, only that you want to use it across "two tasks", without any example or explanation of what those tasks actually are. And, as Delija already mentioned, you can simply Lock the TBitmap's Canvas to prevent the VCL from releasing the HDC automatically. Just make sure you Unlock it before you resize or free the TBitmap, and re-Lock it again after resizing. That makes no sense. If you are using the TBitmap's HDC for both functions, then there is no need to wait for a paint event to draw the string onto the TBitmap. Since you are not drawing the string onto a display HDC directly, there is no need to wait for a paint event. You can just draw onto the TBitmap whenever you want, since it is all just in-memory data. If you want to draw the TBitmap onto a display Canvas so the user can see the image, that certainly needs to be done in a paint event, but that does not mean you have to draw onto the TBitmap itself in the paint event. And in fact, you shouldn't do it that way. Prepare the TBitmap image ahead of time, and then just draw the TBitmap as-is whenever a paint event occurs, and then invalidate the display window whenever the TBitmap image changes so a new paint event can display the updated image. And this way, you don't even need the TBitmap's HDC to be persistent over time, you only need it when you are updating your image. For example: type TTextPanel = class(...) private procedure BmpChanged(Sender: TObject); procedure UpdateData; protected procedure Paint; override; procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; constructor TTextPanel.Create(AOwner: TComponent); begin inherited Create(AOwner); FBmp := TBitmap.Create; FBmp.OnChange := BmpChanged; end; destructor TTextPanel.Destroy; begin FBmp.Free; inherited Destroy; end; procedure TTextPanel.BmpChanged(Sender: TObject); begin Invalidate; end; procedure TTextPanel.Paint; begin inherited Paint; ... Canvas.Draw(0, 0, FBmp); ... end; procedure TTextPanel.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); begin inherited SetBounds(ALeft, ATop, AWidth, AHeight); FBmp.SetSize(Width, Height); UpdateData; end; procedure TTextPanel.UpdateData; begin // update FBmp image as needed... end; I think you are making this task harder than it really needs to be. If you think I'm wrong, feel free to provide an example demonstrating exactly what you are trying to accomplish.
  24. Remy Lebeau

    Help with Query at run time

    Then there is no point in calling getdbrecord() in the OnCreate event, since it will always fail to find a matching record. As it should be, since the user hasn't been allowed to type in a name yet! Then you need to perform a SELECT query without a WHERE clause. And then after you have called FDQuery1.Open(), you can call FQuery1.First() before reading the field values. type TfrmDino = class(TForm) ... private procedure displaydbrecord; public procedure get1stdbrecord; procedure searchdbrecord(const AName: string); end; procedure TfrmDino.FormCreate(Sender: TObject); begin WindowState := wsMaximized; get1stdbrecord; end; procedure TfrmDino.btnSearchClick(Sender: TObject); begin searchdbrecord(edtSearch.Text); end; procedure TfrmDino.displaydbrecord; var sName: String; img1: string; ... begin sName := FDQuery1.FieldByName('name').AsString; edtRecno.Text := FDQuery1.FieldByName('record').AsString; edtName.Text := sName; ... img1 := sName + '.jpg'; ... end; procedure TfrmDino.get1stdbrecord; begin FDQuery1.SQL.Text := 'SELECT record,name,meaning,pronounce,period,maingroup,size,lived,diet,factfile FROM dino'; FDQuery1.Open; FDQuery1.First; displaydbrecord; end; procedure TfrmDino.searchdbrecord(const AName: string); begin FDQuery1.SQL.Text := 'SELECT record,name,meaning,pronounce,period,maingroup,size,lived,diet,factfile FROM dino Where name = :PName'; FDQuery1.ParamByName('PName').AsString := AName; FDQuery1.Open; displaydbrecord; //edtSearch.Clear; end; Did you read the documentation yet? https://docwiki.embarcadero.com/RADStudio/en/Navigating_and_Manipulating_Records Even if you hook up the TDBNavigator to your DataSource, you don't have your UI hooked up to the Navigator, so of course nothing happens when you click the buttons. You would have to either use the TDBNavigator's events to re-query your records every time a button is clicked, or you should change your UI to use TDBEdit components and let them update themselfes automatically when the DataSource is navigated.
  25. Remy Lebeau

    How to keep hdc alive

    Why are you creating an HDC to begin with? TBitmap already has its own HDC that it manages for you. Just draw on your TBitmap whenever you need to. What are you trying to accomplish exactly by managing your own HDC manually? You don't need that 2nd step. You can't. Resizing a bitmap will destroy its old data. But TBitmap will handle copying the old data into the new memory for you. You would have had to do that yourself with a manual HDC, so better to just let TBitmap do it for you.
×