-
Content Count
2684 -
Joined
-
Last visited
-
Days Won
113
Everything posted by Remy Lebeau
-
Why does a stack overflow cause a VCL application to terminate?
Remy Lebeau replied to Der schöne Günther's topic in RTL and Delphi Object Pascal
It's possible that some stack unwinding does occur, but the call stack you provided earlier clearly shows that the exception handler makes a bunch of function calls, which could easily eat up whatever stack space was freed up. Pretty much, yes. A thread has a fairly limited call stack available to it. The stack can grow only so much before it overflows. That is why it is not a good idea to put a lot of data on the stack in the first place. However, you can set the limits in the project options, if you need them higher. Just know that doing so can affect all threads created at runtime (unless overwritten in code on a per-thread basis). -
Not directly, no. You would have to write and install custom software on the host computer for your VM program to connect to and send its report data to, and then that software can use MAPI to email the report data as needed. Perhaps write a custom DCOM component that the VM program can instantiate remotely and proxies the MAPI object itself. Or maybe write a custom SMTP server. Or use a remote-accessible IPC mechanism such as named pipes. TCP sockets, RPC, etc.
-
Why does a stack overflow cause a VCL application to terminate?
Remy Lebeau replied to Der schöne Günther's topic in RTL and Delphi Object Pascal
Makes sense why it would fail. A stack overflow means there is no more stack space available to push new data onto. In x86, all of those extra function calls are going to keep trying to push data onto the call stack. Eventually something has to give way. Moral of the story - don't overload the call stack in the first place! -
Sounds like you (or the host app) are not actually creating new threads to call your DLL functions, instead you/it are just calling your DLL functions in the context of an existing thread. Anything is possible. Without seeing your actual DLL code, and without seeing some code/documentation about the host app's plugin interface, how do you expect anyone to help you?
-
'end;' terminates compound statements, case statements, scope blocks, class/record declarations, etc. See Declarations and Statements (Delphi) 'end.' terminates only a unit block. See Programs and Units (Delphi) You should read the Delphi Language Guide for all the syntax rules.
-
Assign KeyDown function to btnClick
Remy Lebeau replied to Willicious's topic in Delphi IDE and APIs
-
In that case, you could just get rid of the offending StrToInt() altogether: procedure TFormSimpleVariable.ButtonShowValueClick(Sender: TObject); begin //x := StrToInt('x'); LabelResult.Caption := 'The value of x is ' + IntToStr(x); end; But that doesn't jive with what @357mag is asking for in the first place.
-
Wow, that is just screaming for the SessionName to be moved into a common base class, or into a common interface that all of the components implement. If the TEDB... and Tnlh... components are from different libraries, I would simply derive my own classes from them, and define my own interface, eg: type IHaveASessionName = interface ['{6ea2f6fe-6b6f-4ea6-a893-12717e562329}'] function GetTheSessionName: string; procedure SetTheSessionName(const Value: string); property TheSessionName read GetTheSessionName write SetTheSessionName; end; ... type TMyEDBSession = class(TEDBSession, IHaveASessionName) function GetTheSessionName; procedure SetTheSessionName(const Value: string); end; function TMyEDBSession.GetTheSessionName; begin Result := SessionName; end; procedure TMyEDBSession.SetTheSessionName(const Value: string); begin SessionName := Value; end; // And repeat for TEDBDatabase, TnlhTable, TnlhQuery, TnlhScript, etc... ... for var i := 0 to ComponentCount - 1 do begin var Intf: IHaveASessionName; if Supports(Components[i], IHaveASessionName, Intf) then Intf.TheSessionName := sSessionName; end; If the components are on DFMs at design-time, you can alternatively use interposer classes in the DFM units, eg: type TEDBSession = class(edbcomps.TEDBSession, IHaveASessionName) function GetTheSessionName; procedure SetTheSessionName(const Value: string); end; // etc...
-
Contributing to projects on GitHub with Subversion
Remy Lebeau replied to dummzeuch's topic in Tips / Blogs / Tutorials / Videos
+1 for TortoiseGIT -
synapse and thread socket creation error 'winsock not initialized' 10093
Remy Lebeau replied to alogrep's topic in Algorithms, Data Structures and Class Design
That would not cause error 10093 (WSANOTINITIALISED). That error can only happen if a WinSock function is called either before the 1st call to WSAStartup() or after the last call to WSACleanup(). If bind() or listen() failed, they should be raising their own exception, which would terminate the daemon thread before it reaches the marked line of code, since it is not catching them. -
synapse and thread socket creation error 'winsock not initialized' 10093
Remy Lebeau replied to alogrep's topic in Algorithms, Data Structures and Class Design
The WinSock library is reference-counted internally. WSAStartup() increments the refcount, and WSACleanup() decrements it. The library internals are initialized only on the 1st call to WSAStartup(), and cleaned up only on the last call to WSACleanup(). So, every successful call to WSAStartup() must be balanced with a call to WSACleanup(). The code you have shown looks fine (well, mostly ... your error handling needs work), which means that what you have described can occur only if SOME OTHER THREAD is making an unbalanced call to WSACleanup(), thus decrementing the refcount too much and unloading the library even though you are still using it. That is not the fault of the code you have shown, the problem is elsewhere in your project. What else is your project doing that uses WinSock outside of this TTCPHttpDaemon class? What do you mean? Something had to have changed, behavior like this doesn't just change for no reason. Unless, this bug was likely always present in your project to begin with, and you just never happened to hit on it until now. Doubtful. -
TValueListEditor how to catch exception when key exist?
Remy Lebeau replied to softtouch's topic in VCL
Like Peter said, try the OnSetEditText or OnValidate event. OnSetEditText is called when a new grid cell value is being saved. It is called before the cell is updated, and before the new value is validated. OnValidate is called if the InplaceEditor is active and modified when 1) before a new cell is selected, 2) when the ValueListEditor is losing input focus, 3) when the mouse is being pressed down on the ValueListEditor. Either way, you can raise an exception if the user input is not acceptable to you. For instance, you can raise a custom exception that a TApplication.OnException handler can look for, or you can call SysUtils.Abort() to raise an EAbort exception, which the VCL will catch and discard. You could handle the ValueListEditor's default keyUnique exception in the TApplication.OnException event. However, TValueListEditor does not use a specialized descendant class for that particular exception, it just uses the base SysUtils.Exception class directly. So, to differentiate that exception from others, you would have to look at its Message text, which is the localizable resource string "SKeyConflict" from the Vcl.Consts unit: SKeyConflict = 'A key with the name of "%s" already exists'; The exception will not terminate the program. It is being raised in a UI handler in the context of the main message loop, which will catch any uncaught exception, and either pass it to the TApplication.OnException event if assigned or just display it to the user. Either way, the exception is then discarded. -
TValueListEditor how to catch exception when key exist?
Remy Lebeau replied to softtouch's topic in VCL
TValueListEditor has a KeyIsValid() method, which has a RaiseError property. This is the same method that TValueListEditor calls internally, with RaiseError=True, but you can also call it yourself, with RaiseError=False, eg: procedure TForm1.Button1Click(Sender: TObject); var if TValueListStrings(ValueListEditor1.Strings).KeyIsValid('key1', False) then ValueListEditor1.InsertRow('key1', 'hello', true); end; -
Alternatively, the OnMouse(Down|Up) events have a Shift parameter which defines several flags, including ssCtrl, which is present when the Ctrl key is down when the event is triggered.
-
In small pieces, sure. Not whole projects. That is not what this site is for. Then I suggest you ask on one of the many freelancing sites available, such as freelancer.com or similar.
-
Then you are going to have to show the actual error message, and the code it refers to. Every code I've shown here is correct and should be working fine. None of my examples have unused values. On the other hand, some of your examples do.
-
Do note that setting TEdit.NumbersOnly=true merely tells the TEdit to accept only digit characters and no others (so, no negatives, no decimals, etc) , but it will not validate that the text as a whole will convert to a valid integer. So you still have to handle that conversion yourself, if you don't use a UI control that does it for you.
-
On the other hand, the StrToxxxDef() functions don't let you differentiate whether valid vs invalid input was entered unless you use a sentinel value that can't appear in any valid input. The TryStrToxxx() functions let you differentiate very easily. Besides, what is the point of converting a string to an integer/floating type if you are not going to use the converted value anyway? var v: integer; begin // don't ignore the return value! // TryStrToInt('...', v); if TryStrToInt('...', v) then begin // use v as needed ... end else begin // error converting ... end; // don't ignore the return value! // StrToIntDef('...', 0); v := StrToIntDef('...', 0); if v <> 0 then begin // use v as needed ... end else begin // was the input actually '0', or did an error occur ??? end; end;
-
And alternatively, the TryStrToxxx() functions.
-
I'm guessing that code is simply old and can now be removed.
-
Do you understand what StrToInt() actually does? What do you think StrToInt('x') should do, and why do you think it should NOT throw an error (hint: it SHOULD)? The string 'x' is not representative of a valid integer value, so of course it can't be converted into an actual integer. You said the user is entering a value in an Edit box, but this code is not accessing that Edit box at all. You need something more like this instead: procedure TFormSimpleVariable.ButtonShowValueClick(Sender: TObject); var x: Integer; begin x := StrToInt(EditValue.Text); LabelResult.Caption := 'The value of x is ' + IntToStr(x); end; Alternatively, for numeric input, consider using a more appropriate UI control, like TSpinEdit or TNumberBox, which provide the input as actual integers rather than as strings.
-
Your InviaRiceviFile() function is using the wrong Add method of TIdMultipartFormDataStream. You should be using either AddFile() or the TStream overload of AddFormField(). You are using the String overload of AddFormField() instead, which is meant for text fields, not binary data fields. So, you are setting the content of the 'file' field to be just the file path itself, rather than the content of the file that is located at the path. On a side note, don't use the TIdHTTP.Request.CustomHeaders property to specify a 'Content-Type' header, use the TIdHTTP.Request.ContentType property instead. Note that the TStrings overload of the TIdHTTP.Post() method already sets the ContentType to 'application/x-www-form-urlencoded' for you. Also, when using the TIdHTTP.Request.CustomHeaders property to send your own 'Authorization' header, the TIdHTTP.Request.BasicAuthentication property should be set to False, not to True. With that said, try this: function Login: string; const tokenUri = <https del portale che riceve credenziali e genera il token>; tokenToAskForAToken = <token string>; var Params: TStringList; Token: string; begin Result := ''; idhttp2.Request.BasicAuthentication := False; IdHttp1.Request.CustomHeaders.AddValue('Authorization', 'Basic ' + tokenToAskForAToken); IdHttp1.Request.ContentType := 'application/x-www-form-urlencoded'; try Params := TStringList.Create; try Params.Add('grant_type=client_credentials'); Token := IdHTTP1.Post(tokenUri, Params); Memo1.Lines.Add(Token); Result := 'bearer ' + Token; finally Params.Free; end; except on E: Exception do Memo2.Lines.Add(E.Classname + ': ' + E.Message); end; end; procedure InviaRiceviFile; const functionUri = <https del portale che riceve ed invia i file>; bdapFilePath = 'c:\temp\BDAP_P_2022.zip'; //file di input bdapZipFilePath = 'c:\temp\XBRL_BDAP_P_2022.txt'; //file di output var Params: TIdMultiPartFormDataStream; Response: TMemoryStream; begin idhttp2.Request.BasicAuthentication := False; IdHttp2.Request.CustomHeaders.Values['Authorization'] := Login; try Params := TIdMultiPartFormDataStream.Create; try Response := TMemoryStream.Create; try Params.AddFile('file', bdapFilePath, 'application/octet-stream'); IdHttp2.Post(functionUri, Params, Response); Response.SaveToFile(bdapZipFilePath); finally Response.Free; end; finally Params.Free; end; except on E: Exception do Memo2.Lines.Add(E.Classname + ': ' + E.Message); end; end;
-
TIdIpMcastClient - Group join on second interface issue
Remy Lebeau replied to FredM's topic in Indy
That post's code is a mix of Indy and manual coding. In any case, that post is saying that a multicast receiving socket needs to be bound to 0.0.0.0 or ::0 and then join each desired interface to the multicast group. Searching around, I see many other posts with similar advice. Although TIdIPMCastClient can bind its listening sockets to 0.0.0.0/::0 (just set Binding.IP = ''), it then joins their bound IP to the group, so it would be joining 0.0.0.0/::0 to the group, which is clearly not going to work. I may have to add a new event to TIdIPMCastClient that is called after binding to let users decide which interfaces to join, and then update TIdSocketHandle.AddMulticastMembership() to let a caller specify the interface to join (TIdStack.AddMulticastMembership() already has an ALocalIP parameter for that purpose, which TIdSocketHandle sets it its currently bound IP). According to this, Linux can't receive multicast packets if the listening socket is not bound to 0.0.0.0/::0. On the other hand, I've seen several posts, such as this one, which say a receiving socket should be bound to the multicast group IP instead of a local interface, and then join the desired interfaces to the group. Must be a Linux/Posix thing, because that advice is in direct conflict with Microsoft's documentation for receiving multicast on Windows: So, I'm not sure what to think at this point. It seems that everyone agrees that binding a receiver socket to 0.0.0.0/::0 and then having it join an interface to the group will "work", but I've seen several posts say that doing so may cause the socket to receive more UDP packets than the listener wants. Which I guess makes sense. Whereas many people say to bind the socket to the multicast group IP, but Microsoft says not to do that. <sigh> -
Not a good idea to send JSON in the query parameters. But, I suppose it is not forbidden either, so I should probably update TIdHTTPServer to handle that more cleanly, like sending a "414 URI Too Long " response instead of raising an exception. TIdHTTPServer already has a MaximumHeaderLineCount property (which raises an exception instead of sending a "431 Request Header Fields Too Large" response). I could add new MaximumUriLength and MaximumHeaderLineLength properties. I've opened a new ticket for these features: #474: Update TIdHTTPServer to handle long URIs and long request headers more cleanly OK
-
That would work for TIdHTTP, but for TIdHTTPServer you would need access to its OnConnect event in order to update each connected client's IOHandler.MaxLineLength, and AFAIK WebBroker does not expose access to that event. But, I don't have any experience with WebBroker, so who knows...