C#(在Unity中)和C++之间的命名管道

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

本文关键字:之间 管道 C++ Unity      更新时间:2024-05-10

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

这是C++代码(与微软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 %sn"), 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#接收到请求字符串时,应该是Hi,server,它应该在函数GetAnswerToRequest(C++代码的最后一个(中打印它;因此,我总是得到"客户端请求字符串:???",而不是"客户端请求串:嗨,服务器">

  3. 这可能是最关键的:在我关闭c++服务器之前,c#客户端没有得到任何响应,它被阻止等待。我把它归结为C++代码的本质:有一个循环说>loop until done reading,但这个循环从未中断;另一个是(;;(的首字母

我希望你能帮我,如果你需要更多的细节,我会发布它们,我担心这个问题已经足够长了,哈哈。


编辑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(

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

为了传输文件中的二进制信息,我还需要放什么缓冲区来代替pchReply

System.Stringstd::string是不同的对象,需要在托管类型和非托管类型之间封送。

这有点麻烦,最好的办法可能是创建一个C++/CLI包装器。检查此文档:https://learn.microsoft.com/en-us/cpp/dotnet/overview-of-marshaling-in-cpp?view=vs-2019年

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

我使用的是带有CreateFile的Pipe,而不是CreateNamePipe,不知道为什么(不是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。。因为如果你使用类,那就不容易了。。。。。

我建议你用插座代替管道。。你交换数据的困难就会减少