Related
I have a simple C++ console application that starts notepad.exe and loads the file D:\MyTextFile.txt and then the console application exits, but notepad is still running. The code works great:
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory( &pi, sizeof(pi) );
WCHAR pCmd[] = {'n','o','t','e','p','a','d','.','e','x','e',' ','D',':','\\','M','y','T','e','x','t','F','i','l','e','.','t','x','t',0};
BOOL result = CreateProcess
(
_T("C:\\Windows\\System32\\notepad.exe"), // Module name
pCmd, // Command line (as modifiable array)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set bInheritHandles to FALSE
DETACHED_PROCESS, // Detach process
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (returned)
);
return (result) ? 0 : -1;
}
However, if I replace notepad with cmd and MyTextFile.txt with MyBatFile.bat then it doesn't work. Contents of MyBatFile.bat:
C:\Windows\System32\notepad.exe D:\MyTextFile.txt
Modified console application:
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory( &pi, sizeof(pi) );
WCHAR pCmd[] = {'c','m','d','.','e','x','e',' ','/','C',' ','D',':','\\','M','y','B','a','t','F','i','l','e','.','b','a','t',0};
BOOL result = CreateProcess
(
_T("C:\\Windows\\System32\\cmd.exe"), // Module name
pCmd, // Command line (as modifiable array)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set bInheritHandles to FALSE
DETACHED_PROCESS, // Detach process
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (returned)
);
return (result) ? 0 : -1;
}
When I execute the above code I see a command prompt flashing by very quickly, but it doesn't seem to execute MyBatFile.bat. However, if I replace DETACHED_PROCESS with CREATE_UNICODE_ENVIRONMENT then MyBatFile.bat gets executed, but since the process is no longer detached the command prompt hangs until I close notepad, which is undesired. Does anybody know how I can modify my code to be able to execute MyBatFile.bat in a detached process?
I can't explain why, but it seems to work for .bat-files (via cmd.exe) if I choose CREATE_NO_WINDOW instead of DETACHED_PROCESS. The following code seems to work for both .exe-files and .bat-files:
// RunDetached.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int getIndexOfStringIgnoreCare(WCHAR* bigStingToSearchThrough, WCHAR* subStingToFind);
int getFirstIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor);
int getLastIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor);
int _tmain(int argc, _TCHAR* argv[])
{
WCHAR* pOriginalCmd = ::GetCommandLine();
// Test code (modify paths to RunDetached.exe and MyFile.txt appropriately)
// pOriginalCmd = _T("\"D:\\My Visual Studio Projects\\RunDetached\\debug\\RunDetached.exe\" \"C:\\Windows\\System32\\notepad.exe\" \"D:\\1.txt\"");
int CmdLen = (int)wcslen(pOriginalCmd);
// Determine where certain characters are located (excl means the particular index is not included, e.g.
// if indexExcl is 5 then index 4 is the last included index).
int beginningOf1stArg = getFirstIndexOfChar(pOriginalCmd, 0, L'\"');
int endOf1stArgExcl = getFirstIndexOfChar(pOriginalCmd, beginningOf1stArg + 1, L'\"') + 1;
int beginningOf2ndArg = getFirstIndexOfChar(pOriginalCmd, endOf1stArgExcl + 1, L'\"');
int endOf2ndArgExcl = getFirstIndexOfChar(pOriginalCmd, beginningOf2ndArg + 1, L'\"') + 1;
int beginningOf3rdArg = getFirstIndexOfChar(pOriginalCmd, endOf2ndArgExcl + 1, L'\"');
int endOfLastArgExcl = getLastIndexOfChar (pOriginalCmd, CmdLen - 1, L'\"') + 1;
int beginningOfFileName = getLastIndexOfChar (pOriginalCmd, endOf2ndArgExcl - 2, L'\\') + 1;
int endOfFileNameExcl = endOf2ndArgExcl - 1;
if ((beginningOf1stArg < 0) || (endOf1stArgExcl < 0) || (beginningOf2ndArg < 0) || (endOf2ndArgExcl < 0) ||
(endOfLastArgExcl < 0) || (beginningOfFileName < 0) || (endOfFileNameExcl < 0))
{
return -1;
}
// Determine the application to execute including full path. E.g. for notepad this should be:
// C:\Windows\System32\notepad.exe (without any double-quotes)
int lengthOfApplicationNameAndPathInChars = (endOf2ndArgExcl -1) - (beginningOf2ndArg + 1); // Skip double-quotes
WCHAR* lpApplicationNameAndPath = (WCHAR*)malloc(sizeof(WCHAR) * (lengthOfApplicationNameAndPathInChars + 1));
memcpy(lpApplicationNameAndPath, &pOriginalCmd[beginningOf2ndArg + 1], sizeof(WCHAR) * (lengthOfApplicationNameAndPathInChars));
lpApplicationNameAndPath[lengthOfApplicationNameAndPathInChars] = (WCHAR)0; // Null terminate
// Determine the command argument. Must be in modifyable memory and should start with the
// application name without the path. E.g. for notepad with command argument D:\MyFile.txt:
// "notepad.exe" "D:\MyFile.txt" (with the double-quotes).
WCHAR* modifiedCmd = NULL;
if (0 < beginningOf3rdArg)
{
int lengthOfApplicationNameInChars = endOfFileNameExcl - beginningOfFileName; // Application name without path
int lengthOfRestOfCmdInChars = CmdLen - beginningOf3rdArg;
int neededCmdLengthInChars = 1 + lengthOfApplicationNameInChars + 2 + lengthOfRestOfCmdInChars; // Two double-quotes and one space extra
modifiedCmd = (WCHAR*)malloc(sizeof(WCHAR) * (neededCmdLengthInChars + 1)); // Extra char is null-terminator
modifiedCmd[0] = L'\"'; // Start with double-quoute
memcpy(&modifiedCmd[1], &pOriginalCmd[beginningOfFileName], sizeof(WCHAR) * (lengthOfApplicationNameInChars));
modifiedCmd[1 + (lengthOfApplicationNameInChars)] = L'\"';
modifiedCmd[1 + (lengthOfApplicationNameInChars) + 1] = L' ';
memcpy(&modifiedCmd[1 + (lengthOfApplicationNameInChars) + 2], &pOriginalCmd[beginningOf3rdArg], sizeof(WCHAR) * lengthOfRestOfCmdInChars);
modifiedCmd[neededCmdLengthInChars] = (WCHAR)0;
}
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory( &pi, sizeof(pi) );
BOOL result = CreateProcess // Start the process
(
lpApplicationNameAndPath, // Module name and full path
modifiedCmd, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set bInheritHandles to FALSE
(0 <= getIndexOfStringIgnoreCare // Special case for cmd.exe (don't
(lpApplicationNameAndPath, L"cmd.exe")) ? // know why but it seems to work)
CREATE_NO_WINDOW : DETACHED_PROCESS,
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (returned)
);
free(lpApplicationNameAndPath);
if (modifiedCmd != NULL)
{
free(modifiedCmd);
}
if (result) return 0;
wchar_t msg[2048];
FormatMessage
(
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
::GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
msg, sizeof(msg),
NULL
);
fputws(msg, stderr);
_flushall();
return -1;
}
bool compareCharsIgnoreCase(WCHAR char1, WCHAR char2)
{
if (char1 == char2)
{
return true;
}
const int UPPER_LOWER_CASE_OFFSET_IN_ASCII_TABLE = 'a' - 'A';
if ((L'A' <= char1) && (char1 <= L'Z'))
{
return ((char1 + UPPER_LOWER_CASE_OFFSET_IN_ASCII_TABLE) == char2);
}
if ((L'a' <= char1) && (char1 <= L'z'))
{
return ((char1 - UPPER_LOWER_CASE_OFFSET_IN_ASCII_TABLE) == char2);
}
return false;
}
int getIndexOfStringIgnoreCare(WCHAR* bigStringToSearchThrough, WCHAR* subStringToFind)
{
if ((bigStringToSearchThrough == NULL) || (subStringToFind == NULL))
{
return -1;
}
int bigStringLen = (int)wcslen(bigStringToSearchThrough);
int subStringLen = (int)wcslen(subStringToFind);
if ((5000 < bigStringLen) || (5000 < subStringLen)) // Sanity check
{
return -1;
}
for (int i = 0; i < (bigStringLen - subStringLen + 1); i++)
{
for (int j = 0; j < subStringLen; j++)
{
if (!compareCharsIgnoreCase(bigStringToSearchThrough[i + j], subStringToFind[j]))
{
break;
}
else if ((j + 1) == subStringLen)
{
return i;
}
}
}
return -1;
}
int getFirstIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor)
{
int stringLen = (int)wcslen(stringToInvestigate);
if (5000 < stringLen) // Sanity check
{
return -1;
}
for (int i = startIndex; i < stringLen; i++)
{
if (stringToInvestigate[i] == charToLookFor)
{
return i;
}
}
return -1;
}
int getLastIndexOfChar(WCHAR* stringToInvestigate, int startIndex, WCHAR charToLookFor)
{
int stringLen = (int)wcslen(stringToInvestigate);
if (5000 < stringLen) // Sanity check
{
return -1;
}
for (int i = min(stringLen - 1, startIndex); 0 <= i; i--)
{
if (stringToInvestigate[i] == charToLookFor)
{
return i;
}
}
return -1;
}
So, with the above code you can do both
"RunDetached.exe" "C:\Windows\System32\notepad.exe" "D:\MyTextFile.txt"
and
"RunDetached.exe" "C:\Windows\System32\cmd.exe" "/c" "D:\MyBatFile.bat"
and your calling application won't hang. It's important to enclose each and every argument with double-quotes because I use those in the code to find the different arguments.
I've been stuck on an issue with my program and just hoping for any help at this point :(
or guidance towards the right direction. In my code, I'm implenting a mini shell in c++ where the user can pipe 2 or more processes together, yet an issue keeps coming up whenever I execute it. Only the first and last commands actually execute so say I run:
cat b.txt | sort | tail -2
only cat b.txt and tail -2 would execute.
Here is my attempt at the whole program, also referenced to this which helped me tremendously with the setup.
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <sys/utsname.h>
#include <unistd.h>
using namespace std;
//this variable will take in the line of input submitted by the user
char buf[1024];
//PIDs for the two child processes
pid_t pid[300];
//these will be use to check the status of each child in the parent process
int status;
int status2;
int pid_num = 1;
//initializes the pipe
int pipeA[2] = {-1,-1};
int g = 0;
void first_command(int pipeA[], char * command[], bool pipeExists){
if(pipeExists){
dup2(pipeA[1], 1);
close(pipeA[0]);
}
// this will run command[0] as the file to execute, and command as the arg
execvp(command[0], command);
printf("Can not execute FIRST command, please enter a valid command \n");
exit(127);
}
void other_command(int pipeA[], char * command0[], int index){
dup2(pipeA[0], 0);
close(pipeA[1]);
execvp(command0[0], command0);
printf("Can not execute SECOND command, please enter a valid command\n");
exit(127);
}
void main_func() {
//stay inside the loop and keep asking the user for input until the user quits the program
while (fgets(buf,1024,stdin) != NULL){
//initialize a boolean to check if user wants to pipe something, set to false by default until we check with user
bool pipeExists = false;
//initialize this arrays to NULL so anything that store in them gets cleared out.
//these arrays will hold the commands that the user wants to carry out.
char * command[1024] = {NULL, NULL, NULL};
char *command0[1024] = {NULL, NULL, NULL};
char *command1[] = {NULL, NULL, NULL};
char *command2[] = {NULL, NULL, NULL};
char *command3[] = {NULL, NULL, NULL};
char ** my_commands[] = {
command0,
command1,
command2,
command3,
NULL
};
//Important to delete mark the last byte as 0 in our input
buf[strlen(buf) -1] = 0;
//initialize this number to zero to start save the tokens at this index
int index = 0;
//a char * to hold the token saved by strtok
char * ptr;
ptr = strtok(buf, " \"");
//Loop through 'buf' and save tokens accordingly
while(ptr != NULL){
// if the user types exit at any moment, the program will exit gracefully and terminate
if(strcmp( ptr, "exit" ) == 0){
exit(0);
}
//if ptr is equal to | user wants to pipe something and we change pipeExists to true
if(strcmp( ptr, "|" ) == 0){
pipeExists = true;
index= 0;
ptr = strtok(NULL, " ");
}
//enter here while user doesnt want to user pipes
if(!pipeExists){
command[index] = ptr;
ptr = strtok(NULL, " ");
index++;
}
//enter here if user want to use pipes
if(pipeExists){
command0[index] = ptr;
ptr = strtok(NULL, " ");
index++;
}
g++;
printf("%s %i\n", ptr, g);
}
for (int s = 0; my_commands[s] != NULL; s++) {
cout << command0[s] << " \n" << endl;
}
//if pipes exists then initialize it
if(pipeExists){
pipe(pipeA);
}
//create first child
if ((pid[0] = fork()) == 0) {
//pass in the pipe, commands and pipe to function to execute
first_command(pipeA, command, pipeExists);
}
else if(pid[0] < 0){
//error with child
cerr<<"error forking first child"<<endl;
}
// if pipe exists create a second process to execute the second part of the command
if(pipeExists){
for(int f = 0; my_commands[f] != NULL; f++) {
//create second child
if ((pid[f] = fork()) == 0) {
other_command(pipeA, command0, index);
}
else if(pid[f] < 0){
//error with second child
cerr<<"error forking child "<< pid_num << endl;
}
}
pid_num++;
}
//if the pipe was created then we close its ends
if(pipeExists){
for(int z = 0; z < pid_num; z++) {
close(pipeA[z]);
}
}
//wait for the first child that ALWAYS executes
if ( (pid[0] = waitpid(pid[0], &status, 0)) < 0)
cerr<<"error waiting for first child"<<endl;
//wait for the second child but only if user wanted to created to use piping
if(pipeExists){
for(int j = 1; j < pid_num; j++) {
if ( (pid[j] = waitpid(pid[j], &status2, 0)) < 0){
printf("Status: %d", pid[j]);
cerr<<"error waiting for child " << j <<endl;
}
}
}
pid_num = 1;
}//endwhile
}
With the help of many people here, I've been writing a program that writes the contents of the Windows clipboard to a text file. (I'm working in Visual Studio 2010.) I've been trying to work out the logic of a for loop that will test the command-line arguments (if any); the arguments can be
a codepage number
a filename or path
or both, in any order. If no codepage is specified (or if the user specifies an invalid codepage), the program uses the default Windows codepage (typically 1252). If no filename is specified, the program writes the output to "#clip.txt".
I know my method of reading the arguments is inefficient, but it's the best I can figure out right now. I use two for loops. The first checks each command-line parameter; if the string is NOT all-digits, it uses the string as a filename and then breaks. The next loop again checks each parameter, and if the string is all-digits, it assigns it as the codepage number and then breaks.
The idea is that if the user enters
clipwrite 500 850
only the first (500) should get used as the codepage. And if the user enters
clipwrite foo.txt bar.txt
the output should be written to foo.txt.
My code seems to work correctly if the user enters no arguments, one argument only, or one number and one alpha string. But I'm clearly doing something wrong, because if the user enters
clipwrite 500 850
then 850 gets used (it should be ignored). And if the user enters
clipwrite foo.txt bar.txt
the program crashes. Can anyone help me sort what's wrong with my logic? Here's the relevant code (which uses a command-line parsing routine to get argc and argv):
if (argc > 1) {
// get name of output file if specified
for ( i = 1; i < argc; i++ ) {
if (i < 3) {
string argstr = argv[i];
//if string is not digits-only, use as filename
for (size_t n = 0; n <argstr.length(); n++) {
if (!isdigit( argstr[ n ]) ) {
OutFile = argv[i];
break;
}
}
}
}
// get codepage number if specified
for ( i = 1; i < argc; i++ ) {
if (i < 3) {
string argstr = argv[i];
for (size_t n = 0; n <argstr.length(); n++) {
if (!isdigit( argstr[ n ]) ) {
// if all chars are digits
} else {
// convert codepage string to integer
int cpint = atoi(argstr.c_str());
// check if codepage is valid; if so use it
if (IsValidCodePage(cpint)) {
codepage = "."+argstr;
}
break;
}
}
}
}
}
Many thanks for any help with this beginner-level problem.
Maybe something like this:
#include <iostream>
#include <string>
#include <cstring>
#include <cctype>
#include <cstdlib>
int main(int, char **argv) {
std::string filename = "#clip.txt";
int codepage = 1252;
bool bFilenameSet = false;
bool bCodepageSet = false;
for (++argv; *argv; ++argv) { // *argv == NULL at end of arguments
char *p = *argv;
for ( ; *p; ++p)
if (!isdigit(*p))
break;
if (*p) { // non-digit found
if (!bFilenameSet) {
filename = *argv;
bFilenameSet = true;
}
}
else {
if (!bCodepageSet) {
codepage = atoi(*argv);
bCodepageSet = true;
}
}
}
std::cout<< "Filename: "<< filename<< "\n";
std::cout<< "Codepage: "<< codepage<< "\n";
return 0;
}
I ran your program but it wasn't complete, so I assumed that isValidCodePage() function always returns true.
What I can see from your code is that you are overwriting codepage and Outfile because you are only breaking the inner loop, see this article for an explaination of the break statement
I don't see any immediate reason for a crash, but:
When issuing clipwrite 500 850 you use the codepage 850 since your break; only leaves the inner loop but your code keeps iterating over
the arguments and your codepage variable gets overwritten.
Your usage of isdigit is faulty. Whenever a string starts with a digit you try to interpret it as an integer even if its 1bla.txt.
atoi() is evil since it fails to report if a given string can't be parsed as a number. Better use std::stringstream and >> operator.
May be you should do it like this:
int cpint = -1;
std::string fname="";
for ( int i = 1; i < argc && i<3; i++ ) {
std::stringstream argss(argv[i]);
// Check if the string is a decimal
// and only a decimal
if( !(argss >> cpint) || !argss.eof()) {
fname=argv[i];
}
}
if(!fname.empty())
std::cerr << "filename '" << fname "'" << std::endl;
if(cpint!=-1)
std::cerr << "codepage: #" << cpint << std::endl;
Not really tested but I hope you get the idea
Using the answers here I finally got everything working, though I know my code is still inefficient. Here is the VS2010 source code that I used for this clipboard writing utility. Thanks to all who responded.
// ClipWrite.cpp
#include "stdafx.h"
#include <Windows.h>
#include <shellapi.h>
#include <iostream>
#include <fstream>
#include <codecvt> // for wstring_convert
#include <locale> // for codecvt_byname
#include <sstream>
using namespace std;
// helper gets path to this application
string ExePath() {
char buffer[MAX_PATH];
GetModuleFileNameA( NULL, buffer, MAX_PATH );
string::size_type pos = string( buffer ).find_last_of( "\\/" );
return string( buffer ).substr( 0, pos);
//return std::string( buffer ).substr( 0, pos);
}
// set variable for command-line arguments
char **argv = NULL;
// helper to get command-line arguments
int ParseCommandLine() {
int argc, BuffSize, i;
WCHAR *wcCommandLine;
LPWSTR *argw;
wcCommandLine = GetCommandLineW();
argw = CommandLineToArgvW( wcCommandLine, &argc);
argv = (char **)GlobalAlloc( LPTR, argc + 1);
for( i=0; i < argc; i++) {
BuffSize = WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK, argw[i], -1, NULL, 0, NULL, NULL );
argv[i] = (char *)GlobalAlloc( LPTR, BuffSize );
WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK, argw[i], BuffSize * sizeof( WCHAR ),argv[i], BuffSize, NULL, NULL );
}
return argc;
}
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow)
{
// for logging in case of error
int writelog = 0;
string logtext = "";
// create output filename
string filename = ExePath() + "\\#clip.txt";
// get default codepage from Windows, typically 1252
int iCP=GetACP();
string sCP;
ostringstream convert;
convert << iCP;
sCP = convert.str();
// construct string to use for conversion routines (e.g. ".1252")
string sDefaultCP = "."+sCP;
string sOutputCP = "."+sCP;
// read command line for alternate codepage and/or filename
int i, argc;
argc = ParseCommandLine( );
if (argc > 1) {
bool bFilenameSet = false;
bool bCodepageSet = false;
int cpint = -1;
for ( i = 1; i < argc && i<3; i++ ) {
std::string argstr = argv[i];
//if string has only digits, use as codepage;
for (size_t n = 0; n <argstr.length(); n++) {
if (!isdigit( argstr[ n ]) ) {
if (!bFilenameSet) {
filename = argv[i];
bFilenameSet = true;
}
} else {
// convert codepage string to integer
if (!bCodepageSet) {
std::stringstream argss(argv[i]);
if( (argss >> cpint) || !argss.eof()) {
argstr = argv[i];
logtext = logtext + "Requested codepage (if any): " + argstr + "\n";
cout << "Requested codepage (if any): " << argstr << endl;
// check if codepage is valid; if so, use it
if (IsValidCodePage(cpint)) {
sCP = argstr;
sOutputCP = "."+argstr;
}
bCodepageSet = true;
}
}
}
}
}
}
cout << "Codepage used: " + sCP << endl;
// get clipboard text
string cliptext = "";
if (OpenClipboard(NULL)) {
if(IsClipboardFormatAvailable(CF_TEXT)) {
HGLOBAL hglb = GetClipboardData(CF_TEXT);
if (hglb != NULL) {
LPSTR lptstr = (LPSTR)GlobalLock(hglb);
if (lptstr != NULL) {
// read the contents of lptstr
cliptext = (char*)hglb;
// release the lock
GlobalUnlock(hglb);
}
}
}
CloseClipboard();
}
// create conversion routines
typedef std::codecvt_byname<wchar_t,char,std::mbstate_t> codecvt;
std::wstring_convert<codecvt> cp1252(new codecvt(sDefaultCP));
std::wstring_convert<codecvt> outpage(new codecvt(sOutputCP));
ofstream OutStream; // open an output stream
OutStream.open(filename, std::ios_base::binary | ios::out | ios::trunc);
// make sure file is successfully opened
if(!OutStream) {
writelog = 1;
logtext = logtext + "Error opening file " + filename + " for writing.\n";
//return 1;
} else {
// convert to DOS/Win codepage number in "outpage"
OutStream << outpage.to_bytes(cp1252.from_bytes(cliptext)).c_str();
OutStream.close(); // close output stream
if (writelog == 1) {
logtext = logtext + "Output file: " + filename + "\n";
}
}
if (writelog == 1) {
logtext = logtext + "Codepage used: " + sCP + "\n";
string LogFile = ExePath() + "\\#log.txt";
ofstream LogStream;
LogStream.open(LogFile, ios::out | ios::trunc);
if(!LogStream) {
cout << "Error opening file " << LogFile << " for writing.\n";
return 1;
}
LogStream << logtext;
LogStream.close(); // close output stream
}
return 0;
}
I am trying to launch a new process from my current process. I am using CreateProcess() to launch it. The issue is that I need to have certain directories in my PATH to successfully do so. Here is my current implementation but it doesn't work. What am I doing wrong?
// Environment variables
char *env = new char[2048];
char *ptr = env;
char temp[MAX_PATH] = "PATH=";
strcpy(ptr, strcat(temp, plugin_path));
ptr += strlen(ptr) + 1;
char temp2[MAX_PATH] = "PATH=";
strcpy(ptr, strcat(temp, lib_path));
ptr += strlen(ptr) + 1;
*ptr = '\0';
// Execute
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// error checking required
if(!CreateProcess(
NULL, // application name
command_path, // app.exe
NULL,
NULL,
TRUE,
0,
env, // environment
NULL,
&si,
&pi)) {
std::cout << GetLastError();
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
std::cout << "Process Started!";
Please let me know if anything else is required.
EDIT: Somebody mentioned below that I need to be a little more specific. It doesn't work in the sense that the environment variables don't get passed. It fails because the library path is not in PATH. The createProcess does actually launch it though.
EDIT2: Here's the updated code. Same problem. Further, CreateProcess throws error 1087 which doesn't seem to exist in the docs.
// Environment variables
char env[2048];
char *ptr = env;
char *path_path = getenv("PATH");
// copy original path
memcpy(ptr, path_path, strlen(path_path));
ptr += strlen(ptr) + 1;
memcpy(ptr, ";", 1);
ptr++;
// copy plugin path
memcpy(ptr, plugin_path, strlen(plugin_path));
ptr += strlen(plugin_path) + 1;
memcpy(ptr, ";", 1);
ptr++;
// copy libpath
memcpy(ptr, lib_path, strlen(lib_path));
ptr += strlen(lib_path) + 1;
memcpy(ptr, ";", 1);
ptr++;
// double null terminated
memcpy(ptr, "\0\0", 2);
std::cout << "ENV : " << env << std::endl;
// error checking required
if(!CreateProcess(
NULL, // application name
command_path, // app.exe
NULL,
NULL,
TRUE,
0,
env, // environment
NULL,
&si,
&pi)) {
std::cout << GetLastError();
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
std::cout << "Process Started!";
The PATH variable is a single variable. Different directories are listed in that variable, separated by semi-colons. But you've attempted to define the variable twice. That is the mistake.
The code should be something like this (assuming that you want to extend the existing path):
char *env = new char[2048]; // fingers crossed this is enough
strcpy(env, "PATH=");
strcat(env, getenv("PATH"));
strcat(env, ";");
strcat(env, plugin_path);
strcat(env, ";");
strcat(env, lib_path);
env[strlen(env)+1] = '\0';
Although this code (as is yours in the question) is simply begging for a buffer overrun.
It would be so much easier if you used C++ facilities to build your strings. For instance:
std::stringstream ss;
ss << "PATH=" << getenv("PATH");
ss << ";" << plugin_path;
ss << ";" << lib_path;
ss << '\0';
std::string env = ss.str();
Then pass env.c_str() to CreateProcess.
Not only does this make the code easier to read and verify, you know that you won't overrun any buffers.
I also note that you are passing an environment that has only one variable defined in it, namely PATH. It might be better if you started from the environment of the calling process, added the extra directories to PATH, and then passed that as the environment for the new process.
#include <iostream>
#include <windows.h>
#include <cstring>
#include "tchar.h"
void SetUserVariablePath(){
HKEY hkey;
long regOpenResult;
const char key_name[] = "Environment";
const char path[]="D:/custom_command"; //new_value path need to update
regOpenResult = RegOpenKeyEx(HKEY_CURRENT_USER,key_name, 0, KEY_ALL_ACCESS, &hkey);
LPCSTR stuff = "VVS_LOGGING_PATH"; //Variable Name
RegSetValueEx(hkey,stuff,0,REG_SZ,(BYTE*) path, strlen(path));
RegCloseKey(hkey);
}
void GetUserVariablePath(){
static const char path[] = "VVS_LOGGING_PATH" ; //Variable Name
static BYTE buffer1[1000000] ;
DWORD buffsz1 = sizeof(buffer1) ;
{
//HKEY_CURRENT_USER\Environment
const char key_name[] = "Environment";
HKEY key ;
if( RegOpenKeyExA( HKEY_CURRENT_USER, key_name, 0, KEY_QUERY_VALUE, std::addressof(key) ) == 0 &&
RegQueryValueExA( key, path, nullptr, nullptr, buffer1, std::addressof(buffsz1) ) == 0 )
{
std::cout << "The updated value of the user variable is : " << reinterpret_cast<const char*>(buffer1) << '\n' ;
}
}
}
int main()
{
SetUserVariablePath();
GetUserVariablePath();
return 0;
}
I need to get parent directory from file in C++:
For example:
Input:
D:\Devs\Test\sprite.png
Output:
D:\Devs\Test\ [or D:\Devs\Test]
I can do this with a function:
char *str = "D:\\Devs\\Test\\sprite.png";
for(int i = strlen(str) - 1; i>0; --i)
{
if( str[i] == '\\' )
{
str[i] = '\0';
break;
}
}
But, I just want to know there is exist a built-in function.
I use VC++ 2003.
Thanks in advance.
If you're using std::string instead of a C-style char array, you can use string::find_last_of and string::substr in the following manner:
std::string str = "D:\\Devs\\Test\\sprite.png";
str = str.substr(0, str.find_last_of("/\\"));
Now, with C++17 is possible to use std::filesystem::path::parent_path:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p = "D:\\Devs\\Test\\sprite.png";
std::cout << "parent of " << p << " is " << p.parent_path() << std::endl;
// parent of "D:\\Devs\\Test\\sprite.png" is "D:\\Devs\\Test"
std::string as_string = p.parent_path().string();
return 0;
}
Heavy duty and cross platform way would be to use boost::filesystem::parent_path(). But obviously this adds overhead you may not desire.
Alternatively you could make use of cstring's strrchr function something like this:
include <cstring>
char * lastSlash = strrchr( str, '\\');
if ( *lastSlash != '\n') *(lastSlash +1) = '\n';
Editing a const string is undefined behavior, so declare something like below:
char str[] = "D:\\Devs\\Test\\sprite.png";
You can use below 1 liner to get your desired result:
*(strrchr(str, '\\') + 1) = 0; // put extra NULL check before if path can have 0 '\' also
On POSIX-compliant systems (*nix) there is a commonly available function for this dirname(3). On windows there is _splitpath.
The _splitpath function breaks a path
into its four components.
void _splitpath(
const char *path,
char *drive,
char *dir,
char *fname,
char *ext
);
So the result (it's what I think you are looking for) would be in dir.
Here's an example:
int main()
{
char *path = "c:\\that\\rainy\\day";
char dir[256];
char drive[8];
errno_t rc;
rc = _splitpath_s(
path, /* the path */
drive, /* drive */
8, /* drive buffer size */
dir, /* dir buffer */
256, /* dir buffer size */
NULL, /* filename */
0, /* filename size */
NULL, /* extension */
0 /* extension size */
);
if (rc != 0) {
cerr << GetLastError();
exit (EXIT_FAILURE);
}
cout << drive << dir << endl;
return EXIT_SUCCESS;
}
On Windows platforms, you can use
PathRemoveFileSpec or PathCchRemoveFileSpec
to achieve this.
However for portability I'd go with the other approaches that are suggested here.
You can use dirname to get the parent directory
Check this link for more info
Raghu