Inter-Process Communication (IPC) for Windows with C

Roman Rdgz picture Roman Rdgz · May 10, 2012 · Viewed 13k times · Source

I have an old program written in C with Microsoft Visual C++, and I need to implement some kind of "keepalive", so I am able to receive it thought interprocess communication into a new program which will kill and re-launch the first one if no msg has been received in the last 5 seconds.

The problem is that I have been looking for any tutorial or example of IPC for Windows in C language, but almost everything I find is for C++.

Any help or resource?

EDIT: As @Adriano suggested in answers, I'm trying to use Shared Memory. But the launcher program is being terminated by Windows due to some kind of exception I'm not being able to catch. Happens when calling CopyMemory.

The code is the following:

#include "stdafx.h"
#include "windows.h"
#include "iostream"
using namespace std;

int launchMyProcess();
void killMyProcess();
bool checkIfMyProcessIsAlive();

STARTUPINFO sInfo;
PROCESS_INFORMATION pInfo;
HANDLE mappedFile;
LPVOID pSharedMemory;
long lastReceivedBeatTimeStamp;
const int MSECONDS_WITHOUT_BEAT = 500;
const LPTSTR lpCommandLine = "MyProcess.exe configuration.txt";


    int main(int argc, char* argv[])
    {
        mappedFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(int), "Global\\ActivityMonitor");
        LPVOID pSharedMemory = MapViewOfFile(mappedFile, FILE_MAP_READ, 0, 0, sizeof(int));
        if(!launchMyProcess()){
            cout<<"Error creating MyProcess.exe"<<endl;
            UnmapViewOfFile(pSharedMemory);
            CloseHandle(mappedFile);
            return -1;
        }
        while(true){
            Sleep(100);

            if(!checkIfMyProcessIsAlive()){
                cout<<"Relaunching MyProcess...";
                killMyProcess();
                if(!launchMyProcess()){
                    cout<<"Error relaunching MyProcess.exe"<<endl;
                    UnmapViewOfFile(pSharedMemory);
                    CloseHandle(mappedFile);
                    return -1;
                }
            }
        }

        UnmapViewOfFile(pSharedMemory);
        CloseHandle(mappedFile);
        return 0;
    }


    bool checkIfMyProcessIsAlive()
    {
        static int volatile latestMagicNumber = 0;
        int currentMagicNumber = 0;

        CopyMemory(&currentMagicNumber, pSharedMemory, sizeof(int));

        if(currentMagicNumber != latestMagicNumber){
            latestMagicNumber = currentMagicNumber;
            return true;
        }
        return false;
    }

    int launchMyProcess()
    {
        ZeroMemory(&sInfo, sizeof(sInfo));
        sInfo.cb = sizeof(sInfo);
        ZeroMemory(&pInfo, sizeof(pInfo));

        return CreateProcess(NULL, lpCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &sInfo, &pInfo);
    }

    void killMyProcess()
    {
        TerminateProcess(pInfo.hProcess, 0);
        CloseHandle(pInfo.hProcess);
        CloseHandle(pInfo.hThread);
        Sleep(3000);
    }

Answer

Adriano Repetti picture Adriano Repetti · May 10, 2012

If your old C application has a message pump (because it has an UI) maybe the simpliest way to check if it's alive or not is IsHungAppWindow() function and Windows will do the stuff for you.

If this is not your case and you need IPC there are many options, it depends on what kind of IPC mechanism you want to use. Here I'll just list some resources.

For an overview of IPC techniques: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365574(v=vs.85).aspx

Some examples:

EDIT
I think a small example will clarify much more than tons of words. In this example I'll use shared memory but you can use whatever you prefer (and you feel more comfortable with). It's untested so please use it just as reference.

The MONITOR process, should be started first.

VOID CALLBACK CheckItIsAlive(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
    static int volatile latestMagicNumber = 0;

    int currentMagicNumber = 0;
    CopyMemory(&currentMagicNumber, lpParam, sizeof(int));

    if (currentMagicNumber != latestMagicNumber)
        latestMagicNumber = currentMagicNumber;
    else
    {
        // Do something, it's hanged
    }
}

void main()
{
    // Shared memory used to communicate with the other process
    HANDLE mappedFile = CreateFileMapping(INVALID_HANDLE_VALUE,
        NULL, PAGE_READWRITE, 0, sizeof(int), "Global\\MyActivityMonitor");

    LPVOID pSharedMemory = MapViewOfFile(mappedFile,
        FILE_MAP_READ, 0, 0, sizeof(int));

    // Thread used to check activity
    HANDLE queue = CreateTimerQueue();
    HANDLE hTimer = NULL;
    CreateTimerQueueTimer(&hTimer, queue, 
        (WAITORTIMERCALLBACK)CheckItIsAlive, pSharedMemory,
        0, 5000, WT_EXECUTEDEFAULT);

    // Do your work here...

    // Clean up
    DeleteTimerQueue(queue);

    UnmapViewOfFile(pSharedMemory);
    CloseHandle(mappedFile);
}

The MONITORED process, it'll signal its activity to the Monitor process.

VOID CALLBACK NotifyImAlive(PVOID lpParam, BOOLEAN TimerOrWaitFired)
{
    static int volatile counter = 1;

    int tick = counter++;
    CopyMemory(lpParam, &tick, sizeof(int));
}

void main()
{
    // Shared memory used to communicate with the other process
    HANDLE mappedFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE,
        "Global\\MyActivityMonitor");

    LPVOID pSharedMemory = MapViewOfFile(mappedFile,
        FILE_MAP_WRITE, 0, 0, sizeof(int));

    // Thread used to signal activity
    HANDLE queue = CreateTimerQueue();
    HANDLE hTimer = NULL;
    CreateTimerQueueTimer(&hTimer, queue, 
        (WAITORTIMERCALLBACK)NotifyImAlive, pSharedMemory,
        0, 5000, WT_EXECUTEINTIMERTHREAD);

    // Do your work here...

    // Clean up
    DeleteTimerQueue(queue);

    UnmapViewOfFile(pSharedMemory);
    CloseHandle(mappedFile);
}

Shared memory is a pretty lightweight resource and you can use whatever you prefer for your timers (if timing isn't a strict requirement you can do some kind of idle processing. Personally I like this 'cause you won't need to lock any thread and probably you have an idle time processing thread).

Timer functions are supported starting from Windows 2000, be sure that _WIN32_WINNT macro is defined with 0x0500 (or more).

Addendum
I didn't mentioned in the list because they exist only in newer versions of OS but you may even use condition variables. Windows 8 will support a very useful WaitOnAddress function but it's still the future so I think you can't use it.