-
Content Count
2684 -
Joined
-
Last visited
-
Days Won
113
Everything posted by Remy Lebeau
-
What does this function mean? CharPrev / Range Check Error
Remy Lebeau replied to terran's topic in General Help
The difference is that: @S[Length(S)+1] returns a pointer to the null-terminator only on a 1-based string, but goes out of bounds on a 0-based string. PChar(S)+Length(S) returns a pointer to the null-terminator on both a 1-based and a 0-based string. Range checking would need to be disabled, because the null terminator is not included in the Length of the string, so it is not an indexable character, even though it is physically present in memory. All the more reason to use pointer arithmetic instead of character indexing to access the terminator. -
Custom Managed Records and Default(T)
Remy Lebeau replied to rgdawson's topic in Algorithms, Data Structures and Class Design
When I run your code, I see your Default() statement is calling Initialize() two times on the same temporary Stringlist instance, then that temporary is Assign()'ed to the Result record, and then the temporary is Finalized() only one time. Clearly one of the Initialize() calls is erroneous. I have reported this to Embarcadero: RSS-1647: System.Default() on a Custom Managed Record calls its Initalize operator too many times -
What component I can use to display images in Android ?
Remy Lebeau replied to William23668's topic in FMX
Another option would be a TListView with an image per list item: https://docwiki.embarcadero.com/Libraries/en/FMX.ListView.TListView -
What component I can use to display images in Android ?
Remy Lebeau replied to William23668's topic in FMX
Did you look at the TImage component yet? https://docwiki.embarcadero.com/Libraries/en/FMX.Objects.TImage -
What does this function mean? CharPrev / Range Check Error
Remy Lebeau replied to terran's topic in General Help
That would not work. That is taking the last character and type-casting its value to a pointer, which is not the same thing as taking a pointer to the last character. I would have used this instead: CharPrev(PChar(S), PChar(S) + Length(S)); That would work with both 0-based and 1-based strings. -
You didn't show any of the receiving code you tried, so it is difficult to tell you why it is not working. TApplication(Event)::OnMessage does work with PostMessage(), and WndProc() does work with both PostMessage() and SendMessage(). So, either you are not looking for the correct window message ID, or you are posting/sending the message to the wrong HWND to begin with. I'm assuming "myVCLApplication" is the caption of your TForm and not some other window, correct? Is your IDE running with your project loaded when the message is being posted/sent?
-
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
I wasn't suggesting you not destroy your objects. But a destructor is not always the best place to perform complex cleanups, especially during app shutdown. For instance, you could use the Form's OnClose/Query event to detect the user's desired to exit the app, signal your code to start cleaning up (close sockets, cancel I/Os, etc), and disable the UI so the user can't generate more traffic in the meantime. When the cleanup is actually finished (all I/O completions are reported, etc), then exit the app. -
Pointer casting with NativeUint
Remy Lebeau replied to duzzell's topic in Algorithms, Data Structures and Class Design
And even though Native(U)Int existed starting in Delphi 2007, they were buggy up to Delphi 2010. -
How to set Supported platforms for my component?
Remy Lebeau replied to dmitrybv's topic in Delphi IDE and APIs
By annotating it with the ComponentPlatforms attribute: https://docwiki.embarcadero.com/Libraries/en/System.Classes.ComponentPlatformsAttribute Not that I'm aware of. Which ways are you thinking of? When a component does not indicate any specific platforms, then it is available for all platforms, limited only by the framework it uses. A VCL component without ComponentPlatforms will support all platforms that VCL supports, namely Windows 32bit and 64bit. An FMX component without ComponentPlatforms will support all platforms that FMX supports. TDataSource derives directly from TComponent and has no affinity to either VCL or FMX, so it will support whichever framework your project is using, and thus whichever platforms that framework supports. -
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
Not that hard, really. Common practice is to keep a counter of active I/O requests. During shutdown, set a flag to prevent new requests from starting, and cancel active requests still in progress. When requests complete, decrement the counter. Wait for the counter to fall to 0, then close the socket and exit. Probably not the best thing to do in a destructor in a GUI, though. I would have the GUI start the shutdown process and let it run asynchronously, then disable the UI and tell the user to wait, and then finish cleaning up the GUI only when all pending requests have finished. -
Yup. When I tested it, I got 3 events - 1) old item de-focused, then 2) old item de-selected, then 3) new item focused and selected together.
-
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
MAXINT is 0x7fffffff. Fair enough. True. If you initialize first to v2, TServerSocket will still negotiate to v1, but actually use v2, since Winsock can be initiated only once. Yes. Indy is not intended for large servers with very high client loads. But most users don't need to write such large servers. There are better technologies for that purpose if you need it. Which you appear to need. So I wouldn't recommend any existing libraries, you should custom tailor your socket code to meet your specific high performance/resource needs. -
@SnowSonic that code doesn't do what they want to achieve. By the time the OnChange event is fired, the selection has already finished being changed to a new item and any chance of saving the modified data or canceling it has already passed.
-
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
If the backlog fills up, subsequent clients will be rejected with a (WSA)ECONREFUSED error until space clears up. Why do any APIe evolve over time? Because they learned from their past experience and choose a better value that made more sense. The value 5 was used in Winsock 1 at a time when Windows had limited resources. Then it grew. Winsock 2 reflected that. And, for what it's worth, just because SOMAXCONN is now defined as 0x7fffffff does not mean the backlog will actually be that big. https://stackoverflow.com/questions/4709756/listen-maximum-queue-size-per-windows-version Why is that even an issue? Why would you even allow it to initialize to v1? You have to specify a desired version when calling WSAStartup(), and you are responsible for validating that an appropriate version that is suitable for your needs was actually chosen. If the version is too low, stop, don't move forward. Period. Winsock v2 was released 30 years ago. So, unless you are targeting Windows 95, forget about v1 any any limitations related to it. Nothing built-in to Delphi uses async sockets. So either use a 3rd party lib that already implements them, or re-invent the wheel yourself. Your project, your choice. Indy has been tested with thousands of simultaneous clients. Tens of thousands is probably pushing it. You are going to run into that problem no matter what socket technology you choose to use. Indy does use a thread-per-client approach for its TCP servers. It tried implementing fibers and IOCP many years ago, but that approach didn't work out and the effort was abandoned. Maybe it will try again one day, or maybe not, who knows. -
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
Not likely. If they didn't fall, then the port was available. Verify that with the command-line netstat, or SysInternals TCPView, etc. For example, if something else is bound to 127.0.0.1:12345 and you bind to 0.0.0.0:12345, it will succeed as long as port 12345 is available on another IP besides 127.0.0.1. By default, TServerSocket binds to 0.0.0.0 only, it takes some effort to bind it to a specific IP. Do not use TServerSocket in non-blocking mode with that many concurrent clients. Even if the message queue could handle that many messages, the message loop runs in only 1 thread and can handle only 1 message at a time, so overall performance will be terrible. More so if your server has a UI to service, too. Try using TServerSocket in blocking mode instead, which handles clients using threads instead of window messages. But even so, with that many concurrent clients, you are probably better off using direct socket API calls with Overlapped I/O, I/O Completion Ports, or Registered I/O. That will give you the best performance with minimal overhead, as those APIs are designed for high performance servers with large client loads. The APIs that TServerSocket uses are good for only small-to-medium sized servers. SOMAXCONN does not limit the number of clients that can be connected to the server concurrently. That number is limited only by available memory. The listen() API has a parameter to set the size of the server's backlog, which is a staging area where connected clients wait to be accept()'ed by the app's code. SOMAXCONN is a special flag to that parameter, it lets Winsock decide a reasonable size for the backlog. Umm, you don't consider Indy to be a good alternative? It's been bundled in every version of Delphi for the past 20-odd years. With the sheer large number of clients you want to handle, I would recommend that, yes. Certainly, though thread pooling and connection pooling are good to use for that kind of stuff. Definitely do not create thousands of DB connections. Lots of clients should be able to share resources. For instance, while one client is reading a message, it doesn't need the DB yet, so another client could be using it in the meantime. And when that client is done using it, let another client use it. -
Pointer casting with NativeUint
Remy Lebeau replied to duzzell's topic in Algorithms, Data Structures and Class Design
I see a number of things in this code that shouldn't be using raw pointer manipulations in the first place. For instance, there is no reason for ZRawToUnicode() to dig into the internals of a RawByteString just to get its length. Simply use Length(S) instead, eg: function ZRawToUnicode(const S: RawByteString; const CP: Word): UnicodeString; begin if Pointer(S) = nil then Result := '' else Result := PRawToUnicode(Pointer(S), Length(S){$IFDEF WITH_TBYTES_AS_RAWBYTESTRING}-1{$ENDIF}, CP); end; For that matter, the RTL in both Delphi and FPC already carries a codepage inside of RawByteString and knows how to convert RawByteString to UnicodeString using that codepage, so code like this really shouldn't be needed in the first place. But, your IFDEF suggests RawByteString may be an alias for TBytes, so only in that case this kind of code would be needed. I already commented earlier on the code in TZKeyAndFunctionPairList.Get(). However, you still haven't shown what FElements is, or how it's being managed. Lastly, what is the point of the Dest variable in SQLQuotedStr()? It is not being used for anything meaningful in this code snippet. Assuming this code is not the full implementation and Dest is actually written to in code not shown, Dest doesn't appear to point at valid memory, so writing to it will likely crash. You can't increment a nil pointer, and even so, you are incrementing it only 1 byte at a time, not 1 WideChar at a time. -
Well, then that is the user's problem, not yours. They need to pay closer attention to what they are doing. If you absolutely must do what you are asking for, then you need to take into account that in a ListView, a single user action triggers multiple state changes, which is why you are getting multiple events fired. For instance, a list item can gain/lose focus and gain/lose selection, which are separate attributes, so changes to them may or may not be reported in a single event. When you select a list item and then select another item, the old item has to be de-deleted and de-focused, and then the new item has to be selected and focused. So, that is up to 4 distinct events that could occur (maybe less, if the ListView coalesces them). And while the OnChanging and OnSelectItem events do tell you which list item is changing, and even that a state change is occurring, but they don't tell you everything the ListView reports, namely the old and new state values. You could try subclassing the ListView to handle the LVN_ITEMCHANGING and LVN_ITEMCHANGED notification directly, which give you much more details about which attribute(s) are changing/changed, and what the old and new value(s) are. However, even though LVN_ITEMCHANGING does let you cancel a change, it doesn't actually cancel the entire user action. So, for example, if you have Item 1 selected and then try to select Item 2, you can disallow the de-selecion of Item 1, but Item 2 can/will still become selected. Unfortunately, a ListView control simply does not give you enough control to cancel the user action as a whole. So, you are going to have to get a little creative. Such as having the OnChanging event block every change that you don't initiate in code, and then track the user's mouse and keyboard activity to change the ListView selection in code. Or, if the MessageDlg says to cancel, then have OnChanging block all subsequent changes for X amount of time. Etc.
-
Pointer casting with NativeUint
Remy Lebeau replied to duzzell's topic in Algorithms, Data Structures and Class Design
What is FElements declared as? If you can enable pointer arithmetic on it (if not already), then the code becomes much much simpler: Result := @FElements[Index]; // or: Result := FElements+Index; Even if the ElementSize is dynamic at runtime, you can utilize a PByte cast instead of a NativeUInt cast: Result := PByte(FElements)+(Index*ElementSize); -
This is not a good design choice. I do not recommend implicitly updating the current list item when selecting another item. I would instead suggest that you use a separate button or other UI element that the user must activate explicitly to commit any changes to the current list item. If they select another list item without committing changes, then the changes are simply discarded when the new list item's details are displayed.
-
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
Fair enough. In that case, SendBuf() would return -1 without raising an exception on all socket errors, so you would have to call WSAGetLastError() manually after SendBuf() returns. However, on a real error, SendBuf() will call closesocket() before exiting, so you might lose the error code. Basically, yes. There is no point in wasting overhead adding data to the Tx buffer if you know it's never going to be sent. If the listening socket fails to bind/open the port, an ESocketError exception is raised unless SocketErrorProc is assigned (which it is not by default). So, again, I would suggest using a try..except block. Your aversion to using exception handling is actually complicating your usage rather than simplifying it. TServerSocket/TClientSocket use WSAAsyncSelect() to trigger events for non-blocking sockets. The documentation for WSAAsyncSelect() explains this behavior: In general, when you receive an OnClientRead event, it is best to simply read everything that is currently available in the socket in one go, even if that means buffering it somewhere. But, if you were to call ReceiveBuf() in a loop, that is not the end of the world, you just may receive additional OnClientRead events with no data available, which is not a problem as long as you are handling WSAEWOULDBLOCK correctly while reading. -
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
If you need the error code, you would have to get it from either the OnClientError event or by calling WSAGetLastError() directly, yes. However, it is a bit simpler than that, because SendBuf() returns 0 on a graceful disconnect and -1 on failure, and if it fails and the error code is not WSAEWOULDBLOCK then it will raise an ESocketError exception unless an OnClientError handler sets its ErrorCode parameter to 0. So, that means that by default, SendBuf() will return -1 only for WSAEWOULDBLOCK, and will raise an exception otherwise. So, be prepared to catch that exception and act accordingly. -
It sounds like you simply added the code manually to the Form's class in code, but you did not actually hook it up to the Form's UI. Go into the Object Inspector and make sure the OnCloseQuery event is actually assigned this procedure. And next time, don't add event handlers manually to your code, go into the Object Inspector and double-click on the desired event and let the IDE auto-generate the handler for you, then you can fill in its logic as needed.
-
Access Violation and Invalid Pointer exception while accessing files from web pages
Remy Lebeau replied to sp0987's topic in Indy
First, Delphi doesn't allow you to initialize variables in a 'var' block. And second, even if you could, your variable is initialized to nil and not assigned to something else, so the Assigned() checked will ALWAYS return False. But, even if the above was working ok... TIdHTTPServer is a multi-threaded component, its events are fired in worker threads, and you can't access UI controls directly in worker threads. So, if you are calling Log() in your server's events, then the above code can easily be crashing. You MUST not access UI controls from outside of the main UI thread. Use TThread.Synchronize() or equivalent to run this code block in the UI thread, using a mutex lock is not sufficient. Try this: Procedure GLog(Msg: String); begin if Assigned(gLogger) and Assigned(gLogger.Memo) then begin TThread.Synchronize(nil, procedure begin if Assigned(gLogger) and Assigned(gLogger.Memo) then begin gLogger.Memo.Lines.BeginUpdate; try while gLogger.Memo.Lines.Count > 600 do gLogger.Memo.Lines.Delete(0); gLogger.Memo.Lines.Add(Msg); finally gLogger.Memo.Lines.EndUpdate; end; gLogger.Memo.Perform(EM_SCROLLCARET, 0, 0); end; end); end; _Log(Msg); end; -
Handling TCP send buffer overflow the right way
Remy Lebeau replied to Dmitry Onoshko's topic in Network, Cloud and Web
That will complicate your protocol, but it can work if your protocol design allows for it. Such as by breaking up the file data into small chunks and send each chunk as an individual packet, and each packet carries a header specifying its type and size so the client can differentiate one packet from another. Make sure to NEVER overlap packets - don't send a new packet until a previous packet is done. That requires putting a lock on the socket if you send packets from multiple threads. Assuming your protocol doesn't require the client to explicitly ack everything it receives (most protocols don't), then the only way to detect that condition is when SendBuf() fails with a WSAEWOULDBLOCK error because the socket's outgoing buffer filled up due to the client not removing data from that buffer. Until that buffer fills up, any data "sent" is simply buffered locally by the socket without error and is picked up by the OS behind the scenes, so you won't know if the data was actually transmitted and received until an error occurs. You have to check the socket error code to find out why SendBuf() failed. Any error code other than WSAEWOULDBLOCK should be treated as a fatal error so close the connection. Otherwise, cache all current unsent data and future data somewhere until the OnClientWrite event tells you that the socket can accept data again, then you can try re-sending your cached data, removing any bytes that are accepted by SendBuf() without error, until either the cache is empty or a new error occurs. Yes, exactly. The logic is actually really simple: When sending a new packet, if the cache is not empty then append the entire packet to the end of the cache and don't attempt to send it yet. Otherwise, send as many bytes as possible for the packet. If SendBuf() fails, stop sending the packet. If the error is WSAEWOULDBLOCK then save the remaining unsent bytes to the end of the cache. In the OnClientWrite event, if the cache is not empty then send as many bytes as possible from the cache, removing any bytes that SendBuf() accepts. If SendBuf() fails for any reason (you don't need to look at the error code here), stop sending the cache and leave the remaining unsent bytes in it for a future event to re-try. I've posted examples of this many times in the past in various forums. I use the TCustomWinSocket.Data property to hold the cache (usually using a TMemoryStream), that way the cache follows the socket as its passed around. But yes, the cache could grow very large if you don't put a max cap or timeout on it. If you try to send a lot of data to an unresponsive client, or if data stays in the cache for a long period of time, then assume the client is dead (even if the OS hasn't reported it yet) and close the connection. On a similar note, the OnClientRead event can do similar caching. It should read all available bytes from the socket and put them into a separate cache, then you can remove and process only complete packets from the cache, waiting for future events to finish incomplete packets. Packets can span across multiple OnClientRead events per socket. -
Access Violation and Invalid Pointer exception while accessing files from web pages
Remy Lebeau replied to sp0987's topic in Indy
What about the code for Log()? Since that is the function you were complaining about earlier. Which function exactly is the exception actually being raised in?