我在 C 中有以下结构的数据:
struct data
{
unsigned int count;
unsigned int total;
};
我能够使用命名管道将字符串数据从 C 发送到 C#。但是现在我需要发送这个结构。
谁能帮助我使用命名管道将此信息从 C 传递到 C#?
这是我引用的示例 C 程序:
#include <windows.h>
#include <stdio.h>
HANDLE fileHandle;
void ReadString(char* output) {
ULONG read = 0;
int index = 0;
do {
ReadFile(fileHandle, output + index++, 1, &read, NULL);
} while (read > 0 && *(output + index - 1) != 0);
}
int main()
{
// create file
fileHandle = CreateFileW(TEXT("\\\\.\\pipe\\my-very-cool-pipe-example"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
// read from pipe server
char* buffer = new char[100];
memset(buffer, 0, 100);
ReadString(buffer);
printf("read from pipe server: %s\r\n", buffer);
// send data to server
const char* msg = "hello from C\r\n";
WriteFile(fileHandle, msg, strlen(msg), nullptr, NULL);
}
对应的C#程序如下:
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading.Tasks;
namespace PipeServerCsharp
{
class Program
{
static void Main(string[] args)
{
var instances = new Task[5];
for (int i = 0; i < instances.Length; i++)
{
instances[i] = Task.Run(() =>
{
var namedPipeServer = new NamedPipeServerStream("my-very-cool-pipe-example", PipeDirection.InOut, 5, PipeTransmissionMode.Byte);
var streamReader = new StreamReader(namedPipeServer);
namedPipeServer.WaitForConnection();
var writer = new StreamWriter(namedPipeServer);
writer.Write("Hello from c#");
writer.Write((char)0);
writer.Flush();
namedPipeServer.WaitForPipeDrain();
Console.WriteLine($"read from pipe client: {streamReader.ReadLine()}");
namedPipeServer.Dispose();
});
}
Task.WaitAll(instances);
}
}
}
取自:https://github.com/gabbersepp/dev.to-posts/tree/master/blog-posts . IPC示例
最佳答案
简介
管道没有数据或结构的概念。它只是一端输入字节,另一端输出字节。那么将 C struct 转换为 字节流 然后转换为 C# struct 的最佳方法是什么? C 结构?
结构背景
显然,结构是 C 程序端的一 block 内存,因此最简单的方法就是逐字节(假设地)复制结构并将它们粘贴到管道的输入端。 C# 中的结构也只是一 block 内存,尽管它为 .NET 运行时存储了一些额外的数据。从管道逐字节读入 C# 结构
首先谈谈struct
。虽然 struct
可能被声明为
struct data
{
__int16 count;
__int32 total;
__int16 average;
};
编译器可以自由打包struct
。大多数编译器可能会将上述结构打包成(这些只是示例)
struct data
{
__int16 count;
__int16 count_padding; // some padding
__int32 total;
__int16 average;
__int16 average_padding; // some padding
};
显然相应地更改其余代码。 CPU 在边界上获取内存。在必须获取 __int32
的 32 位系统上,如果它未在边界上对齐,则可能需要 2 次获取。所以你的编译器会做艰苦的工作并重新排列你的结构。问题是不同的编译器或具有不同优化设置的相同编译器将创建不同的结构,这意味着内存中的布局不同。 [参见Structure padding and packing了解更多详情]
如果我们将一个以字节为单位的结构按原样输入管道,而另一个程序读取它并以不同方式更改结构,那么它们就会成为问题。所以请记住结构包装。您可以按如下方式重新排序上述结构,以便一切都“更好”地对齐
struct data
{
__int16 count; // rearranged
__int16 average;
__int32 total;
};
有特殊的编译器选项可以强制打包。
例子
这里没有进一步的音频是一个工作示例。先运行C#程序,再运行C程序。
注意:我特别选择了 #pragma pack(push, 4)
和 [StructLayout(LayoutKind.Sequential, Pack = 32)]
来演示您可能如何影响包装。如果您省略 #pragma pack(push, 4)
和 Pack = 32
它也可以工作或选择其他一些匹配对。 [StructLayout(LayoutKind.Sequential]
很重要,因为它可以防止对结构的结构字段重新排序。C 不会重新排序结构 (AFAIK),只会填充,所以这就是那里没有选项的原因。
using System;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using var namedPipeServer = new NamedPipeServerStream("my-very-cool-pipe-example", PipeDirection.InOut, 5, PipeTransmissionMode.Byte);
// var streamReader = new StreamReader(namedPipeServer); don't want a stream reader since it reads text (We need binary)
var binaryReader = new BinaryReader(namedPipeServer);
var snapshot = new Snapshot();
var buffer = new byte[16].AsSpan(); // make sure your buffer is big enough
namedPipeServer.WaitForConnection(); // wait for the C program to connect
int cc = 0;
while(namedPipeServer.IsConnected && ++cc < 5)
{
snapshot.Count = binaryReader.ReadInt16();
snapshot.Average = binaryReader.ReadInt16();
snapshot.Total = binaryReader.ReadInt32();
// tada the struct is populated
Console.WriteLine(snapshot);
}
Console.WriteLine("Now using MemoryMarshal");
while (namedPipeServer.IsConnected)
{
var bytesRead = binaryReader.Read(buffer);
var snapshots = MemoryMarshal.Cast<byte, Snapshot>(buffer);
// tada the struct is populated
Console.WriteLine($"BytesRead = {bytesRead}, {snapshots[0]}");
}
// Same C struct
[StructLayout(LayoutKind.Sequential /* don't reorder */, Pack = 32)] // 32 bit boundaries like the C version (usually leave 0 / default)
public struct Snapshot
{
public Int16 Count;
public Int16 Average;
public Int32 Total;
public override string ToString() =>
$"Snapshot {{Count: {Count}, Average: {Average}, Total: {Total}}}";
}
C程序
#include <windows.h>
#include <stdio.h>
#include <inttypes.h>
#pragma pack(push, 4) // align to 32 bit boundaries for example sake (usually leave default)
typedef struct tagSnapshot {
__int16 Count;
__int16 Average;
__int32 Total;
} Snapshot;
#pragma pack(pop) // undo the align we don't want the whole program to have our change
int main()
{
HANDLE hPipe;
Snapshot snapshot;
// Create Write Only Pipe
hPipe = CreateFile(TEXT("\\\\.\\pipe\\my-very-cool-pipe-example"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
snapshot.Total = 0;
for (short i = 0, cc = 0; cc < 10; i+=3, cc++) {
// some random maths (to change the struct)
snapshot.Average = cc / 2;
snapshot.Count = cc;
snapshot.Total += cc;
// struct is now in the pipe as a stream of bytes
Sleep(200);
WriteFile(hPipe, &snapshot, sizeof(Snapshot), NULL, NULL);
printf("Snapshot {Count: %d, Average: %d, Total: %d}\n", snapshot.Count, snapshot.Average, snapshot.Total);
}
CloseHandle(hPipe);
}
结论
发送结构是相对微不足道的,特别是如果它只有一个或一些重复模式。如果需要更复杂的协调并发送不同的结构,它就会变得更加棘手。在这种情况下,需要使用或构建一个库来抽象复杂的东西。也就是说,Win32 NamedPipes 有一种消息格式,可能会根据您的需要提供帮助。
关于c# - 如何在具有命名管道的同一系统上将结构数据从 C 传递到 C# 应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68962345/