Jump to content

Remy Lebeau

Members
  • Content Count

    2998
  • Joined

  • Last visited

  • Days Won

    135

Everything posted by Remy Lebeau

  1. That means you are creating the TTimer in the main thread to begin with, not in the background thread. That's exactly how TTimer already works. Its constructor creates a hidden window in the calling thread, which when activated will then receive timer messages from that same thread. So the thread that creates the TTimer must have a message loop to receive and dispatch those timer messages. So, you might think about moving the creation of the TTimer into the background thread's Execute() method, and that may work 99% of the time, but know that the creation of that hidden window is not done in a thread-safe manner, so you really should not be using TTimer in a background thread at all. If you really need a thread-based timer, you could just use the Win32 timeSetEvent() function, which is a multimedia timer that runs its callback in a background thread that the OS manages, If you really want to use a custom TThread class, then using TEvent is the simplest option that allows you to terminate the thread on demand, eg: uses ..., Classes, SyncObjs; type TTimerThread = class (TThread) private fEvent: TEvent; fInterval: LongWord; fOnElapsed: TNotifyEvent; protected procedure Execute; override; procedure TerminatedSet; override; public constructor Create(AInterval: LongWord; AOnElapsed: TNotifyEvent); reintroduce; destructor Destroy; end; constructor TTimerThread.Create(AInterval: LongWord; AOnTimer: TNotifyEvent); begin inherited Create(False); fInterval := AInterval; fOnElapsed := AOnElapsed; fEvent := TEvent.Create; end; destructor TTimerThread.Destroy; begin fEvent.Free; inherited Destroy; end; procedure TTimerThread.TerminatedSet; begin fEvent.SetEvent; end; procedure TTimerThread.Execute; begin while fEvent.WaitFor(fInterval) = wrTimeout do fOnElapsed(Self); end;
  2. Remy Lebeau

    Thread Destroy with no corresponding Thread Create?

    Why is the code creating the TStringStream object in the constructor and destroying it in the destructor, instead of just using it locally inside of Execute()? Do other threads need access to the TStringStream? If not, then it doesn't need to be a class member.
  3. Remy Lebeau

    docwiki

    Their system went down late Friday night, multiple servers were affected, including DocWiki, Blogs, QualityPortal, etc. Last I heard, they are still working on resolving the problem.
  4. How exactly are you attempting to open the file? Are you getting an error? If so, what is it? I'm no Android expert, but outside of a public folder, I think Android does not allow two apps to share files with each other unless both apps cooperate with each other, ie the providing app has to grant access to the receiving app. And since the providing app in this case is no longer available, you might be SOL. 🤷
  5. Remy Lebeau

    What is otares?

    Why? What are you planning on doing with otares files if you could get them generated? Delphi doesn't use otares files anymore, it's a legacy thing only.
  6. Remy Lebeau

    Help with 'Continue' pls.

    No. I just knew the feature supposedly exists. Ouch! And not even opened for review yet....
  7. Remy Lebeau

    Help with 'Continue' pls.

    Nope. Eof simply evaluates the current record, it does not move to another record. Hence the need to call Next() explicitly. Yes.
  8. Remy Lebeau

    Extend Standard VCL

    In a word, no. However, if you intend to use the extended component in just one form/project, and don't mind setting the new properties in code at runtime rather than with the Object Inspector at design-time, then you could use an interposer class to add the properties, and that way you don't have to make the extra effort of putting the new component in a new package and installing it into the IDE. For example: unit MyUnit; interface uses ..., Vcl.Forms, Vcl.StdCtrls, System.IniFiles, ...; type TCheckBox = class(Vcl.StdCtrls.TCheckBox) public IniSection: string; IniProperty: string; procedure LoadFromIni(Ini: TCustomIniFile); procedure SaveToIni(Ini: TCustomIniFile); end; TMyForm = class(TForm) SomeCheckBox: TCheckBox; procedure FormCreate(Sender: TObject); ... private procedure LoadConfig; procedure SaveConfig; end; ... implementation ... procedure TCheckBox.LoadFromIni(Ini: TCustomIniFile); begin Checked := Ini.ReadBool(IniSection, IniProperty, False); end; procedure TCheckBox.SaveToIni(Ini: TCustomIniFile); begin Ini.WriteBool(IniSection, IniProperty, Checked); end; procedure TMyForm.FormCreate(Sender: TObject); begin SomeCheckBox.IniSection := ...; SomeCheckBox.IniProperty := ...; end; procedure TMyForm.LoadConfig; var Ini: TIniFile; begin ... SomeCheckBox.LoadFromIni(Ini); ... end; procedure TMyForm.SaveConfig; var Ini: TIniFile; begin ... SomeCheckBox.SaveToIni(Ini); ... end;
  9. Remy Lebeau

    OpenSSL Commands

    I updated my earlier example. Make sure CmdLine is not pointing at a string literal when passing it to CreateProcess(), as the 2nd parameter of the Unicode version of CreateProcess() (CreateProcessW) is not allowed to point at read-only memory.
  10. Remy Lebeau

    OpenSSL Commands

    Use the Win32 CreateProcess() function. Of course, a better option might be to NOT shell out to openssl.exe at all, but to use the OpenSSL API directly in your own code instead. It really depends on what you are trying to accomplish exactly. Only if you execute them as parameters to cmd.exe. Then you are doing something wrong, but we can't see what you are actually doing. Please show your actual code you are having trouble with. For example: uses ..., Windows; procedure RunCommandLine(CmdLine: String); var si: TStartupInfo; pi: TProcessInformation; begin {$IFDEF UNICODE}UniqueString(CmdLine);{$ENDIF} ZeroMemory(@si, SizeOf(si)); si.cb := SizeOf(si); if not CreateProcess(nil, PChar(CmdLine), nil, nil, False, 0, nil, nil, si, pi) then RaiseLastOSError; ... CloseHandle(pi.hThread); CloseHandle(pi.hProcess); end; ... RunCommandLine('C:\path\openssl.exe version'); Just note that openssl.exe is a console app, so if you run this code in a non-console program, it will create a new console that will close when openssl.exe exits. If you actually want to see the console window to read the output, or even to capture the output into your own code, requires more work than this code shows.
  11. Remy Lebeau

    Overview of the generated files after build/compile

    Feel free to file a feature request with Embarcadero
  12. Remy Lebeau

    Overview of the generated files after build/compile

    There is no such list generated by the IDE/compiler. And the only documentation that I know of which talks about generated files is: File Types Index File Extensions of Files Generated by RAD Studio
  13. Remy Lebeau

    How can I make TTimer run in the main tread ?

    Then TTimer is not a good choice, as it is not a real-time timer, it is a message-based timer, and so is subject to the speed of the message queue. And also, the WM_TIMER message is low-priority and only generated when there are no other messages pending. You should use an actual multimedia timer instead. And don't use PlaySound() when high performance is needed. Pre-prepare the audio sample ahead of time and use a multimedia API to play the sample when needed. Such as waveOutWrite(), or more modern audio APIs like WASAPI, DirectSound/XAudio2, etc.
  14. Remy Lebeau

    D11.3 Surfaces a Bug in 8-Year-Old Code that reads DBF file...

    @Steve Maughan you might want to read: Converting 32-bit Delphi Applications to 64-bit Windows since the original code was doing things that were not meant to be done in 64bit builds to begin with.
  15. Remy Lebeau

    Delphi 12 IDE: unchecking a component is NOT SAVED

    You can update the "HKCU\SOFTWARE\Embarcadero\BDS\23.0\Known Packages" Registry key. Find the entry for the package, and either: modify the entry's Data value to prefix it with an underscore ("_"). remove the entry (optionally move it to the "HKCU\SOFTWARE\Embarcadero\BDS\23.0\Disabled Packages" key)
  16. Remy Lebeau

    GetWindowHandle + Ctrl V

    If your app actually needs admin rights, then you should add an application manifest to your project to specify its requestedExecutionLevel at compile-time, not change the properties of the EXE file after the compile is finished. Modern Delphi versions even have a project setting for that very purpose: http://docwiki.embarcadero.com/RADStudio/en/Manifest_File http://docwiki.embarcadero.com/RADStudio/en/Application_Manifest Basically, what you did for your app, but for the IDE instead, so it runs as an admin, and then any project it runs will also run as an admin.
  17. You should NEVER stream pointers from one process' address space into another process' address space, unless both processes are running concurrently and one process needs to directly access the other process's memory via the ReadProcessMemory() and/or WriteProcessMemory() APIs (using shared memory would be better, though). Otherwise, just don't do it! Stream offsets instead. And in the example given, a linked list can certainly be streamed using offsets instead of pointers. The actual pointers would only be meaningful in the memory of the process that originally creates the list, and in the process that loads the stream into a new list in it own memory. Pointers are fairly meaningless when passed around from one process to another.
  18. 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.
  19. 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?
  20. 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.
  21. 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.
  22. 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
  23. 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
  24. 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.
  25. 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;
×