Jump to content

Remy Lebeau

Members
  • Content Count

    2322
  • Joined

  • Last visited

  • Days Won

    94

Everything posted by Remy Lebeau

  1. Remy Lebeau

    Get Minimize, Maximize and Close button width of a Tform

    Your code snippet is declaring a variable named TitleInfo, but you are sending WM_GETTITLEBARINFOEX with a pointer to a variable named TitleBarInfoEx instead. Also, make sure you initialize the TTitleBarInfo/Ex.cbSize member before sending WM_GETTITLEBARINFOEX. https://learn.microsoft.com/en-us/windows/win32/menurc/wm-gettitlebarinfoex https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-gettitlebarinfo So, try this instead: var TitleInfo : TTitleBarInfoEx; ... begin ... TitleInfo.cbSize := SizeOf(TitleInfo); // <-- ADD THIS! SendMessage(Handle, WM_GETTITLEBARINFOEX, 0, LPARAM(@TitleInfo)); ... end;
  2. Remy Lebeau

    Forms showing on multiple desktops

    Are you switching between actual Win32 desktop, or virtual desktops? They are very different things. A window cannot be moved between Win32 desktops, it exists only in the desktop in which it was created. A window can be freely moved between virtual desktops, either by user action, or by code calling the IVirtualDesktopManager.MoveWindowToDesktop() method. Note, a window can appear in multiple desktops: https://learn.microsoft.com/en-us/answers/questions/611694/windows-virtual-desktops Another interesting tidbit: https://superuser.com/questions/950960/pin-applications-to-multiple-desktops-in-windows-10 Do your Forms in question have their BorderStyle set to bsToolWindow or bsSizeToolWin, by chance?
  3. Remy Lebeau

    How do I convert from string to char?

    Using UnicodeString::operator[] to index outside of the bounds of the string (< 1, or > Length()) will throw an ERangeError exception.
  4. Remy Lebeau

    How do I convert from string to char?

    Like any other string type, UnicodeString is an array of characters. You can index into individual characters, eg: // UnicodeString is 1-based, not 0-based! System::Char ch = EditCharacterEntry->Text[1]; int chValue = static_cast<int>(ch); Note that UnicodeString is a UTF-16 encoded string, where System::Char is a UTF-16 codeunit. It is wchar_t on Windows and char16_t on other platforms. If you need the 8-bit 'char' type specifically, you will have to perform a Unicode->ANSI data conversion at runtime, eg: // AnsiString is also 1-based! char ch = AnsiString(EditCharacterEntry->Text)[1]; int chValue = static_cast<int>(ch); Note that such a conversion is potentially lossy for non-ASCII characters > 127. AnsiString uses the caller's default codepage, so you will lose data if the UnicodeString holds Unicode characters that are not present in that codepage. You can use AnsiStringT<T> instead if you need to specify a specific codepage, eg: // AnsiStringT is also 1-based! // 28591 = ISO-8859-1, see https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers char ch = AnsiStringT<28591>(EditCharacterEntry->Text)[1]; int chValue = static_cast<int>(ch); Note that UTF8String is an alias for AnsiStringT<65001>.
  5. Remy Lebeau

    Quite confused about how to save a project

    The main project file should be a .cpp file without an associated .h[pp] file, and then each Unit in the project is a .cpp+.h[pp] pair (or a .cpp/.h[pp]/.dfm triplet if the Unit is a TForm/TFrame/TDataModule).
  6. You did not show your code that is using TThreadDataInterface_TS, but I would assume a bug in your code before I would assume a bug in the standard library. This kind of random behavior is a good indicator that your code is experiencing undefined behavior. Perhaps you are calling Write_TS() on an invalid TThreadDataInterface_TS object? Maybe some piece of code has previously corrupted memory and TThreadDataInterface_TS is the unwitting victim? There are many possibilities. But they all stem from one fact - you have a bug SOMEWHERE in your code, and this behavior is the RESULT of that bug.
  7. Remy Lebeau

    Quite confused about how to save a project

    The IDE does not create header files with a .cpp extension, so you must have done that manually at some point.
  8. Remy Lebeau

    Quite confused about how to save a project

    SAVE - save the current unit file using its current filename. SAVE AS - save the current unit file using a new filename. SAVE PROJECT AS - save the current project using a new filename. SAVE ALL - save the current project and all unit files using their current filenames. Correct to both. Save one file/unit, vs save all files/units. Save whenever you want. But, consider also enabling the IDE's auto-save options, too: https://docwiki.embarcadero.com/RADStudio/en/Saving_and_Desktop
  9. Remy Lebeau

    Indy TCP client

    There is usually no need to ever use ReuseSocket on a client, unless you use its BoundIP and BoundPort properties to make it connect from a specific local IP/Port pair, AND you are connecting to the same remote IP/Port pair as a previous connection from the same local IP/Port. By default, a client socket connects from a random local port. ReuseSocket is more commonly used only on servers instead. When a server gets shutdown and restarted quickly, ReuseSocket can can allow it to listen on the same local IP/Port as the previous run, in case the OS hasn't released the local IP/Port yet. You are using the InputBuffer the wrong way. After you Write() out your wBuffer, you are waiting in an endless loop until a reply arrives. Why are you using a loop at all? That is not necessary. Indy's reading behavior is designed to block the calling thread waiting for requested data to arrive. Most of the IOHandler's reading methods get their data from the InputBuffer only, not from the socket directly. CheckForDataOnSource() reads directly from the socket and saves whatever it receives into the InputBuffer. So, if you ask a reading method (ie, in this case, ReadBytes()) to read something (ie, in this case, 65 bytes), the method does not exit until all of the bytes for that something are available in the InputBuffer, or until the ReadTimeout elapses (which is infinite by default). In your case, when you do eventually get a reply, you are reading it from the InputBuffer into your rBuffer, but then you are ignoring rBuffer that you just read into and instead you are checking the InputBuffer directly to see if it has any buffered bytes that you have NOT read yet, and only if it DOES then you are flagging yourself to make the next Write() call. But if the InputBuffer is EMPTY (because you have already read everything that was in it) then you are NEVER calling Write() again, and you end up stuck in your reading loop waiting for CheckForDataOnSource() to receive new bytes from the socket which you are NEVER requesting the server to send. You have over-complicated your thread logic. You don't need all of that InputBuffer handling at all. Just call Write(), then ReadBytes() (letting it block), and repeat. That being said, there are some other issues with your code, too. You are not calling Disconnect() after Connect() is successful. You are accessing the InputBuffer in the main threadd while your worker thread may also be accessing it at the same time. And you are not capturing the Exception when calling TThread.Queue(), so the Exception will be destroyed long before the main thread has a chance to access its Message. With all of that said, try this instead: inherited Create(True); TCPClient := TIdTCPClient.Create; TCPClient.Host := AHost; TCPClient.Port := APort; TCPClient.ConnectTimeout := 5000; TCPClient.ReadTimeout := ...; // infinite by default ... // a separate procedure is needed so that TThread.Queue() can // capture and extend the lifetime of the String. See the // documentation for more details: // https://docwiki.embarcadero.com/RADStudio/Sydney/en/Anonymous_Methods_in_Delphi // procedure DisplayMessageInUI(const AMsg: string); begin TThread.Queue(nil, procedure begin Form2.mmo1.Lines.Add(AMsg); end); end; ... SetLength(wBuffer, 6); //write some bytes into wBuffer SetLength(rBuffer, 65); while not Terminated do begin try TCPClient.Connect; except on E: Exception do begin DisplayMessageInUI('Exception: ' + e.Message); for i := 1 to 5 do begin if Terminated then Exit; Sleep(1000); end; Continue; end; end; try try i := 1; while not Terminated do begin TCPClient.IOHandler.Write(wBuffer); TCPClient.IOHandler.ReadBytes(rBuffer, 65, False); // waits for reply // { Alternatively, if you want to keep checking Terminated while waiting: while TCPClient.IOHandler.InputBufferIsEmpty do begin TCPClient.IOHandler.CheckForDataOnSource(100); TCPClient.IOHandler.CheckForDisconnect; if Terminated then Exit; end; TCPClient.IOHandler.ReadBytes(rBuffer, 65, False); } DisplayMessageInUI('Reply received'); //do some stuff with rBuffer Inc(i); Sleep(1000); end; finally TCPClient.Disconnect; end; except on E: Exception do begin DisplayMessageInUI('Exception: ' + e.Message); if Terminated then Exit; Sleep(1000); end; end; end;
  10. Remy Lebeau

    Delphi 11.3 is available now!

    You have to use the new TCustomGrid_ActualGridLineWidth() function to account for that.
  11. The behavior you describe happens if Form1's window is set as the owner window (in Win32 API terms, not VCL terms) of Form2's window. A window cannot go behind its owner. You can use GetParent() or GetWindow() to determine a window's owner. In the first example, you can freely switch between Form1 and Form2 only if Form1 is not the owner of Form2. SetWindowPos() DOES NOT change window ownership! That can only be done with CreateWindow/Ex() when creating a new window, or with SetParent() or SetWindowLongPtr() on an existing window. How a TForm determines which owner to use when creating its window is a bit complex (there are other conditions that can affect the following, but this is the basics - see the source code for TCustomForrm.CreateParams() if you want to see the full logic) : If a TForm's PopupMode is pmNone (the default), then: if Application.MainFormOnTaskBar is true, the Application.MainForm window will be the owner. unless there is no MainForm, or Application.MainFormOnTaskBar is false, then the Application window will be the owner. If a TForm's PopupMode is pmAuto, then: the currently active TForm window will be the owner, unless there is no active TForm, or its window is currently minimized or hidden, then the Application.MainForm window will be the owner, unless there is no MainForm, or Application.MainFormOnTaskBar is false, then the Application window will be the owner. If a TForm's PopupMode is pmExplicit, then: the PopupParent window will be the owner, unless there is no PopupParent, then the Application.MainForm window will be the owner, unless there is no MainForm, then the Application window will be the owner.
  12. It is a shame that the IDE doesn't have an option to validate that issue automatically when a component is first dropped on a Form Designer. It knows the type being created, it could quickly check the component's RTTI to see if its most-derived constructor is properly overriding the virtual TComponent constructor, and if not then warn the user of that potential bug so they can fix it. Oh well...
  13. Remy Lebeau

    My program does not work

    I have no idea what you are referring to. Please provide a screenshot.
  14. Remy Lebeau

    Owner Drawing the Submenu Arrow

    Here is a dirtier hack that shouldn't require overriding WndProc at all (untested!): procedure TForm12.cmniOpenRecentAdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState); begin // pop the HDC state that TForm saved... RestoreDC(ACanvas.Handle, -1); // Prevent the OS from drawing the arrow... ExcludeClipRect(ACanvas.Handle, ARect.Left, ARect.Top, ARect.Right, ARect.Bottom); // save the new state so TForm will restore it... SaveDC(ACanvas.Handle); end; procedure TForm12.cmniOpenRecentDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); begin ... // OnAdvancedDrawItem is fired after OnDrawItem, so don't exclude here... // ExcludeClipRect(ACanvas.Handle, ARect.Left, ARect.Top, ARect.Right, ARect.Bottom); end;
  15. Remy Lebeau

    My program does not work

    Not true. Something DOES happen. It is just not what you are expecting to happen. Your code has two logic flaws: 1) You are inadvertently invoking pointer arithmetic rather than string concatenation. " " is a string literal of type 'const char[2]', where its 1st element is a space character, and its 2nd element is a null terminator, eg: const char literal[2] = {' ', '\0'}; So, in the expression: numbers[t] + " " You are adding a char[] array to an integer, so the compiler will implicitly decay the char[] array into a char* pointer to its first element, and then the integer will advance that pointer by the number of characters which the integer specifies. So, on the 1st loop iteration, you are effectively assigning the TEdit::Text property a char* pointer to the literal's character at index 0, ie the space character, as-if you had written the code like this: const char literal[2] = {' ', '\0'}; const char *temp = &literal[0]; temp += numbers[t]; // effectively: temp += 0, aka &literal[0] Edit1->Text = temp; Which works fine, as the resulting char* pointer being assigned to the TEdit::Text is a valid null-terminated C-string of length 1 character, so you end up with just the space character in the TEdit::Text, as expected. Then, on the 2nd loop iteration, you are effectively assigning the TEdit::Text property a char* pointer to the literal's character at index 1, ie the null terminator, eg: const char literal[2] = {' ', '\0'}; const char *temp = &literal[0]; temp += numbers[t]; // effectively: temp += 1, aka &literal[1] Edit1->Text = temp; Which also works fine, as the resulting char* pointer being assigned to the TEdit::Text is a valid null-terminated C-string of length 0 characters, so you end up clearing the TEdit::Text. However, on the 3rd loop iteration, you are effectively assigning the TEdit::Text property a char* pointer to the literal's character at index 2, which doesn't exist, so you end up invoking undefined behavior by reading into invalid memory. const char literal[2] = {' ', '\0'}; const char *temp = &literal[0]; temp += numbers[t]; // effectively: temp += 2, aka &literal[2] Edit1->Text = temp; // UB, literal[2] doesn't exist !!! To fix that, you need to explicitly convert each number into a String object yourself before adding " " to it. The compiler will NOT do that conversion automatically for you. You can use the RTL's IntToStr() function, or the String's class constructor that takes an integer as a parameter. 2) your final loop is attempting to display only one element from the numbers array on each iteration. You are completely replacing the content of TEdit::Text with a new string value. As such, by the time that loop is finished, only the last element in the numbers array would be visible in the TEdit. To fix that, you would need to change this: Edit1 -> Text = ...; To this instead: Edit1 -> Text = Edit1 -> Text + ...; So that each loop iteration is appending to the current TEdit::Text value, rather than replacing it. With all of that said, try this instead: void __fastcall TForm1::Button1Click(TObject *Sender) { int numbers[3]; int t; for(t = 0; t < 3; ++t) { numbers[t] = t; } Edit1->Clear(); for(t = 0; t < 3; ++t) { Edit1->Text = Edit1->Text + IntToStr(numbers[t]) + " "; // or: // Edit1->Text = Edit1->Text + String(numbers[t]) + " "; } } That being said, I would suggest another change - don't assign the TEdit::Text on each loop iteration. Use a local String variable first, and then assign that to the TEdit::Text after the loop is finished. Don't make the UI do more work than it actually needs to, eg: void __fastcall TForm1::Button1Click(TObject *Sender) { int numbers[3]; int t; for(t = 0; t < 3; ++t) { numbers[t] = t; } String str; for(t = 0; t < 3; ++t) { str += (IntToStr(numbers[t]) + " "); // or: // str += (String(numbers[t]) + " "); // alternatively: // str.cat_sprintf(_D("%d "), numbers[t]); } Edit1->Text = str; }
  16. Remy Lebeau

    Why do I have this??

    All of that code is irrelevant. You asked what the Destroying property is meant for. But none of that code is setting or reading that property. So, where is it ACTUALLY being used? If you can't find that, then the property is likely no longer being used at all and could be removed so you don't have to worry about it anymore. I question why it was ever needed in the first place, since the Form's ComponentState property has a csDestroying flag available. Unless it is not the Form's destruction that you were keeping track of...
  17. Remy Lebeau

    Interface Reference counting

    Yes, because objects are reference types. You are passing in a pointer to a TStringList object, so it doesn't matter whether the pointer itself is passed by value or by reference, you still have to dereference the pointer either way to access the object.
  18. Remy Lebeau

    Offline Help updates available from Embarcadero

    Agreed. I HATE that they did that. I used to refer to the old docs pretty often, and now its all gone. I have the source code for D5-XE3 and 10.3, but not for XE4-10.2 or 11.x.
  19. Just FYI, I have just now looked back at an old project of mine, which was first written back in the XP days before SystemFileAssociations was introduced. It still registers a custom PropertySheetHandler for a specific file extension under HKCR\<FileExtProgID>\ShellEx\PropertySheetHandlers and HKLM\SOFTWARE\Windows\CurrentVersion\Shell Extensions\Approved, and it still works fine on Win7 and Win10 (I didn't test on Win11). If SystemFileAssociations also works, good for it.
  20. Remy Lebeau

    [Very unsure] Indy vs sgcIndy

    I have just now fixed the two issues you mentioned: https://github.com/IndySockets/Indy/issues/456
  21. Remy Lebeau

    Looking for a forum is this it?

    I'm a professional developer who worked for a company that used C++Builder (almost) exclusively for almost 20 years.
  22. Remy Lebeau

    Why does CoInitializeEx returns 1 or $80010106 ?

    Aggregating the FTM means your COM object would need to call the CoCreateFreeThreadedMarshaler() function and save the IUnknown interface it gives you, and then have your object's QueryInterface() method return that interface when it is asked for IID_IMarshal. However, nothing in the ActivateAudioInterfaceAsync() documentation mentions aggregating the FTM at all. All it says is that your completionHandler object needs to implement the IAgileObject interface, nothing more. IAgileObject is an indicator that lets the system know that the object is free-threaded and thus can be called across apartment boundaries without marshaling. You DO NOT need to implement TAggregatedObject in this situation. TAggregatedObject and IAgileObject are completely different and related things. Right, because you did not hook up the TAggregatedObject.Controller to point at another COM object. As you can see in the code snippet you posted, TAggregatedObject.QueryInterface() simply delegates to another COM object. Correct. Yes. You would need something like this: TActivateAudioInterfaceCompletionHandler = class(TInterfacedObject, IActivateAudioInterfaceCompletionHandler, IAgileObject) public function ActivateCompleted(activateOperation: IActivateAudioInterfaceAsyncOperation): HRESULT; stdcall; end; function TActivateAudioInterfaceCompletionHandler.ActivateCompleted(activateOperation: IActivateAudioInterfaceAsyncOperation): HRESULT; stdcall; begin // do something... end; ... ActivateAudioInterfaceAsync( ..., TActivateAudioInterfaceCompletionHandler.Create as IActivateAudioInterfaceCompletionHandler, ... );
  23. Remy Lebeau

    Interface Reference counting

    https://docwiki.embarcadero.com/RADStudio/en/Parameters_(Delphi)#Untyped_Parameters Yes. No. It would not return anything at all, because the parameter is being passed in by value, not by var reference. So, it is essentially just a local variable that is a copy of the caller's variable. As such, you are just assigning the object to a local variable, and the caller's variable is left untouched. The object's refcount will be incremented when the local variable is assigned to, and then be decremented when the local variable goes out of scope. The caller will never see the object at all. It is not the object itself that goes out of scope, just the interface variable that refers to the object. But no, the variable is not assigned nil, it is already out of scope, its value doesn't matter anymore. But, when the variable does go out of scope, if it has a non-nil value then the refcount of the object it is referring to will be decremented. The object is automatically destroyed only when its refcount falls to 0. Never call Free() on an interfaced object. Use reference counting instead. And, there is no FreeAndNil() member method, only a standalone function. So SomeObject.FreeAndNil() is not valid, only FreeAndNil(SomeObject) is valid. And, it is only valid on an object variable, not an interface variable. In any case, if you need an interface variable, that has not gone out of scope yet, to be set to nil automatically when the object it refers to is destroyed, you need to declare the interface variable as [weak].
  24. Remy Lebeau

    Interface Reference counting

    It depends. You did not show the actual declaration and implementation of CreateAndReturnAnObject(), so it is possible that it could go either way. For instance, if the parameter is an untyped var, then the created object WILL NOT have its reference count incremented when assigned to the parameter, and so the object will die (or worse, be leaked) when CreateAndReturnAnObject() exits: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; begin MyObject := TMyInterface.Create; // <-- refcount IS NOT incremented! Result := True; end; To solve that, you would need to use an explicit type-cast: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; begin IMyInterface(MyObject) := TMyInterface.Create; // <-- refcount IS incremented! Result := True; end; // Or: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; begin MyObject := TMyInterface.Create as IMyInterface; // <-- refcount IS incremented! Result := True; end; Or, call _AddRef() directly: function TSomeclass.CreateAndReturnAnObject(var MyObject): Boolean; var Intf: IMyInterface; begin Intf := TMyInterface.Create; // <-- refcount IS incremented here MyObject := Intf; Intf._AddRef; // <-- refcount is incremented again Result := True; end; // <-- refcount is decremented here On the other hand, if the parameter is typed as a var reference to IMyInterface, then the reference count WILL be incremented as expected: function TSomeclass.CreateAndReturnAnObject(var MyObject: IMyInterface): Boolean; begin MyObject := TMyInterface.Create; // <-- refcount IS incremented! Result := True; end; The calling code you have shown would compile in both cases (once you drop the var at the call site, see below), so it is important to know what the parameter is actually typed as to begin with. Specifying var when passing a variable to a function parameter is not valid syntax to begin with. You can use var only when declaring a variable/parameter, but not when passing it around. Pascal is not C#, or other languages, that require you to be explicit about how a variable is passed to a function parameter. It WILL be auto-incremented when it is assigned to a variable/parameter of a valid interface type.
  25. Remy Lebeau

    [Very unsure] Indy vs sgcIndy

    I didn't have any issue with the PACKAGE being named sgcIndy, that is clearly a custom name that is different than Indy's own packages. My only issue was with your WEBSITE ADVERTISING your package as just "Indy" instead of as "sgcIndy".
×