C#和C++之间的进程通信

10

我正在为一款游戏编写机器人,该游戏有一个C++ API接口(即当事件发生时,游戏调用Cpp dll中的方法,dll可以回调游戏中的方法来触发动作)。

我不想用C++编写我的机器人,我是一位相当有经验的C#程序员,但我完全没有C++的经验。因此,显而易见的解决方案是使用ipc将事件发送到C#程序,并将动作发送回C++程序,这样我只需要在C++中编写一个基本的框架,以调用方法和发送事件即可。

如何做到最好呢?如果能提供示例代码,那就非常感激了,因为目前我没有学习C++的特别愿望!


3
是的。我已经使用管道编写了一个阻塞版本。但游戏调用的所有方法都不允许阻塞 - 而且我认为异步IPC可能有些过分。 老实说,我希望能找到某种已经编写好的框架或库来使用,因为我只想做非常基本的IPC。 - Martin
4个回答

9

在Windows中,有许多不同的方法可以进行进程间通信(IPC)。对于C#和C++之间的通信,我倾向于使用Sockets作为API,因为在C++中(一旦你理解了它),WinSock是可以的,而在C#中则非常容易。

命名管道如果您不想使用sockets,则可能更好,因为它们专门用于IPC。在C++下的API似乎相当简单,在此处有一个示例here


1
在同一台机器上的IPC绝对是使用命名管道的情况,套接字则不够优化。 - Aviad P.
我已经使用命名管道实现了基本版本,但它会阻塞,导致游戏崩溃。我感觉异步IO可能会让我的C++能力有些吃不消 :/ - Martin
异步IO应该不会太难,我相信你可以使用无阻塞的管道。如果不行,准备度过漫长的一晚!@Aviad:是的,套接字可能不是很好,但我想提供一个与Aggelo略有不同的答案 :) - mdm
好的,有一个名为“PeekNamedPipe”的方法可以告诉你管道中有多少数据,但那似乎也会阻塞 :/ 除了使用异步方式,我看不到其他的方法来完成它。我想我必须这样做 :/ - Martin
我发现这篇文章在谷歌上,它是关于使用C#来实现基本异步进程间通信的管道。阅读这篇文章可以让你了解如何使用管道实现简单的异步IPC。 - mdm

6

一种解决方案是创建一个托管的C++类库,其中包含常规的__declspec(dllexport)函数,这些函数调用引用的C#类库中的托管方法。

示例 - 托管C++项目中的C++代码文件:

#include "stdafx.h"

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

C#模块(解决方案中的独立项目):

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

请注意,我正在使用System.Windows.Forms.MessageBox.Show调用来演示这是一个实际的.NET调用。
样例基本(非CLR)Win32控制台应用程序:
__declspec(dllimport) int Foo(int bar);

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

记得使用从托管C++项目构建的.lib文件链接Win32控制台应用程序。

3
在这种情况下,我希望看到一个使用.NET Framework命名管道的C++/CLI团队和一个C#团队。

1
我已经使用管道编写了一个阻塞版本。但是游戏调用的所有方法都不允许阻塞。
通过在单独的线程中运行阻塞管道函数来解决此问题。我正在寻找更好的解决方案,但目前的基本思路如下:
#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();
}

需要注意的事项:

  • 这段代码每帧只能处理1个数据包,并且会一直阻塞直到响应被发送。相反,你可以使用队列系统,将通过管道发送的数据包在管道循环中排队,然后在主游戏线程中进行处理(这正是游戏本身所做的!)
  • 受帧速率限制
  • 某些游戏操作每帧只能调用一次,否则可能导致客户端崩溃。我曾在虚幻引擎游戏中遇到过这个问题(特别是在同一帧内两次切换UI相关内容时)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接