c# - C#[在Unity中]和C++之间的命名管道

标签 c# c++ unity3d ipc

我目前正在Unity中开发一个脚本,该脚本与C++通信以便接收字节流。现在,我正在研究一个示例,其中两个进程传达一条标准消息,并且通过查看堆栈溢出,我发现了一些我决定使用的有趣示例。

这是C++代码(与Microsoft clic提供的示例相同,但我进行了一些更改以尝试了解正在发生的情况)



#include "NamedPipeWithCSharpNew.h"
#include <windows.h> 
#include <stdio.h> 
#include <tchar.h>
#include <strsafe.h>

#define BUFSIZE 512

DWORD WINAPI InstanceThread(LPVOID);
VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD);

int _tmain(VOID)
{
    BOOL   fConnected = FALSE;
    DWORD  dwThreadId = 0;
    HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
    LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");

    // The main loop creates an instance of the named pipe and 
    // then waits for a client to connect to it. When the client 
    // connects, a thread is created to handle communications 
    // with that client, and this loop is free to wait for the
    // next client connect request. It is an infinite loop.

   for(;;)
 {
        _tprintf(TEXT("\nPipe Server: Main thread awaiting client connection on %s\n"), lpszPipename);
        hPipe = CreateNamedPipe(
            lpszPipename,             // pipe name 
            PIPE_ACCESS_DUPLEX,       // read/write access 
            PIPE_TYPE_BYTE |       // byte type pipe 
            PIPE_READMODE_BYTE |   // byte-read mode 
            PIPE_WAIT,                // blocking mode 
            PIPE_UNLIMITED_INSTANCES, // max. instances  
            BUFSIZE,                  // output buffer size 
            BUFSIZE,                  // input buffer size 
            0,                        // client time-out 
            NULL);                    // default security attribute 

        if (hPipe == INVALID_HANDLE_VALUE)
        {
            _tprintf(TEXT("CreateNamedPipe failed, GLE=%d.\n"), GetLastError());
            return -1;
        }

        // Wait for the client to connect; if it succeeds, 
        // the function returns a nonzero value. If the function
        // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 

        fConnected = ConnectNamedPipe(hPipe, NULL) ?
            TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

        if (fConnected)
        {
            printf("Client connected, creating a processing thread.\n");

            // Create a thread for this client. 
            hThread = CreateThread(
                NULL,              // no security attribute 
                0,                 // default stack size 
                InstanceThread,    // thread proc
                (LPVOID)hPipe,    // thread parameter 
                0,                 // not suspended 
                &dwThreadId);      // returns thread ID 

            if (hThread == NULL)
            {
                _tprintf(TEXT("CreateThread failed, GLE=%d.\n"), GetLastError());
                return -1;
            }
            else CloseHandle(hThread);
        }
        else
            // The client could not connect, so close the pipe. 
            CloseHandle(hPipe);
 }
     return 0;
}

DWORD WINAPI InstanceThread(LPVOID lpvParam)
// This routine is a thread processing function to read from and reply to a client
// via the open pipe connection passed from the main loop. Note this allows
// the main loop to continue executing, potentially creating more threads of
// of this procedure to run concurrently, depending on the number of incoming
// client connections.
{
    HANDLE hHeap = GetProcessHeap();
    TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));
    TCHAR* pchReply = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));


    DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
    BOOL fSuccess = FALSE;
    HANDLE hPipe = NULL;

    // Do some extra error checking since the app will keep running even if this
    // thread fails.

    if (lpvParam == NULL)
    {
        printf("\nERROR - Pipe Server Failure:\n");
        printf("   InstanceThread got an unexpected NULL value in lpvParam.\n");
        printf("   InstanceThread exitting.\n");
        if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
        if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
        return (DWORD)-1;
    }

    if (pchRequest == NULL)
    {
        printf("\nERROR - Pipe Server Failure:\n");
        printf("   InstanceThread got an unexpected NULL heap allocation.\n");
        printf("   InstanceThread exitting.\n");
        if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
        return (DWORD)-1;
    }

    if (pchReply == NULL)
    {
        printf("\nERROR - Pipe Server Failure:\n");
        printf("   InstanceThread got an unexpected NULL heap allocation.\n");
        printf("   InstanceThread exitting.\n");
        if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
        return (DWORD)-1;
    }

    // Print verbose messages. In production code, this should be for debugging only.
    printf("InstanceThread created, receiving and processing messages.\n");

    // The thread's parameter is a handle to a pipe object instance. 

    hPipe = (HANDLE)lpvParam;

    // Loop until done reading
    while (1)
    {
        // Read client requests from the pipe. This simplistic code only allows messages
        // up to BUFSIZE characters in length.
        fSuccess = ReadFile(
            hPipe,        // handle to pipe 
            pchRequest,    // buffer to receive data 
            BUFSIZE * sizeof(TCHAR), // size of buffer 
            &cbBytesRead, // number of bytes read 
            NULL);        // not overlapped I/O 

        if (!fSuccess || cbBytesRead == 0)
        {
            if (GetLastError() == ERROR_BROKEN_PIPE)
            {
                _tprintf(TEXT("InstanceThread: client disconnected.\n"));
            }
            else
            {
                _tprintf(TEXT("InstanceThread ReadFile failed, GLE=%d.\n"), GetLastError());
            }
            break;
        }

        // Process the incoming message.
        GetAnswerToRequest(pchRequest, pchReply, &cbReplyBytes);
        printf("Continuing..\n"); // qua ci arriva

        // Write the reply to the pipe. 
        fSuccess = WriteFile(
            hPipe,        // handle to pipe 
            pchReply,     // buffer to write from 
            cbReplyBytes, // number of bytes to write 
            &cbWritten,   // number of bytes written 
            NULL);        // not overlapped I/O 

        if (!fSuccess || cbReplyBytes != cbWritten)
        {
            _tprintf(TEXT("InstanceThread WriteFile failed, GLE=%d.\n"), GetLastError());
            break;
        }
        printf("Continuing..\n"); // qua ci arriva

    }

    // Flush the pipe to allow the client to read the pipe's contents 
    // before disconnecting. Then disconnect the pipe, and close the 
    // handle to this pipe instance. 

    FlushFileBuffers(hPipe);
    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);

    HeapFree(hHeap, 0, pchRequest);
    HeapFree(hHeap, 0, pchReply);

    printf("InstanceThread exiting.\n");
    return 1;
}

VOID GetAnswerToRequest(LPTSTR pchRequest,
    LPTSTR pchReply,
    LPDWORD pchBytes)
    // This routine is a simple function to print the client request to the console
    // and populate the reply buffer with a default data string. This is where you
    // would put the actual client request processing code that runs in the context
    // of an instance thread. Keep in mind the main thread will continue to wait for
    // and receive other client connections while the instance thread is working.
{
    _tprintf(TEXT("Client Request String:\"%s\"\n"), pchRequest);

    // Check the outgoing message to make sure it's not too long for the buffer.
    if (FAILED(StringCchCopy(pchReply, BUFSIZE, TEXT("default answer from server"))))
    {
        *pchBytes = 0;
        pchReply[0] = 0;
        printf("StringCchCopy failed, no outgoing message.\n");
        return;
    }
    *pchBytes = (lstrlen(pchReply) + 1) * sizeof(TCHAR);
}


这是C#代码:
 private static string pipeName = "mynamedpipe";

[...]

 void Update()
    {
        if (Input.GetKey(KeyCode.C))
        {
            using (var client = new NamedPipeClientStream(pipeName))
            {
                client.Connect(100);
                var writer = new StreamWriter(client);
                var request = "Hi, server.";
                writer.WriteLine(request);
                writer.Flush();

                var reader = new StreamReader(client);
                var response = reader.ReadLine();
                Debug.Log("Response from server: " + response);
            }

        }

    }

的问题是: POST已更新,请不要在编辑时向下查询这些问题
  • 我不明白在哪里可以看到 pchReply 的内容或如何编辑它,注释说这是默认数据字符串,但是当数据交换完成后,C#程序读取的字符串是“d”。
  • 当C++服务器从C#接收到请求字符串时,应该是嗨,服务器,它应该在函数GetAnswerToRequest中打印它(C++代码的最后一个)。结果,我总是得到“客户端请求字符串:????”而不是“客户端请求字符串:嗨,服务器”
  • 这可能是最关键的:在我关闭c++服务器之前,C#客户端没有任何响应,它被阻止等待。我针对C++代码的本质进行了处理:有一个循环说> Loop,直到完成阅读为止,但此循环永不中断。另一个是初始的for(;;)

  • 希望您能对此提供帮助,如果您需要更多详细信息,我会在此发布它们,我担心这个问题已经足够长了哈哈。

    编辑1:

    感谢您的答复,我关注的事实是,无论在C#还是C++中,我都不需要任何字符串类型,我需要将二进制文件从C++端传输到C#。这是我更新的内容:

    C++
      GetAnswerToRequest(pchRequest, pchReply, &cbReplyBytes);
     std::ifstream uncompressedFile;
            uncompressedFile.open("C:/Users/prova.p3d",std::ifstream::binary);
            std::streambuf* raw = uncompressedFile.rdbuf();
    
     fSuccess = WriteFile(
                hPipe,        // handle to pipe 
                pchReply,     // buffer to write from
                cbReplyBytes, // number of bytes to write 
                &cbWritten,   // number of bytes written 
                NULL);        // not overlapped I/O 
    
    
    VOID GetAnswerToRequest(LPTSTR pchRequest,
        LPTSTR pchReply,
        LPDWORD pchBytes)
    {
    
        if (FAILED(StringCchCopy(pchReply, BUFSIZE, TEXT("default answer \n from server"))))
        {
            *pchBytes = 0;
            pchReply[0] = 0;
            printf("StringCchCopy failed, no outgoing message.\n");
            return;
        }
        *pchBytes = (lstrlen(pchReply) + 1) * sizeof(TCHAR);
    }
    

    C#:
     byte[] buffer = new byte[512000];
                    int bytesRead = client.Read(buffer, 0, 512000); 
    
                    int ReadLength = 0;
                    for (int i = 0; i < bytesRead; i++)
                    {
                            ReadLength++;
                    }
    
                    if (ReadLength >0)
                    {
                        byte[] Rc = new byte[ReadLength];
                        Buffer.BlockCopy(buffer, 0, Rc, 0, ReadLength);
    
                        using(BinaryWriter binWriter = new BinaryWriter(File.Open("C:/Users/provolettaCS.p3d",FileMode.Create)))
                        {
                            binWriter.Write(Rc); 
                            binWriter.Close();
                        }
    
                        buffer.Initialize();
    

    现在,这可以在C++的标准响应下正常工作,这意味着我创建的文件包含以下内容:

    default answer from serverNULL (Don't know why there's that NULL in the end, though)



    但是我试图用自己的变量pchReply交换WriteFile函数中的“raw”,即uncompressedFile.rdbuf(),但是当我尝试保存文件C#时,我保存了一堆NULL。

    为了传输文件中的二进制信息,我还需要放置什么其他缓冲区而不是pchReply

    最佳答案

    System.Stringstd::string是不同的对象,您需要在托管类型和非托管类型之间进行调度。

    这有点痛苦,您最好的选择是创建一个C++ / CLI包装器。检查此文件:
    https://docs.microsoft.com/en-us/cpp/dotnet/overview-of-marshaling-in-cpp?view=vs-2019

    关于c# - C#[在Unity中]和C++之间的命名管道,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61590037/

    相关文章:

    windows - 在失焦时接受输入

    c# - 多个表中具有相同名称的列导致 SubSonic Select 出现问题

    c++ - 我可以将智能指针与互斥锁成员一起使用吗?

    Android 不需要权限 - Marshmallow 6.0.1 Unity

    c++ - 一个txt文档的简单加密解密

    c++ - 多次从第一行读取文本文件(C++)

    c# - 清零后无法进行定时器循环

    C# 字符串拆分 - 无结束分隔符

    c# - 串行端口或 USB 端口?

    c# - SQL Linq .Take() 来自巨大数据库的最新 20 行,性能方面