[英]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);
}
}
}
問題是:帖子已更新,請不要回答這些,但請查看向下滾動的編輯
我不明白在哪里可以看到pchReply的內容或如何編輯它,評論說它是默認數據字符串,但是當數據交換完成時,C# 程序讀取的字符串是“d”。
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 ); 結果,我總是得到“客戶端請求字符串:????” 而不是“客戶端請求字符串:嗨,服務器”
這可能是最關鍵的:在我關閉 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.String
和std::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.