簡體   English   中英

C# [in Unity] 和 C++ 之間的命名管道

[英]Named Pipes between C# [in Unity] and C++

我目前正在 Unity 中開發一個與 C++ 通信的腳本,以便接收 stream 字節。 現在我正在研究一個示例,其中兩個進程通信標准消息,並且通過堆棧溢出,我發現了一些我決定使用的有趣示例。

這是 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);
            }

        }

    }

問題是:帖子已更新,請不要回答這些,但請查看向下滾動的編輯

  1. 我不明白在哪里可以看到pchReply的內容或如何編輯它,評論說它是默認數據字符串,但是當數據交換完成時,C# 程序讀取的字符串是“d”。

  2. When the C++ server receives the request string from C#, that should be Hi, server , it should print it in the function GetAnswerToRequest ( the last one of C++ code ); 結果,我總是得到“客戶端請求字符串:????” 而不是“客戶端請求字符串:嗨,服務器”

  3. 這可能是最關鍵的:在我關閉 c++ 服務器之前,c# 客戶端沒有得到任何響應,它被阻塞等待。 我把它解決了 C++ 代碼的性質:有一個循環說 >Loop until done reading,但這個循環永遠不會中斷; 另一個是初始 for(;;)

我希望你能幫助我,如果你需要更多細節我會發布它們,我擔心這個問題已經夠長了哈哈。


編輯1:

Thank you for the responses, I am focusing on the fact that I don't need any string type whatsoever neither in C# or C++, I need to transmit a binary file from C++ side to 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++ 的標准響應,這意味着我創建的文件里面有這個:

serverNULL 的默認答案(雖然不知道為什么最后會有 NULL)

但是我嘗試將WriteFile function 中的“ pchReply ”與我的變量raw交換,即uncompressedFile.rdbuf()但是當我嘗試保存文件 C# 端時,我保存了一堆 Z6C3E2926B4D48245D

為了傳輸文件中的二進制信息,我還需要放置什么其他緩沖區而不是pchReply

您無法像字符串 C# 一樣讀取字符串 C++:

我正在使用 Pipe 和 CreateFile 而不是 CreateNamePipe,不知道為什么(不是 C++ 專家),我有一個 pipe 用於讀取和另一個用於寫入。 在這種情況下,緩沖區會自動填充 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 ..因為如果您使用 class 這並不容易.....

我建議您使用套接字而不是 namePipe .. 交換數據的困難會更少

System.Stringstd::string是不同的對象,您需要在托管類型和非托管類型之間編組。

這有點痛苦,最好的辦法可能是創建一個 C++/CLI 包裝器。 檢查此文檔: https://docs.microsoft.com/en-us/cpp/dotnet/overview-of-marshaling-in-cpp?view=vs-2019

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM