Interprocess communication between C# and C++
Asked Answered
T

4

10

I'm writing a bot for a game, which has a C++ API interface (ie. methods in a Cpp dll get called by the game when events occur, the dll can call back methods in the game to trigger actions).

I don't really want to write my bot in C++, I'm a fairly experienced C# programmer but I have no C++ experience at all. So, the obvious solution is to use ipc to send event to a C# program, and send actions back to the C++ one, that way all I need to write in C++ is a basic framework for calling methods and sending events.

What would be the best way to do this? Sample code would be greatly appreciated as I no have particular desire to learn C++ at this point!

Topography answered 14/1, 2010 at 12:49 Comment(1)
Yes. I've already written a blocking version using pipes. But none of the methods the game calls are allowed to block - and I think asynchronous ipc would be pushing it a bit. To be honest, I was hoping for some kind of framework or library that has already been written that I could use, since I only want to do very basic IPCTopography
B
6

One solution is to create a managed C++ class library with regular __declspec(dllexport) functions which call managed methods in a referenced C# class library.

Example - C++ code file in managed C++ project:

#include "stdafx.h"

__declspec(dllexport) int Foo(int bar) 
{
    csharpmodule::CSharpModule mod;
    return mod.Foo(bar);
}

C# Module (separate project in solution):

namespace csharpmodule
{
    public class CSharpModule
    {
        public int Foo(int bar)
        {
            MessageBox.Show("Foo(" + bar + ")");
            return bar;
        }
    }
}

Note that I am demonstrating that this is an actual .NET call by using a System.Windows.Forms.MessageBox.Show call.

Sample basic (non-CLR) Win32 console application:

__declspec(dllimport) int Foo(int bar);

int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << Foo(5) << std::endl;
    return 0;
}

Remember to link the Win32 console application with the .lib file resulting from the build of the managed C++ project.

Briney answered 14/1, 2010 at 14:38 Comment(0)
L
9

There are lots of different ways of doing IPC in Windows. For C# to C++, I'd be tempted to use Sockets as the API under both C++ (WinSock is OK once you get your head around it) and C# is pretty easy.

Named Pipes might be better though if you don't want to use sockets, and were designed specifically for IPC. The API under C++ seems pretty simple, example here.

Luminiferous answered 14/1, 2010 at 12:56 Comment(5)
IPC on same machine is definitely a case for named pipes, sockets are sub-optimal.Briney
I have a basic version working using named pipes already, but it blocks, which causes the game to crash. I get the feeling asynchronous IO would be pushing my C++ capabilities a bit too much :/Topography
Async IO shouldn't be too hard, I'm pretty sure you can use pipes without blocking. If not, be prepared for a long night! @Aviad: Yes sockets probably aren't as good but I wanted to offer an answer that contrasted Aggelo's slightly :)Luminiferous
Well there is a method "PeekNamedPipe" which tells you how much data is in the pipe, but that seems to block too :/ I can't see any other way to do it, except with asynch. I guess I'll have to do that :/Topography
codeproject.com/KB/dotnet/NamedPipeOverlapped.aspx - found that after a quick Google, it's in C# but gives you a good idea of how to do simple async IPC with pipes.Luminiferous
B
6

One solution is to create a managed C++ class library with regular __declspec(dllexport) functions which call managed methods in a referenced C# class library.

Example - C++ code file in managed C++ project:

#include "stdafx.h"

__declspec(dllexport) int Foo(int bar) 
{
    csharpmodule::CSharpModule mod;
    return mod.Foo(bar);
}

C# Module (separate project in solution):

namespace csharpmodule
{
    public class CSharpModule
    {
        public int Foo(int bar)
        {
            MessageBox.Show("Foo(" + bar + ")");
            return bar;
        }
    }
}

Note that I am demonstrating that this is an actual .NET call by using a System.Windows.Forms.MessageBox.Show call.

Sample basic (non-CLR) Win32 console application:

__declspec(dllimport) int Foo(int bar);

int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << Foo(5) << std::endl;
    return 0;
}

Remember to link the Win32 console application with the .lib file resulting from the build of the managed C++ project.

Briney answered 14/1, 2010 at 14:38 Comment(0)
P
3

In such occasion, I would like to see a C++/CLI party and a C# one using the .NET Framework's named pipes.

Phira answered 14/1, 2010 at 12:53 Comment(0)
G
1

I've already written a blocking version using pipes. But none of the methods the game calls are allowed to block

This issue is solved by running the blocking pipe functions in a separate thread. I found this issue looking for a better solution, but here is the basic idea of what I currently have:

#include <mutex>
// #include ...

HANDLE pipe;
char* pipeBuffer;
int pipeSize;
bool pipeHasData = false;
std::mutex m;
std::condition_variable cv;

void named_pipe()
{
    // optional
    int pid = _getpid();
    std::wstring pipe_name = L"\\\\.\\pipe\\Game-" + std::to_wstring(pid);
    
    // (very) shortened loop to show mutex lock
    // add your own error checking and retrying
    hPipe = init_named_pipe(pipe_name);
    while (true)
    {
        if (PeekNamedPipe(hPipe, NULL, 0, NULL, &bytesAvailable, NULL) == FALSE)
        {
            break;
        }

        pipeBuffer = buffer;
        pipeSize = dwRead;

        pipeHasData = true;
        {
            std::unique_lock<std::mutex> lock(m);
            cv.wait(lock);
        }
    }
}

void (__fastcall* oGame_Tick)(float deltaTime);
void __fastcall hkGame_Tick(float deltaTime)
{
    oGame_Tick(deltaTime);
    
    if (pipeHasData)
    {
        // parse the packet received in pipeBuffer and call your game actions
        // respond by writing back to pipe
        ProcessPipe(pipe, pipeBuffer, pipeSize);

        {
            m.lock();
            pipeHasData = false;
            pipeBuffer = nullptr;
            pipeSize = NULL;
            m.unlock();
            cv.notify_all();
        }
    }
}

// called once on dll attach
void __cdecl init()
{
    // hook gameloop tick function here
    
    // start loop in new thread
    std::thread t1(named_pipe);
    t1.detach();
}

Things to note:

  • This code will only process 1 packet per frame and will block until a response is sent. Instead you can use a Queue system where packets sent through the pipe are queued up from your pipe loop, and then processed in the main game thread (this is exactly how the games themselves do it!)
  • Limited by frame rate
  • Some game actions are only meant to be called once per frame and can otherwise crash the client. I've had this issue with Unreal Engine games (specifically when toggling UI related things twice in a single tick)
Gargle answered 10/11, 2022 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.