1

I'm working in Visual Studio 2017. In my current solution, I have 2 active projects. The 2nd project depends on the 1st project and my 2nd project is my startup application. When I run or compile my 2nd project, there is already a compiled executable of my 1st project that is within my solutions directory...

Here is the directory hierarchy of my solutions - projects:

  • "SolutionName/Project1/"
    • This contains the source code for Project1
  • "SolutionName/Project2/"
    • This contains the source code for Project2
  • "SolutionName/x64/"
    • This contains the x64 versions:
  • "SolutionName/x64/Debug"
    • This contains the x64 debug builds and executables
  • "SolutionName/x64/Release"
    • This contains the x64 release builds and executables

When I run the application with the 2nd project as my startup... The code compiles and runs fine, however, it doesn't appear to be executing the executable from the 1st project...

Here is my main.cpp

#include <Windows.h>
#include <exception>
#include <stdio.h>
#include <tchar.h>
#include <cstdint>
#include <iostream>

uint32_t runProgram(LPCSTR lpApplicationName) {
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;

    // Set the size of the structures
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Run the program
    CreateProcessA(
        lpApplicationName,  // the path
        NULL,               // Command line
        NULL,               // Process handle not inheritable
        NULL,               // Thread handle not inheritable
        FALSE,              // Set handle inheritance to FALSE
        CREATE_NEW_CONSOLE, // Opens file in seperate console
        NULL,               // Use parent's environment block
        NULL,               // Use parent's starting directory
        &si,                // Pointer to STARTUPINFO structure
        &pi                 // Pointer to PROCESS_INFORMATION structure
    );

    uint32_t cache_size = 0;

    WaitForSingleObject(pi.hProcess, INFINITE);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return cache_size;
}

int main() {
    try {
        const uint32_t cache_size = runProgram("CacheQuery.exe");
        std::cout << cache_size << '\n';
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << "\n\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

If I build my solution in Debug mode, both executables are in the same directory for Debug and if I build my solution in Release mode, again both release versions of my executables are in the same directory...

I am getting an output of 0 in the console so I know that runProgram() is being called and returning, but I'm expecting another console to be opened with the displayed results from the invoked program with its own console handle from its own process. However, I'm not seeing it being invoked or called. CacheQuery.exe which is my 1st project and it doesn't seem to be executed...

I'm a bit new to this part of the Windows API, so I'm not sure if I'm doing this correctly... I need for my second project to call and run the 1st project... This is part 1 of my question. Once I get this resolved and know that project 2 is calling and executing project 1, then I'll ask my next question about how to retrieve the value that the called executable returns upon exit...

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 1
    You are not doing any error handling when calling `CreateProcessA()`. When it returns `FALSE`, check `GetLastError()`. You are creating the process using a *relative file path*, so chances are `CreateProcessA()` can't find `CacheQuerry.exe`, likely because the calling process' *current working directory* is not what you are expecting it to be. You can check that with `GetCurrentDirectory()`. ALWAYS use absolute paths. If you want to use paths that are relative to the EXE of the current process, retrieve that process's path using `GetModuleFileName(NULL)` and then tweak it as needed. – Remy Lebeau Aug 04 '20 at 23:51
  • @RemyLebeau I was kind of thinking on those lines... but that's what I'm trying to figure out... Am I passing in the right string literal into the function call to execute it? – Francis Cugler Aug 04 '20 at 23:53
  • I created a temp variable within the `runProgram()` function and assigned it to `GetLastError()` and then I print it to the console, it is giving me a value of 2. – Francis Cugler Aug 04 '20 at 23:55
  • 1
    [Error Code 2](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-) is `ERROR_FILE_NOT_FOUND`. Again, double-check with `GetCurrentDirectory()` that the *current working directory* is what you are expecting it to be. It likely is not. IIRC, Visual Studio DOES NOT set the CWD to the same folder as the EXE it launches. If the 2 EXEs are in the same folder then you should use `GetModuleFileName(NULL)` to get the full path of the EXE of the current process, then change the filename of that path to the EXE of the other app, then use that full path with `CreateProcessA()` – Remy Lebeau Aug 04 '20 at 23:56
  • @RemyLebeau I was suspecting that, and I was in the process of looking it up before you commented... – Francis Cugler Aug 04 '20 at 23:57
  • @RemyLebeau If it can not find `"CacheQuerry.exe"` what's my next step? Do I change my project's working directory or do I change the string literal for the file name? – Francis Cugler Aug 05 '20 at 00:00
  • I already told you what to do. Go re-read my comments again. Refresh the page if needed. – Remy Lebeau Aug 05 '20 at 00:00
  • Instead of using the extra function calls to check for the paths, and modifying them directly... In my 2nd project's settings, I changed the working directory to point where the executables are being built. It is now running the process in the background... – Francis Cugler Aug 05 '20 at 00:05
  • That is not a reliable long-term solution. You really should construct the target path in code instead, to ensure you always have what you are expecting, regardless of the environment. I have posted an answer now to show how to do this. – Remy Lebeau Aug 05 '20 at 00:21
  • @Remy Lebeau I understand that, and I can change that in a revision. Currently I'm just trying to get it to work now as I'm working on another function that relies on this to work... I can come back to this and change it to find the target path... – Francis Cugler Aug 05 '20 at 00:23

1 Answers1

1

Aside from the lack of any error handling on CreateProcessA(), you are trying to launch CacheQuery.exe using a relative path, so the issue is most likely that the current working directory of the calling process is not what you are expecting it to be, causing CreateFileA() to fail if it can't find CacheQuery.exe.

You should never rely on relative paths, always use absolute paths instead!

Assuming the two EXE files are in the same folder, then you can try something like this:

#include <Windows.h>
#include <shlwapi.h>
#include <cstdio>
#include <tchar.h>
#include <cstdint>
#include <iostream>
#include <string>
#include <sstream>

#pragma comment(lib, "Shlwapi.lib")

void ThrowWin32Error(const char *funcname, DWORD errCode) {
    std::ostringstream oss;
    oss << funcname << " failed.";
    if (errCode != 0) {
        oss << " Error " << errCode;
    }
    throw std::runtime_error(oss.str());
}

void CheckWin32Error(const char *funcname, bool result) {
    if (!result) ThrowWin32Error(funcname, 0);
}

void CheckWin32ErrorCode(const char *funcname, bool result) {
    if (!result) ThrowWin32Error(funcname, GetLastError());
}

std::string getPathToCacheQuery() {
    char path[MAX_PATH+16] = {};

    DWORD size = GetModuleFileNameA(NULL, path, MAX_PATH);
    if (size == MAX_PATH)
        ThrowWin32Error("GetModuleFileNameA", ERROR_INSUFFICIENT_BUFFER);
    else
        CheckWin32ErrorCode("GetModuleFileNameA", size > 0);

    CheckWin32Error("PathRemoveFileSpecA",
        PathRemoveFileSpecA(path)
    );

    CheckWin32Error("PathAppendA",
        PathAppendA(path, "CacheQuery.exe")
    );

    return path;
}


uint32_t runProgram(const std::string &ApplicationName) {
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;

    // Set the size of the structures
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Run the program
    CheckWin32ErrorCode("CreateProcessA",
        CreateProcessA(
            ApplicationName.c_str(),// the path
            NULL,                   // Command line
            NULL,                   // Process handle not inheritable
            NULL,                   // Thread handle not inheritable
            FALSE,                  // Set handle inheritance to FALSE
            CREATE_NEW_CONSOLE,     // Opens file in seperate console
            NULL,                   // Use parent's environment block
            NULL,                   // Use parent's starting directory
            &si,                    // Pointer to STARTUPINFO structure
            &pi                     // Pointer to PROCESS_INFORMATION structure
        )
    );

    uint32_t cache_size = 0;

    WaitForSingleObject(pi.hProcess, INFINITE);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return cache_size;
}

int main() {
    try {
        std::string path = getPathToCacheQuery();
        const uint32_t cache_size = runProgram(path);
        std::cout << cache_size << '\n';
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << "\n\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770