Именованные каналы между C# [в Unity] и C ++ - PullRequest
1 голос
/ 04 мая 2020

В настоящее время я разрабатываю сценарий в Unity, который взаимодействует с C ++ для получения потока байтов. Сейчас я работаю над примером, в котором два процесса передают стандартное сообщение, и просматривая переполнение стека, я нашел несколько интересных примеров, которые я решил использовать.

Вот код C ++ (это тот же пример предоставлено Microsoft cli 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 ++ c ода); в результате я всегда получаю «Строка запроса клиента: ????» а не «Строка запроса клиента: Привет, сервер»

  3. Это может быть самым важным: пока я не закрою сервер c ++, клиент c# не получит никакого ответа, это заблокировано ожидание. Я обратился к природе кода C ++: есть один l oop, говорящий> L oop, пока не закончится чтение, но этот l oop никогда не прерывается; другой является начальным для (;;)

Я надеюсь, что вы можете помочь мне с этим, и если вам нужны дополнительные подробности, я опубликую их, я боялся, что этот вопрос уже давно достаточно хаха.


РЕДАКТИРОВАТЬ 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 ++, что означает, что файл, который я создаю, имеет это внутри:

ответ по умолчанию от serverNULL (хотя я не знаю, почему в конце концов этот NULL)

Но я попытался поменяться " pchReply "в функции WriteFile с моей переменной raw, то есть uncompressedFile.rdbuf(), но когда я пытаюсь сохранить сторону файла C#, я сохраняю группу NULL.

Какой еще буфер нужно поставить вместо pchReply для передачи двоичной информации, которая находится внутри моего файла?

Ответы [ 2 ]

0 голосов
/ 04 мая 2020

System.String и std::string - это разные объекты, и вам нужно выполнить маршалинг между управляемым и неуправляемым типами.

Это немного болезненно, и вам лучше всего создать оболочку C ++ / CLI , Проверьте этот документ: https://docs.microsoft.com/en-us/cpp/dotnet/overview-of-marshaling-in-cpp?view=vs-2019

0 голосов
/ 04 мая 2020

Вы не можете прочитать строку C ++ как строку C#:

Я использую Pipe с CreateFile, а не CreateNamePipe, не знаю почему (не эксперт в C ++), и у меня есть канал для чтения и другой для записи. И в этом случае буфер заполняется автоматически 0x CC .. не знаю, почему

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

Side 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, если можете ... потому что если вы используете класс, это будет непросто .....

Я предлагаю вам использовать сокет вместо namePipe .. у вас будет меньше сложностей для обмена данными

...