sheredom / subprocess.h Goto Github PK
View Code? Open in Web Editor NEW๐ single header process launching solution for C and C++
License: The Unlicense
๐ single header process launching solution for C and C++
License: The Unlicense
Hello.
As you pointed out in the documentation, the subprocess_read_stdout()
function is blocking when there is no data available to read.
I need to be able to read and write to a subprocess before joining it, but my problem is that I need the read operation to be asynchronous. Is there an easy solution for changing this behavior?
Working with character encoding on Windows is really annoying.
Passing UTF-8 characters on the command line using CreateProcessA
seems to be impossible. While local code pages seem to be able to handle special characters such as Chinese and Japanese, they don't seem to be able to do that for emoji.
In my case, the way I use it is to convert your commandLineCombined
to UTF16LE characters and then call CreateProcessW
. My original input was UTF8 characters, and this modification seems to handle UTF8 characters properly.
What do you think of this? Thanks.
Yeah. I'm also speechless. Apparently on Windows you MUSTN'T quote too little or too much.
#include <stdio.h>
#include <stddef.h>
#include <malloc.h>
#include "subprocess.h"
int main() {
const char *cmd[] = { "cmd", "/c", "echo hello", NULL };
struct subprocess_s sub;
int r = subprocess_create(cmd, 0, &sub);
FILE *p = subprocess_stderr(&sub);
char bruh[32];
while (fgets(bruh, 32, p)) {
printf("%s\n", bruh);
}
int pr;
subprocess_join(&sub, &pr);
return 0;
}
The code is a bit messy, but its obvious this is your typical run of the mill echo
example adapted for Windows.
The only problem is it doesn't work.
Apparently cmd.exe
doesn't like its flags to be quoted. This library quotes every argument, so something from the above example would produce "cmd" "/c" "echo hello world"
. Perfectly fine for you and me (and sh), but for cmd... it interpreted "/c" "echo hello world"
as /c "\"echo hello world"
! Suddenly there's an extra quote there. The solution is apparently
const char *cmd[] = { "cmd", "/c echo hello", NULL };
This entire thing is just mind-boggling.
Apparently the gold standard's force
parameter is actually important! The only way I see to fix this problem is to not quote unescaped parameter, but I am not entirely sure (also pretty hard to do).
EDIT: some potential fixes:
454a455
> int need_quoting;
625,626c626,631
< // For the ' ' and two '"' between items and trailing '\0'
< len += 3;
---
> // For the trailing \0
> len++;
>
> // Quote the argument if it has space or tabs in it
> if (strpbrk(commandLine[i], "\t\v ") != NULL)
> len += 2;
659c664,668
< commandLineCombined[len++] = '"';
---
>
> need_quoting = strpbrk(commandLine[i], "\t\v ") != NULL;
> if (need_quoting) {
> commandLineCombined[len++] = '"';
> }
678c687,690
< commandLineCombined[len++] = '"';
---
>
> if (need_quoting) {
> commandLineCombined[len++] = '"';
> }
Hello,
Thanks for this useful library!
I encountered a problem when calling subprocess_join: it cannot detect whether the process failed.
The following lines taken from the code show the reason:
if (out_return_code) {
if (WIFEXITED(status)) {
*out_return_code = WEXITSTATUS(status);
} else {
*out_return_code = 0 // process did not exit normally, but we return 0 as exit status, anyway (!)
}
}
Is there another way to check for process failures?
Thanks!
Hi I tried a sample program with cat command but was not able to get required output. The code just hangs at first call to fgets. Am I doing something wrong?
#include <stdio.h>
#include <string.h>
#include "subprocess.h"
int main()
{
const char *command_line[] = {"/usr/bin/cat", NULL};
struct subprocess_s subprocess;
int result = subprocess_create(command_line, 0, &subprocess);
if (0 != result) {
puts("an error occurred!");
return 1;
}
FILE* p_stdout = subprocess_stdout(&subprocess);
FILE* p_stdin = subprocess_stdin(&subprocess);
FILE* p_stderr = subprocess_stderr(&subprocess);
char msg[] = "test";
char resp[2000] = {0};
puts(msg);
fputs(msg, p_stdin);
memset(resp, 0, 2000);
fgets(resp, 4, p_stdout);
puts(resp);
puts(msg);
fputs(msg, p_stdin);
memset(resp, 0, 2000);
fgets(resp, 4, p_stdout);
puts(resp);
getchar();
return 0;
}
By default subprocess.h
will not work on MinGW because of _MSC_VER
.
I managed to get it to compile (and at least work (create, read stderr and join)) by:
_alloca
) and instead #include <malloc.h>
-D_MSC_VER=1920
(this should be replaced by #ifdef _WIN32
and similiar-D_DLL
as gcc doesn't like it when it tries to compare to something undefined.I have the following go code that I'm converting to c++
javaPath := getJavaPath()
cmd := exec.Command(javaPath, "-m", "pvphero/com.pvphero.PvpHero", "-live", "-hidden")
if dev {
cmd = exec.Command(javaPath, "-p", "E:\\Sync\\Insignia\\hd-client\\out\\production\\classes;E:\\Sync\\Insignia\\hd-client\\out\\production\\resources", "-m", "pvphero/com.pvphero.PvpHero", "-hidden", "-live")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
fmt.Println("Launching client", cmd)
_ = cmd.Start()
I was wondering is there any way to redirect the stdout of my child process to share the same one as its parent process? Or will I just need to make another thread that loops/polls for stdout messages? I tried looking through the tests you have on here but couldn't find any similar use case.
PS. Your library is awesome.
Is it possible to make stdout and stderr be the same FILE*
? This can be
useful when for getting the full context of a processes failure, or if the
intention is to show its output in order as if in a terminal.
Hallo,
I was very happy to find this lib.
But it just doesn't work.
I tried it with C and C++ in MVS 2017.
At the example
const char *command_line[] = { "echo", "\"Hello, world!\"", NULL };
struct process_s process;
int result = process_create(command_line, 0, &process);
if (0 != result) {
// an error occurred!
}
I always get the result -1
If I start nodejs instead of echo, a result of 0 comes but the process does not start.
I've got a really weird bug that I cant seem to figure out. I'm using your library to spawn a child java process for my game client.
The problem is when I start the client using subprocess.h, it does not have any internet access at all and throws a bunch of exceptions.
java.net.UnknownHostException: No such host is known (js5.pvpherodev.com)
at java.base/java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method)
at java.base/java.net.InetAddress$PlatformNameService.lookupAllHostAddr(InetAddress.java:932)
at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1505)
at java.base/java.net.InetAddress$NameServiceAddresses.get(InetAddress.java:851)
at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:1495)
at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1354)
at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1288)
at java.base/java.net.InetAddress.getByName(InetAddress.java:1238)
at pvphero/com.pvphero.client.Signlink.run(Signlink.java:165)
at java.base/java.lang.Thread.run(Thread.java:832)
Here is the C++ code which spawns that process
void Updater::LaunchClient() {
auto dev = true;
struct subprocess_s process{};
int result = -1;
if (dev) {
const char *command_line[] = {"java.exe", "-p",
R"(E:\Sync\Insignia\hd-client\out\production\classes;E:\Sync\Insignia\hd-client\out\production\resources)",
"-m", "pvphero/com.pvphero.PvpHero", /*"-hidden",*/ "-live", NULL};
result = subprocess_create(command_line, subprocess_option_combined_stdout_stderr, &process);
} else {
const char *command_line[] = {getJavaPath().c_str(), "-m", "pvphero/com.pvphero.PvpHero",
"-live", /*"-hidden",*/ NULL};
result = subprocess_create(command_line, 0, &process);
}
if (0 != result) {
std::cout << "Failed" << std::endl;
}
}
If I run my client from command line with the exact same params as in the C++ code it works.
Screenshot of running the process spawned manually from command line (works with internet access):
https://dl.dropboxusercontent.com/s/1566obcus51qxsy/ProcessHacker_iHNItCSlU1.png
Screenshot of the running process spawned via C++/subprocess.h (no internet access and fails to resolve domain):
https://dl.dropboxusercontent.com/s/tazv5eaykbeomvj/ProcessHacker_8pD2audHMF.png
Any idea what could be causing this?
@nburrus curious on your take on this - your test case custom_search_path
sets the PATH
in a custom enivornment, and then calls subprocess_create_ex
with this PATH
and subprocess_option_search_user_path
- which only works if you use the PATH
provided in environment
. I'm not sure if this is the best approach or not, trying to get my head around it at present. I don't think this would work on windows because we don't and won't modify the PATH
, and it is affecting me trying to move over to posix_spawn
.
Any thoughts? At present my thinking is I'll rework the test so that you cannot use a PATH
specified in a custom environment
.
Hey,
I know "process.h" is a really compelling name, but for me at least having such a generic name is a bit of a pain. Including this file it conflicts with: c:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\ucrt\process.h
, which vs2017 picks up first when including process.h
This wouldn't be a problem if I were just adding it to my project normally, as I could just put it in a folder, but since I'm using a package manager and grab your repo - my only easy option is to add your repository as an include directory. Because you don't have a dist/
or include/
directory or anything like that, I don't have a way to be selective about including the exact file I want to.
I'm going to work around this by skipping the package manager for this dependency, but I'd like to make the suggestion that maybe you could have some branded name? Like Sean Barrett's STB? Would be pretty handy for avoiding conflicts!
my 2 cents.
thanks ๐
I have the need in my c++ program of modifying the command line args given certain conditions. This favors using a vector<std::string>
instead of the current approach.
#include <subprocess.h>
#include <initializer_list>
#include <vector>
char *convert(const std::string & s)
{
char *pc = new char[s.size()+1];
std::strcpy(pc, s.c_str());
return pc;
}
int cmd(std::initializer_list<std::string> args)
{
std::vector<char*> vc;
std::transform(args.begin(), args.end(), std::back_inserter(vc), convert);
// vc.push_back(NULL);
struct subprocess_s subprocess;
int result = subprocess_create(&vc[0], 0, &subprocess);
if (0 != result) {
printf("error occurred.");
};
std::cout << "vc.size(): " << vc.size() << std::endl;
for ( size_t i = 0 ; i < vc.size() ; i++ )
std::cout << vc[i] << std::endl;
for ( size_t i = 0 ; i < vc.size() ; i++ )
delete [] vc[i];
return 0;
}
The problem I have is that if I push_back(NULL)
to add a NULL
sentinel I get an error and it doesn't work as expected.. Do I have to modify subprocess.h
itself with this approach and just drop the sentinel requirement or is there a way of making something like the above work?
The changes introduced here de3cbec produce compilation errors on these two platforms: https://github.com/aardappel/lobster/actions/runs/2278815862
Now I am totally fine #ifdeffing support out for those 2 platforms, since it doesn't make any sense to spawn processes there anyway. But the curious thing is that the old fork code DID compile on those platforms.
So it is more of a question if you want to #ifdef it in your library for maximum modularity, or if you want users to #ifdef it :) I don't mind either way. Feel free to close if WillNotFix.
I know another repo: reproc
for a long time it's the only available library to handle the process create/read/write.
Now seeems we have a new choice.
Did the author check any feature not supported by reproc but supported here?
Thanks,
AH
I was having an issue where starting a process via you lib was failing in a strange way. After debugging I realized that there is a subtle bug in how you call CreateProcessA. By default you do this:
char *used_environment = SUBPROCESS_NULL;
Which is then fed into the CreateProcessFunction, except instead of it receiving a nullptr, it receives a valid ptr to an empty string. This breaks the semantics so ends up running the process with an empty environment instead of using the inherited environment.
I hacked it as follows just to get it to work, but I'm not entirely sure what the idiomatic c way to that is (I'm mostly a c++ prog).
I did this at the CreateProcessA callsite:
( used_environment[0] == SUBPROCESS_NULL ) ? SUBPROCESS_NULL : used_environment
parent process may be busy and only want to check the status of subprocess occasionally
The reading stdout example in the README didn't work for us; stdout was just empty.
However, when we used "/bin/echo" instead of "echo" for the command, it worked!
I imagine changing the example from echo to /bin/echo would make it not work on windows though, so I'm not sure the wisest course here.
I want to save the output text of an executed command ...
This may be a command specific problem but commands that need to be run as root fail silently.
Example:
int subprocess_join_wrapper(struct subprocess_s & subproc) {
int process_return;
int result = subprocess_join(&subproc, &process_return);
std::cout << "STDOUT" << std::endl;
FILE* p_stdout = subprocess_stdout(&subproc);
char command_output[32];
fgets(command_output, 32, p_stdout);
std::cout << "\n";
if (0 != result) {
std::cout << "STDERR" << std::endl;
FILE* p_stderr = subprocess_stderr(&subproc);
fgets(command_output, 32, p_stderr);
std::cout << "\n";
}
return result;
}
void run_command(const char *command[], struct subprocess_s & subprocess ) {
std::cout << "Running Command: ";
print_command(command);
std::cout << "Process Created: " << subprocess_create(command, 0, &subprocess) << std::endl;
int join_result = subprocess_join_wrapper(subprocess);
std::cout << "Process Joined: " << join_result << std::endl;
}
void LinkAdd(std::string linkName) {
struct subprocess_s subprocess{};
const char *deviceCreation[] = {"/bin/ip", "link", "add", "dev", linkName, "type", "bridge", nullptr};
std::cout << "Adding link for " << linkName << "\n";
run_command(deviceCreation, subprocess);
std::cout << "Finished Adding link for " << linkName << "\n";
}
int main() {
LinkAdd("newLink");
}
If the executable isnt run as root the output is:
Adding link for newLink
Running Command: /bin/ip link add dev newLink type bridge
Process Created: 0
STDOUT
STDERR
Process Joined: 0
While running the same command on the command line gives
RTNETLINK answers: Operation not permitted
I would like to output "RTNETLINK answers: Operation not permitted" or I would like to capture a failure when a command that needs to be run as root is not run as root.
First of all, thank you for this project, it's the most convenient C/C++ library I've seen so far!
This question is out of my personal preference.
Sometimes I like to use a cmd like this:
const char* command_line[] = {
"foo \"bar\" \"\\\\\"\" ",
NULL
};
And this is the current msvc implementation in the project:
// Combine commandLine together into a single string
len = 0;
for (i = 0; commandLine[i]; i++) {
// For the ' ' and two '"' between items and trailing '\0'
len += 3;
for (j = 0; '\0' != commandLine[i][j]; j++) {
switch (commandLine[i][j]) {
default:
break;
case '\\':
if (commandLine[i][j + 1] == '"') {
len++;
}
break;
case '"':
len++;
break;
}
len++;
}
}
Well, I didn't look closely at the code, but when I had only one command, I observed the wrong escape.
As mentioned above, this is likely to be a complex command line if there is only one command. It is better to follow the user's input and just take the command line without modifying it.
What do you think about this? Thanks!
seems like it is, should have looked at the code before creating an issue, sorry
When launching large numbers of subprocesses on Windows, eventually we get a failure to launch another subprocess and subsequent errors from our code indicating file descriptor resource starvation - it can't open a new log file to report errors.
C code is very rarely appropriate as C++ code. It is non-idiomatic, resource-unsafe, and difficult to combine with common C++ libraries and other idiomatic code.
I'd say it is "also usable in C++", but not beyond that.
Hi, first of all thanks for this library, I've been looking for something nice like this for a while. I'm having an issue trying to unzip a file using 7z and I'm not sure how to fix it. I think it has to do with the way subprocess wraps things in quotes. I was hoping something like this would work:
const char* command_line[] = { "7za.exe", "x C:/myfile.zip -oC:/mydir/ -y", nullptr };
However the final command line ends up being something like:
"7za.exe" "x C:/myfile.zip -oC:/mydir/ -y"
and 7zip interprets that second part as a single argument. Is there a way for me to directly pass the arguments without adding the quotes? Would that even be the correct solution?
Thanks!
Hi,
Thanks for the great lib, it's saved me a lot of pain. Would it be able to add support for the 'CREATE_NO_WINDOW' creation flag for the windows CreateProcess call?
I've hacked it locally since I dont ever want the process windows to show up but it might be useful for others as well.
The subprocess_create_ex()
function just has return -1
in case of an error, without properly closing already open file descriptors.
Example:
Lines 876 to 880 in 7e59b69
In my test, trying to call this function with a non-existing command results in 6 more new FDs each time.
In RHEL 7.3, when using subprocess_create
with a 0
for the environment option, if I don't specify the full path of the binary, the call to execvp fails and returns 255.
const char * command_line[] = { "sleep", "10", NULL };
struct subprocess_s subprocess;
int result = subprocess_create(command_line, 0, &subprocess);
if (0 != result)
{
std::cerr << "Error with subprocess" << std::endl;
}
int retcode = 0;
result = subprocess_join(&subprocess, &retcode);
if (0 != result)
{
std::cerr << "Error joining subprocess" << std::endl;
}
std::cout << "Ret code: " << retcode << std::endl; // This prints 255
However, if I use any environment option, like subprocess_option_inherit_environment
, your lib uses execve instead, which does need to have the full path. Here's a better explanation for that behavior.
To my understanding, the behavior should be consistent in both cases.
Readme informs to create a process like so:
const char command_line[] = {"echo", "\"Hello, world!\"", NULL};
struct process_s process;
int result = process_create(command_line, &process);
if (0 != result) {
// an error occurred!
}
however process_create takes an integer bitfield of process_option_e
.
The code also has const char command_line[] = {"echo", "\"Hello, world!\"", NULL};
, which isn't valid at least on msvc. Needed to be changed to const char* command_line[] = {"echo", "\"Hello, world!\"", NULL};
(note the type)
After making this change and providing process_option_e::process_option_combined_stdout_stderr
and 0
, both times this example code gives me a -1 result, and the exit code is -1.
Hello.
A question:
For a complete ready to bake example see final code block. Short story, using the handle returned from subprocess_stdin(&process);
,
a call like int ret = fputs(text.c_str(), p_stdin);
always returns 0, as does the subsequent subprocess_read_stdout()
.
Any thoughts on this? Have I missed something in the documentation?
And a suggestion:
subprocess_read_stdout()
blocks. On Windows, at least, it can be made non-blocking by changing the last argument to 0. Might this not be made an optional argument?
Many thanks.
// Means we've got an async read!
if (error == errorIoPending) {
if (!GetOverlappedResult(handle,
SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped),
&bytes_read, 0)) { // changed from 1 (wait) to 0 (don't wait)
const unsigned long errorIoIncomplete = 996;
const unsigned long errorHandleEOF = 38;
error = GetLastError();
Built with cl main.cpp
in a VS2022 command shell.
#include <iostream>
#include <sstream>
#include "subprocess.h"
int main(int argc,char* argv[])
{
int ret = -1;
try
{
struct subprocess_s process { 0 };
// run a basic shell
//const char* command_line[] = { "cmd.exe", "/K", nullptr};
//const char* command_line[] = { "cmd.exe", "/K r:\\apps\\mingw\\msys\\1.0\\bin\\ls.exe -als", nullptr};
const char* command_line[] = { "cmd.exe", "/K ls.exe -als", nullptr}; // this correctly says ls.exe cannot be found so is alive
//const char* command_line[] = { "cmd.exe", "/K", "echo %path%", nullptr };
int result = subprocess_create(command_line,
subprocess_option_combined_stdout_stderr |
subprocess_option_no_window |
subprocess_option_enable_async,
&process);
if (0 != result) {
// an error occurred!
}
// hack for the moment.
_sleep(1000);
char buffer[255];
int chars = 0;
do
{
// modified to NOT block
chars = subprocess_read_stdout(&process, buffer, 254);
if (chars)
{
buffer[chars] = '\0';
std::cout << buffer;
}
} while (chars > 0);
while (true)
{
std::string text;
std::getline(std::cin, text);
FILE* p_stdin = subprocess_stdin(&process);
int ret = fputs(text.c_str(), p_stdin);
std::cout << "Wrote " << ret << std::endl;
if (ret)
{
do
{
chars = subprocess_read_stdout(&process, buffer, 254);
if (chars)
{
buffer[chars] = '\0';
std::cout << buffer;
}
} while (chars > 0);
}
}
//
result = subprocess_destroy(&process);
if (0 != result) {
// an error occurred!
}
ret = 0;
}
catch (std::exception& ex)
{
std::cout << "exception: " << ex.what();
}
catch (...)
{
std::cout << "unknown exception.";
}
return ret;
}
Hey there, great little library. Thank you so much for releasing it!
There's something missing from the header's docs that would be immensely helpful for consumers: lifetime requirements of parameters.
For example, subprocess_create_ex
's environment array parameter - does the memory pointed to by the pointer value need to out-live the subprocess's? Or can that memory be freed directly after the call returns?
Adding that sort of information would be really helpful. :)
Thank you for sharing this nice library.
I was using it in one of my project but I stumbled in a problem on linux about PATH resolution not done.
Code snippet is:
const char *command_line[] = {"xrdb", "-query", NULL};
struct subprocess_s process;
int result = subprocess_create(command_line, 0, &process);
but it fails, it seems the full path is needed. The only way I found to make it work was to provide full path and using the inherit environment option.
Now this is confusing to me. Looking at the documentation my understanding was that the full path is not needed. I addition the documentation says absolutely nothing about that.
There is an easy way to have PATH resolution for the binary out-of-the-box ?
Shouldn't the documentation says something about the PATH ?
PS: I found a related issue that lead to the PR that introduced create_subprocess_ex but I failed to understand the relation with the PATH issue I had.
previously im doing this to launch a C++ bin in a separate terminal in linux:
std::string cmd_linux = fmt::format("$TERMINAL -e \"./{:s}\"", filename);
FILE* p = popen(cmd_linux.c_str(), "r");
if (p == NULL)
return RES_FAIL;
pclose(p);
but converting it to using subprocess works in that it doesnt throw any error, but there's nothing ran.
const char* cmd_linux[] = {filename.c_str(), NULL};
//const char* cmd_linux[] = {"$TERMINAL", "-e", filename.c_str(), NULL}; //doesnt even work
struct subprocess_s subprocess;
int result = subprocess_create(cmd_linux, subprocess_option_inherit_environment, &subprocess);
if (result != 0)
return RES_FAIL;
the file to be ran is in the same directory as the program. The program to run has a getchar()
at then end so it wont close automatically
Excellent stuff. thanks.
I'm getting linkage errors in C++ code that uses process.h; the predeclarations of various internal Windows API calls doesn't match what's eventually defined by windows.h, if both headers are included:
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\handleapi.h(39,6): error C2732: linkage specification contradicts earlier specification for 'CloseHandle'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\handleapi.h(38): message : see declaration of 'CloseHandle'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\handleapi.h(87,6): error C2732: linkage specification contradicts earlier specification for 'SetHandleInformation'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\handleapi.h(84): message : see declaration of 'SetHandleInformation'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\namedpipeapi.h(34,6): error C2732: linkage specification contradicts earlier specification for 'CreatePipe'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\namedpipeapi.h(30): message : see declaration of 'CreatePipe'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\synchapi.h(382,6): error C2732: linkage specification contradicts earlier specification for 'WaitForSingleObject'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\synchapi.h(380): message : see declaration of 'WaitForSingleObject'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\processthreadsapi.h(154,6): error C2732: linkage specification contradicts earlier specification for 'GetExitCodeProcess'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\processthreadsapi.h(152): message : see declaration of 'GetExitCodeProcess'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\processthreadsapi.h(371,6): error C2732: linkage specification contradicts earlier specification for 'CreateProcessA'
1>C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um\processthreadsapi.h(361): message : see declaration of 'CreateProcessA'
You can reproduce the problem by adding #include <windows.h>
at the end of test.cpp.
The error starts occurring in 5bdac3a; I think it has something to do with the narrowing of the extern "C"
scope, which no longer includes the predeclarations. They're always defined as extern "C"
in windows.h; moving them inside an extern "C"
scope fixes the linkage error. Is there a reason they were moved outside that scope?
What it says on the tin. Essentially inserting a chdir
before the exec
.
would be nice if you could pass custom environments to the processes by passing a list of environment variable strings or NULL to inherit the current processes environment.
We are currently validating various libs for a simple process handling in C++ (came from Boost.Process...) and are quite excited about subprocess.h
However, when doing some basic tests we might have found a part of the lib, which could be improved to ease troubleshooting for the user/developer.
E. g. when somebody gives an invalid command (e.g. lss) no further information is given. In contrast, other libs give some hint in the form of execv's perror message or a dedicated error code. In case you are interested i appended you our patch file. With this update it is possible to check the child's stdout/stderr for the error message or simply provide it to the user.
If you have any further questions please feel free to ask.
Do you have any plans on supporting an extra option to start a detached child process?
This would be really useful (ie. Qt QProcess:startDetached)
If a subprocess outputs large amount of data, it deadlocks both parent and subprocess due to pipe blocking:
#include <stdlib.h>
#include <stdio.h>
#include "subprocess.h"
int main() {
const char *command_line[] = {"dd", "if=/dev/zero", "bs=1k", "count=65", NULL};
struct subprocess_s process;
int result = subprocess_create(command_line, subprocess_option_search_user_path, &process);
if (result) {
fprintf(stderr, "Failed to create subprocess: %d\n", result);
return 1;
}
int proc_return;
result = subprocess_join(&process, &proc_return);
if (result) {
fprintf(stderr, "Failed to join subprocess\n");
return 1;
}
printf("Subprocess returned %d\n", proc_return);
result = subprocess_destroy(&process);
if (result) {
fprintf(stderr, "Failed to join subprocess\n");
return 1;
}
return 0;
}
Notice that subprocess dd
writes 65k data to stdout, which is greater than Linux's default pipe buffer size, thus blocking the child. However, since subprocess_read_stdout
must be used after joining, the parent process cannot progress either, as it can neither drain the pipe nor wait for child to finish.
what is the right way to support to current directory while creating subprocess?
if (!CreateProcessA(
SUBPROCESS_NULL,
commandLineCombined, // command line
SUBPROCESS_NULL, // process security attributes
SUBPROCESS_NULL, // primary thread security attributes
1, // handles are inherited
flags, // creation flags
used_environment, // used environment
SUBPROCESS_NULL, // use parent's current directory
SUBPROCESS_PTR_CAST(LPSTARTUPINFOA,
&startInfo), // STARTUPINFO pointer
SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) {
return -1;
}
Hey,
It seems I get a deadlock if the process I launch has a relatively long output (works at 300 characters, but doesn't work at 450 or more it seems).
What I do is basically:
subprocess_create(...);
subprocess_join(...);
while (fgets(buffer, sizeof(buffer), subprocess_stdout(&process)))
...
subprocess_destroy(...);
and it blocks inside join (in WaitForSingleObject()
), but only if the output is long (otherwise it works).
If I try to do the join after the fgets
, then it blocks inside fgets
(even for small outputs).
Any idea? Am I doing something wrong?
Hi! I recently updated the library with the latest version and it wasn't working for me. I analyzed the changes of the new version and found out that the call that was using execvp
now is replaced for execv
(in subprocess_create_ex
). The problem is that breaks the behavior since execv
expects a path (the path to the binary file) and execvp
expects a file (a binary file name or a path, if you provide the binary file name it will search in PATH the binary name provided). It's also inconsistent with the other exec calls since execve
is being used (which expects also a file). For the moment it just made that change locally but I just wanted to let you know this.
Thanks for the library.
When working with an async process, it'd be nice to have a clean method to periodically check if the process is still alive or if it has already finished. Right now, _join()
, _destroy()
and _terminate()
seem to all be about waiting for a process to end - or just destroying the struct and sending it away from the current process (as stated that _destroy()
can leave a process running even after main execution has finished).
It's a common practice to make single header C libraries with implementation guards, this allow isolating the implementation details code from the main program, thus avoiding variable name conflicts, speeding up compile time if the user include from multiple C files, and also making the library more friendly to generate libraries for other programming languages (that's my intent).
See other awesome single header C libraries such as sokol_gfx.h, miniaudio.h, as examples.
The user would use like this:
#define SUBPROCESS_IMPL
#include "subprocess.h"
And somewhere in the C header
#ifdef SUBPROCESS_IMPL
/* all function implementations */
#include "subprocess.h"
Although this would break user code when he updates the library, you could negate the ifdef like STB libraries do in some headers, with a define like SUBPROCESS_ONLY_IMPL
.
It's also a good practice to allow the user to change the library API declaration qualifier, for example I would like to add static
all the library functions, again sokol
and miniaudio
are good examples on that. Usually the compiler inline static functions.
Separating implementation from the declaration at first looks that this would make harder for the C compiler to inline the code, but the compiler can usually inline functions by itself when the static
qualifier is used. Also not everybody want to all their functions to be inlined as may increase binary size, also build times, so it's
I was about to ask the same for your JSON library. Although the implementation is inlined to be fast, would still be good to separate the implementation code, and this can be done without much penalties.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.