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

问题描述 投票:0回答:1

我目前正在Unity中开发一个与C++通信以接收字节流的脚本。目前我正在开发一个样本,在这个样本中,两个进程可以进行标准的消息交流,通过Stack overflow我发现了一些有趣的例子,我决定使用。

下面是C++的代码(和微软给出的例子是一样的 点击 但我做了一些改变,以尝试了解发生了什么)



#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);
            }

        }

    }

这个 问题 是。帖子是更新的,请不要回答这些,但看看编辑向下滚动。

  1. 我不明白我在哪里可以看到内容的 pchReply 或者说我怎么编辑,注释中说是默认的数据字符串,但是当数据交换完成后,C#程序读取的字符串是 "d"。

  2. 当C++服务器收到C#的请求字符串时,应该是 你好,服务器 ,它应该在函数GetAnswerToRequest(C++代码的最后一个)中打印出来,结果我总是得到 "客户端请求字符串:??? "而不是 "客户端请求字符串。Hi, server"

  3. 这可能是最关键的:在我关闭c++服务器之前,c#客户端没有得到任何响应,是阻塞等待。我是针对c++代码的性质来解决的:有一个循环说>Loop直到读完为止,但是这个循环从来没有中断过;另一个是初始的for(;;)

希望你能帮我解决这个问题,如果你需要更多的细节,我会贴出来,我怕这个问题已经够长了,哈哈。


EDIT 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 (不知道为什么最后会有个NULL)

但我试着换了"pchReply"中 WriteFile 函数与我的变量 raw那就是 uncompressedFile.rdbuf() 但当我尝试保存C#方面的文件时,我保存了一堆NULL。

我还需要放什么其他缓冲区来代替 pchReply 以便传输文件中的二进制信息?

c# c++ unity3d ipc
1个回答
0
投票

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

这有点麻烦,你最好的选择可能是创建一个C++CLI包装器。查看这个文档。https:/docs.microsoft.comen-uscppdotnetoverview-of-marshaling-in-cpp?view=vs-2019。


0
投票

你不能像读取字符串C#那样读取字符串C++。

我使用的是CreateFile管道而不是CreateName管道,不知道为什么(不是C++专家),我有一个管道用于读取,另一个用于写入。而且在这种情况下,缓冲区会自动填充0xCC...不知道为什么。

hPipe1=CreateFile(lpszPipename1,    GENERIC_WRITE ,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
hPipe2=CreateFile(lpszPipename2,    GENERIC_READ ,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);

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

      //memset(pchReply, 0xCC, BUFSIZE);

在C#侧你必须读取字节

        using (var client = new NamedPipeClientStream(pipeName))
        {
          client.Connect(100);
          ASCIIEncoding encoder = new ASCIIEncoding();
          var writer = new StreamWriter(client);
          var request = "Hi, server.";
          writer.WriteLine(request);
          writer.Flush();

            byte[] buffer = new byte[512];
            int bytesRead = client.Read(buffer, 0, 512);

            int ReadLength = 0;
            for (int i = 0; i < 512; i++)
            {
            if (buffer[i].ToString("x2") != "cc")//end char?
            {
                ReadLength++;
            }
            else
                break;
            }
            if (ReadLength > 0)
            {
            byte[] Rc = new byte[ReadLength];
            Buffer.BlockCopy(buffer, 0, Rc, 0, ReadLength);

            Debug.Log("C# App: Received " + ReadLength +" Bytes: "+ encoder.GetString(Rc, 0, ReadLength));
            buffer.Initialize();
            }
        }

所以你必须把所有的字符从C++翻译成C#......如果可以的话,尽量使用ascii......因为如果你使用类的话,是不容易的......。

如果可以的话,尽量使用ascii,因为如果你使用类,就不容易了......。

我建议你使用socket而不是namePipe......你会减少交换数据的困难。

© www.soinside.com 2019 - 2024. All rights reserved.