|
Dynamic memory, loops, and segmentation faults
(I apologize if you haven't seen The Wizard of Oz and therefore didn't understand my title.)
Anyways, I'm having some trouble with this program and can't seem to find the problem. The program takes other programs and/or shortcuts to run as command-line arguments. It can also send command-line arguments to the .exe files that it runs. The format is like this:
"c:\multishort\multishort.exe" c:\windows\system32\mspaint.exe AAc:\test.bmp c:\windows\system32\notepad.exe AAc:\test.txt
This SHOULD run Paint with c:\test.bmp open and Notepad with c:\test.txt open. Actually it does neither. Currently, the program simply decides to exit after executing line 33. This is most puzzling. Here is the code:
/*
MultiShort v1.0
by ubergeek
runs multiple programs from one shortcut (see readme.txt for more details)
MultiShort is free software. There is no warranty, implied or otherwise. If
you like it, great. Use it. If you don't like it, don't use it. If it causes
any harm to you or your computer, such as not working, deleteing files, or
kidnapping your relatives, I am not responsible.
ms.h
*/
#ifndef _MULTISHORT_PROGRAM_H_FILE_ //inclusion guard
#define _MULTISHORT_PROGRAM_H_FILE_
#include <windows.h> //windows API functions
#include <shlobj.h> //IShellLink and IPersistFile interfaces
#include <wtypes.h> //CLSCTX enumeration
#include <objbase.h> //interface-related functions
#include <objidl.h> //interface-related functions
#include <strsafe.h> //StringCchCopy -- replacement for strcpy that causes segmentation faults much less often
#include <string> //string class
//must link libole32.a and libuuid.a (in VC++ it would be ole32.lib and uuid.lib)
using namespace std;
enum PROGTYPE { EXE, LNK };
inline int dbgmsg(const char *msg) { return MessageBox(NULL, msg, "MultiShort: Debugging Message", MB_OK | MB_ICONINFORMATION); }
inline int errmsg(const char *msg) { return MessageBox(NULL, msg, "MultiShort: Error", MB_OK | MB_ICONERROR); }
inline int dbgmsg(const string &strmsg) { return dbgmsg(strmsg.c_str()); }
inline int errmsg(const string &strmsg) { return errmsg(strmsg.c_str()); }
struct LNKINFO //holds information about a shortcut (.lnk) file
{
string filename; //actual filename of the .lnk
string pointedprog; //file the shortcut points to
string pointedargs; //any arguments to be passed on to pointedprog
int pointedshowcmd; //window state to give to pointedprog
};
struct PROG
{
PROGTYPE type;
union /*anonymous*/
{
string *pexename;
LNKINFO *plnkinf;
};
};
bool isExecutable(string); //takes command-line argument (path and filename) as an std::string, and returns true if the file has a .exe extension, and false if it doesn't.
void getShortcutInfo(LNKINFO*); //takes path\filename of a shortcut (.lnk) and fills a LNKINFO structure
void toLower(string*); //takes a string and converts any uppercase letters in it to lowercase
int getNumProgs(int, char**); //takes the argc and argv from main, parses it, and determines how many actual programs/shortcuts there are to run
bool parseCommandLine(int, char**, PROG*, int*);
#endif //inclusion guard
/*
MultiShort v1.0
by ubergeek
runs multiple programs from one shortcut (see readme.txt for more details)
MultiShort is free software. There is no warranty, implied or otherwise. If
you like it, great. Use it. If you don't like it, don't use it. If it causes
any harm to you or your computer, such as not working, deleteing files, or
kidnapping your relatives, I am not responsible.
multishort.cpp
*/
#include "ms.h"
int main(int argc, char *argv[]) //argc tells me how many command-line arguments there are, and argv is an array of the command-line arguments passed to MultiShort, split at spaces (but not split inside quoted arguments)
{
if (argc < 2)
{
errmsg("You didn't give me anything to run! Read readme.txt if you're confused.");
return 1;
}
if ( (argv[1][0] == 'A') && (argv[1][1] == 'A') )
{
errmsg("First argument must be a program or shortcut, not an argument. Read readme.txt if you're confused.");
return 1;
}
PROG *progs_to_run = NULL;
int numprogs = 0;
bool success = parseCommandLine(argc, argv, progs_to_run, &numprogs); //will allocate progs_to_run for me. this means that i should definitely check that and not forget to delete[] it!
dbgmsg("back from parsecommandline"); //line 33. Nothing after this is run. ???
for (int i = 0; i < numprogs; ++i)
{
if (progs_to_run[i].type == EXE) dbgmsg(progs_to_run[i].pexename->c_str());
else
{
dbgmsg(progs_to_run[i].plnkinf->pointedprog.c_str());
dbgmsg(progs_to_run[i].plnkinf->pointedargs.c_str());
}
}
dbgmsg("done outputting parsecmdln's work");
if (!success)
{
errmsg("Fatal error. Exiting.");
return 1;
}
if (numprogs < 2) //if you didn't provide two or more programs to run...
{
errmsg("You must provide two or more programs to run as command-line arguments. If you didn't provide any programs to run, why run this program? It doesn't as yet do anything else. If you only provided one program to run, then why use MultiShort? Just make a regular shortcut. Read readme.txt if you're confused."); //...then tell the user what went wrong and how to fix it.
return 1;
}
STARTUPINFO startinfo; //these structures are required by CreateProcess, but at this time MultiShort don't use them for anything. They provide a higher level of control over how the program is run, for example it can be run minimized.
ZeroMemory(&startinfo, sizeof(STARTUPINFO));
startinfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION procinfo;
dbgmsg("starting real work");
for (int i = 0; i < numprogs; i++) //this loop will run until all the command-line arguments have been considered
{
if (progs_to_run[i].type == EXE)
{
ZeroMemory(&procinfo, sizeof(PROCESS_INFORMATION));
CreateProcess(NULL, const_cast<char*>(progs_to_run[i].pexename->c_str()), NULL, NULL, false, 0, NULL, NULL, &startinfo, &procinfo); //this is the most important line in the program. it runs the program specified in the command-line argument
CloseHandle(procinfo.hProcess); //and, clean up after Windows
CloseHandle(procinfo.hThread);
}
else //the file is not in the form path\filename.exe||bat||com
{
getShortcutInfo(progs_to_run[i].plnkinf); //fill our structure
if (isExecutable(progs_to_run[i].plnkinf->pointedprog))
{
string exec = "\""; //start with a space
exec += progs_to_run[i].plnkinf->pointedprog; //add the filename
exec += "\" "; //closing quote and a space
exec += progs_to_run[i].plnkinf->pointedargs; //and any arguments
startinfo.wShowWindow = progs_to_run[i].plnkinf->pointedshowcmd;
startinfo.dwFlags |= STARTF_USESHOWWINDOW;
ZeroMemory(&procinfo, sizeof(PROCESS_INFORMATION));
CreateProcess(NULL, const_cast<char*>(exec.c_str()), NULL, NULL, false, 0, NULL, NULL, &startinfo, &procinfo); //this is the most important line in the program. it runs the program specified in the command-line argument (lnkinf.pointedprog + lnkinf.pointedargs as lnkinf.pointedshowcmd)
CloseHandle(procinfo.hProcess); //and, clean up after Windows
CloseHandle(procinfo.hThread);
}
else
{
SHELLEXECUTEINFO shlexecinf;
ZeroMemory(&shlexecinf, sizeof(SHELLEXECUTEINFO));
shlexecinf.cbSize = sizeof(SHELLEXECUTEINFO); //initialize our structure...
shlexecinf.hwnd = GetDesktopWindow(); //display messages with the desktop window as parent
shlexecinf.lpVerb = "open"; //open the pointedprog
shlexecinf.lpFile = new char[progs_to_run[i].plnkinf->pointedprog.length()+2]; //allocate our strings
shlexecinf.lpParameters = new char[progs_to_run[i].plnkinf->pointedprog.length()+2];
StringCchCopy(const_cast<char*>(shlexecinf.lpFile), progs_to_run[i].plnkinf->pointedprog.length()+1, progs_to_run[i].plnkinf->pointedprog.c_str()); //copy them
StringCchCopy(const_cast<char*>(shlexecinf.lpParameters), progs_to_run[i].plnkinf->pointedargs.length()+1, progs_to_run[i].plnkinf->pointedargs.c_str());
shlexecinf.nShow = progs_to_run[i].plnkinf->pointedshowcmd;
if (!ShellExecuteEx(&shlexecinf)) //attempt to run the pointedprog...
{
char errstr[300];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errstr, 299, NULL); //...but if it doesn't work, check what the error was and retrieve it
string err = "Failed to execute \""; //assemble error description string
err += progs_to_run[i].plnkinf->pointedprog.c_str();
err += "\" because ";
err += errstr;
err += ". Make sure that the file exists and is not currently running or opened by another program.";
errmsg(err.c_str()); //display the error
}
delete[] shlexecinf.lpFile; //memory leaks are bad
delete[] shlexecinf.lpParameters;
}
}
}
delete[] progs_to_run; //memory leaks are still bad, although this one wouldn't be as serious because the program is going to exit in about a millisecond anyways and release all its memory back to the operating system
return 0; //all done! goodbye!
}
bool isExecutable(string filename)
{
int ilastdot = filename.rfind(".", filename.length()); //find the dot that designates the beginning of the extension
string extension = filename.substr(ilastdot, filename.length() - ilastdot); //using that dot, slice off the extension
toLower(&extension); //convert the extension to lowercase for comparison purposes
return ( (extension == ".exe") || (extension == ".bat") || (extension == ".com") ); //check if the extension denotes an executable file
}
void toLower(string *str)
{
for (string::iterator iter = str->begin(); iter != str->end(); ++iter)
{ //iterate through the string
if ( ((int)*iter >= 65) && ((int)*iter <= 90) ) //if it's an uppercase letter
{
*iter += 32; //convert it to lowercase
}
}
}
void getShortcutInfo(LNKINFO *shortcut)
{
CoInitializeEx(NULL, 0); //make CoCreateInstance work
IShellLink *shell_link; //declare interface pointers
IPersistFile *persist_file;
char path[MAX_PATH+1], args[INFOTIPSIZE+1];
LRESULT ret = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&shell_link); //get a pointer to the IShellLink interface
shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); //get a pointer to the IPersistFile interface
WCHAR file[shortcut->filename.length()+1];
MultiByteToWideChar(CP_ACP, 0, shortcut->filename.c_str(), -1, file, MAX_PATH);
persist_file->Load(file, STGM_READ); //open the link
shell_link->Resolve(GetDesktopWindow(), SLR_UPDATE); //resolve the link and update it if it has changed
shell_link->GetPath(path, MAX_PATH, NULL, 0); //gather information
shell_link->GetArguments(args, INFOTIPSIZE);
shell_link->GetShowCmd(&shortcut->pointedshowcmd);
shortcut->pointedprog = path; //transfer information
shortcut->pointedargs = args;
shell_link->Release(); //release the interfaces
persist_file->Release();
CoUninitialize(); //undo whatever CoInitializeEx did
}
bool parseCommandLine(int argc, char **argv, PROG *output, int *num)
{
*num = 0;
output = new PROG[*num]; //allocate output
if (!output) return false;
dbgmsg("set up num, allocated output");
int index = 0;
bool making_exe = false;
for (int i = 1; i < argc; ++i, ++(*num)) //iterate through the arguments
{
dbgmsg("in loop");
if ( !( (argv[i][0] == 'A') && (argv[i][1] == 'A') ) )
{ //it's a program, not an argument
dbgmsg("program");
if (isExecutable(string(argv[i])))
{
making_exe = true;
++index;
output[index].type = EXE;
output[index].pexename = new string;
*output[index].pexename = "\"";
*output[index].pexename += argv[i];
*output[index].pexename += "\"";
}
else
{
making_exe = false;
++index;
output[index].type = LNK;
output[index].plnkinf = new LNKINFO;
output[index].plnkinf->filename = argv[i];
}
}
else
{ //it's an argument
dbgmsg("argument");
int len = strlen(argv[i]);
for (int j = 2; j < len; ++j) //this loop trims off the leading AA
{
argv[i][j-2] = argv[i][j];
}
argv[i][len-2] = '\0';
if (making_exe) //no arguments for .lnks
{
if (!output[index].pexename) output[index].pexename = new string;
*output[index].pexename += " ";
*output[index].pexename += argv[i];
}
else
{
if (!output[index].plnkinf) output[index].plnkinf = new LNKINFO;
output[index].plnkinf->pointedargs += argv[i];
}
}
}
return true;
}
There are no compiling or linking errors with Dev-C++ 4.9.9.2 (set to compile a console program but in Linker Settings it is set to not create a console window.)
Does anyone see the problem? (If you can't make sense of the code, tell me and I'll put even more comments in.)
|