[rcore] Process manipulation API for Windows and POSIX-compliant platforms#5822
[rcore] Process manipulation API for Windows and POSIX-compliant platforms#5822konakona418 wants to merge 14 commits into
Conversation
d4d050c to
5472b6c
Compare
|
There have been a few updates, so I will squash them together |
e51f360 to
24b36ef
Compare
|
@konakona418 thanks for working on this addition, I think it still requires some work but it's a good start... Windows support should definitely be there... |
|
Hi @raysan5 |
e7bab26 to
9b21238
Compare
|
Hello @raysan5 I just implemented the API for Windows except for And about this patch some questions remain:
Please let me know if more adjustments are required! |
orcmid
left a comment
There was a problem hiding this comment.
As difficult as it might appear, shouldn't these be handled at the platform level?
Perhaps there is no meaningful POSIX distinction with respect to platforms?
|
Hi @orcmid Currently I followed the existing method applied to a few similar functions in rcore.c, which handles platforms via |
|
Hello @raysan5 I finished working on process suspension and resuming just now, in which i dynamically loaded and used the ntdll internal functions But still, I'm not sure whether dynamic loading Nt* functions is a good idea for raylib or not. What's your opinion? |
d472b7e to
fc3cac5
Compare
|
SDL3 also has a process management API that might be good for reference |
|
@konakona418 thank you very much for all the work put on this new feature! It looks great to me but I need some time to review it properly, I also want to check other libraries approaches for reference. |
|
@raysan5 thanks for the feedback! Please take your time to review it. If any part needs to be changed, please tell me and I'd be happy to update this PR! |
|
@konakona418 I've been reviewing other libraries/languages handling of processes, specifically: SDL3, Python, Rust, .NET, Qt and Glib. I also asked AI for some ideas and approaches. Some concerns about the current API and improvements to be more aligned with raylib style and simplicity:
What are your thoughts? |
|
Hi @raysan5 Thanks for the code review!
|
| PROCESS_STATE_FINISHED // Process has exited | ||
| } ProcessState; | ||
|
|
||
| // Process information for CheckProcess() function |
There was a problem hiding this comment.
Mmmmh... now Process spawns into two different structs + 1 enum, those are the kind of additions I try to minimize... but let's keep it for now...
There was a problem hiding this comment.
One thing I'm worried about this is that the intention of the naming is kind of unclear...
What's your opinion?
|
|
||
| // Process execution functions | ||
| RLAPI Process InitProcess(const char *command, char *const args[]); // Initialize a new process, returns a Process struct | ||
| RLAPI ProcessInfo CheckProcess(Process process); // Check if a process is still running |
There was a problem hiding this comment.
If this is the new approach, probably a GetProcessInfo() would be more clear
| RLAPI Process InitProcess(const char *command, char *const args[]); // Initialize a new process, returns a Process struct | ||
| RLAPI ProcessInfo CheckProcess(Process process); // Check if a process is still running | ||
| RLAPI int WaitProcess(Process process); // Wait for process to finish, returns exit code, blocking | ||
| RLAPI const char *ReadProcessOutput(Process process, int *length); // Read process output as raw buffer (not null-terminated), returns pointer to a static buffer, so data must be copied before next call to ReadProcessOutput() |
There was a problem hiding this comment.
Could we just return a NULL terminated string so we can avoid length parameter? As per my understanding, it can just fills the internal buffer until next ReadProcessOutput() is called (updating it on the request), right? Or the full output is returned on every call?
There was a problem hiding this comment.
Yes, the current implementation behaves as a stream API, which means the results won't be accumulated. So users need to allocate their own buffer, and copy the content in the returned read-only buffer to their buffer based on length, otherwise previous outputs will be lost.
Other approaches include bool ReadProcessOutput(Process process, void *buf, int bufSize, int *length), which takes a buffer created by user and can prevent extra copy or char *ReadProcessOutput(Process process, int *length) which allocates a buffer dynamically which needs to be MemFree(), but the current implementation just resembles a few text manipulation functions which uses a static buffer as well...
As for the null-terminated string issue, my thought is, for normal ASCII char streams this is sufficient but there are other cases where this approach might cause problems, for example, UTF-8 strings. The pipe read operation basically treats stdout/stderr as a byte stream and does not respect the codepoint boundaries of UTF-8 strings, and inserting NULL terminators may break the strings and cause strange outcomes...
Another case is that users might want to pass raw byte data like large binary objects (though less common), and here's another example. Currently GetClipboardImage() function is not supported on Wayland, and to read clipboard image users can use a utility program called wl-clipboard which contains such features with process API, and read the binary image data directly. In this case, the stream itself may contain NULL bytes which does not stands for the terminator of something (e.g. color black 0 0 0), and adding NULL can also break the struct of the binary object.
There was a problem hiding this comment.
And I'm a bit concerned that maybe static buffer is not a good idea for process API, because this might lead to some confusion easily, for example:
int l1, l2;
TraceLog(LOG_INFO, "p1: %s; p2: %s", ReadProcessOutput(p1, &l1), ReadProcessOutput(p2, &l2));And this will print identical outputs for p1 and p2 which is read from p2's IO pipe.
What do you think?
There was a problem hiding this comment.
Mmmh... good catch... but otherwise a buffer must be allocated at some point, for example at InitProcess(), inside p1.processData and just fill and return it by ReadProcessOutput() or let the user manage their own buffer with this mentioned limitation (reused static buffer). Actually most Text*() functions deal with same issue and in my experience it has never been a big issue if clearly stated that limitation.
There was a problem hiding this comment.
About the *length, I still prefer to avoid it when dealing/expecting text strings, for library consistency. UTF-8 shouldn't be a problem for NULL terminator.
| //---------------------------------------------------------------------------------- | ||
|
|
||
| #if defined(_WIN32) | ||
| struct ProcessInternal { |
There was a problem hiding this comment.
I see the need for the ProcessInternal... and it's also platform dependant... it needs a bit more thought... AudioStream also uses and internal struct... 🤔
|
@konakona418 Added some further reviews, my comments on mentioned points:
Already reviewed current approach, I think
We can avoid that |
|
If they all use a shared buffer, then you can't start multiple processes at once with that API? Sounds like a major limitation. |
|
@Peter0x44 Actually, you can. That's because the data will only be read to the buffer when |
Uh, wait, with concurrent processes? And with no semaphore or other guarding? How do you get atomicity between the call of the function and copying of the data? |
|
It assumes invocations to
|
I noticed that in the wishlist for raylib7.0 (#5710) there are plans for process manipulation APIs, so I wrote the following functions for posix-compliant platforms:
InitProcessto launch a new processCheckProcesswhich checks whether a process is still runningPauseProcessto pause executionCloseProcessto terminate a processAnd a counterpart for
PauseProcess:ResumeProcessPlease let me know if any changes need to be made!