У меня есть приложение на C#, которому необходимо обмениваться некоторыми двоичными данными с Python, и я планирую использовать общую память (файл, отображаемый в памяти). Таким образом, мне нужно иметь одинаковую бинарную структуру с обеих сторон.
Я создаю структуру на C# (ST_Layer), которая содержит массив элементов другой структуры (ST_Point). Для доступа к этим элементам я определил функцию getPoint, которая будет возвращать указатель на запрошенную позицию ST_Point.
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
class TestProgram
{
[StructLayout(LayoutKind.Explicit, Pack = 8)]
public struct ST_Point
{
[FieldOffset(0)] public double X;
[FieldOffset(8)] public double Y;
}
[StructLayout(LayoutKind.Explicit, Pack = 8)]
public unsafe struct ST_Layer
{
[FieldOffset(0)] public ushort nPoints;
[FieldOffset(8)] public double fHeight;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
[FieldOffset(16)] public ST_Point[] Points;
public unsafe ST_Point* getPoint(int n)
{
fixed (ST_Layer* thisPtr = &this)
{
int ptr = (int)&(thisPtr->Points) + n * ((16 + 3) / 4) * 4; // Sizeof ST_Point 16
return (ST_Point*)ptr;
}
}
}
static void Main(string[] args)
{
unsafe
{
ST_Layer layer = new ST_Layer();
layer.nPoints = 3;
layer.fHeight = 4;
layer.getPoint(0)->X = 5;
layer.getPoint(3)->Y = 6;
for (int i = 0; i < 10; i++)
Debug.WriteLine("Data [" + i + "] " + layer.getPoint(i)->X + ", " + layer.getPoint(i)->Y + " - ");
while (true)
Thread.Sleep(50);
}
}
}
У меня есть 2 вопроса:
getPoint для доступа к массиву ST_Point, потому что не видел лучшего способа доступа к нему. Можно ли получить к нему доступ как к обычному массиву (т. е. layer.Points[0].X)? Я не могу создать массив ST_Point (layer.Points = new ST_Point[10]), так как он будет создан вне ST_Layer, и данные не будут переданы в Python.Я видел это и это, но не знаю, как получить доступ к отдельным полям ST_Point.
Спасибо за помощь.
[Также спасибо Р. Мартиньо Фернандесу за подсказку относительно проблемы x86/x64]
@GSerg, возможно, ты прав (ты прав), но решение, похоже, работает. Разве возвращенный указатель неверен?
Ни один из этих кодов не делает того, что вы думаете. Unsafe fixed не предназначен для возврата из него указателя, тот факт, что указатель не перемещается, не означает, что он не будет перемещаться в будущем. MarshalAs не работает, если вы на самом деле не занимаетесь сортировкой. Я предлагаю вам вместо этого использовать MemoryMappedFile, который идеально подходит для вашего случая использования.
@Charlieface, на самом деле я использую MemoryMappedFile, но тогда мне нужно назначить эту память структуре, и именно здесь я обнаруживаю проблему. ` using (var mmf = MemoryMappedFile.CreateOrOpen("SharedMemory", Marshal.SizeOf<ST_Layer>())) { unsafe { using (var accessor = mmf.CreateViewAccessor()) { byte* SharedMemPtr = null; accessor.SafeMemoryMappedViewHandle.AcquirePointer(refsharedMemPtr); ST_Layer* Layer = (ST_Layer*)sharedMemPtr;`
Так почему бы вам просто не использовать accessor.Read<ST_Layer> для копирования и удаления, зачем вы возитесь с указателями? Вы не можете ожидать, что управляемая структура будет корректно работать с небезопасным указателем, вам необходимо ее маршалировать. Только если это unmanaged struct, вы можете использовать указатель без маршалинга, и только если указатель закреплен.
@Charlieface, спасибо за твой комментарий. Я вижу, что ваше решение похоже на решение Мэтью Уотсона. Или я что-то упускаю?
Ну, он добавил InlineArray, что означает меньше накладных расходов на сортировку. Но в любом случае unsafe вам не нужен, я просто пытался понять, почему вы все так излишне усложнили.
@Charlieface, пожалуйста, прочтите мой последний комментарий в ответе Мэтью: я предпочитаю не делать Read() и Write() при каждом изменении данных. Однако я могу подумать об этом еще раз, поскольку unsafe явно не лучшая практика.





Если вы используете последнюю версию .NET, вы можете использовать System.Runtime.CompilerServices.InlineArray для чтения и записи структуры непосредственно из MMF и в него без какого-либо небезопасного кода.
Атрибут InlineArray позволяет указать структуру, содержащую фиксированное количество элементов определенного типа значения, доступ к которым можно получить с помощью операции индексирования:
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
namespace Console1;
public static class Program
{
static void Main()
{
using MemoryMappedFile mmf = MemoryMappedFile.CreateNew("MappedFile", 1000);
var view = mmf.CreateViewAccessor();
using UnmanagedMemoryAccessor accessor = view;
var s = new ST_Layer();
for (int i = 0; i < 10; ++i)
{
s.Points[i] = new ST_Point { X = i, Y = -i };
}
long offset = 500; // Arbitrary offset with the MMF.
view.Write(offset, ref s);
view.Read(offset, out ST_Layer t);
for (int i = 0; i < 10; ++i)
{
Console.WriteLine($"{t.Points[i].X}, {t.Points[i].Y}");
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct ST_Point
{
[FieldOffset(0)] public double X;
[FieldOffset(8)] public double Y;
}
// This is the crucial bit: Define a fixed inline array with 10 elements:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct TenPoints
{
ST_Point _firstElement;
}
[StructLayout(LayoutKind.Explicit)]
{
[FieldOffset(0)] public ushort nPoints;
[FieldOffset(8)] public double fHeight;
[FieldOffset(16)] public TenPoints Points;
}
В документах говорится, чтоPack не влияет на struct с LayoutKind.Explicit.
@GoodNightNerdPride Это правда, вы выбираете одно или другое. Я просто копировал то, что было у ОП. Я удалил атрибуты пакета, чтобы избежать путаницы.
@MatthewWatson, спасибо за помощь. Однако в вашем решении вам все равно придется вызывать view.Write() или view.Read(), чтобы обновить общие данные. Если вы замените var s = new ST_Layer() указателем на общую память (byte* sharedMemPtr = null;view.SafeMemoryMappedViewHandle.AcquirePointer(ref sharedMemPtr);ST_Layer* s = (ST_Layer*)sharedMemPtr;), вы сможете получить прямой доступ к данным s->Points[]. Или я что-то упускаю?
Забыл добавить, что решение-указатель (view.SafeMemoryMappedViewHandle.AcquirePointer(ref sharedMemPtr)) должно быть небезопасным.
@Asier Почему вам нужно использовать решение с указателем? Правильный подход — использовать view.Read() и view.Write(). Разница в скорости будет незначительной, и небезопасный код вам не понадобится.
@MatthewWatson, я предпочитаю решение с указателем. Использование Read() и Write() требует вызова их каждый раз при изменении значения (фактическая структура больше, чем показанная здесь). Кроме того, если обе программы выполняют RW данных, для изменения значения потребуется вызов семафора, вызов Read(), затем изменение одного значения и, наконец, вызов Write().
«Кроме того, если обе программы выполняют RW данных, для изменения значения потребуется семафор, вызов Read(), затем изменение одного значения и, наконец, вызов Write()». Вам также понадобится семафор (или аналогичный), когда изменение данных с помощью указателя – как еще другой конец узнает, что вы изменили данные? Также похоже, что вам понадобится межпроцессная блокировка, чтобы предотвратить чтение частично обновленных данных.
Другая сторона будет просто читать новое значение всякий раз, когда ей это понадобится. Каждая из программ обновляет разные переменные, даже если они связаны. Что касается предотвращения частичного обновления данных, да, мне нужно это контролировать, но это происходит только в нескольких местах, и в настоящее время ими управляют.
«Другая сторона будет просто читать новое значение всякий раз, когда ей это понадобится». Что, если она считывает точку именно тогда, когда другая сторона обновила X, но не обновила Y? Тогда это будет чтение неверных данных (эквивалент прерванного чтения).
Что касается предотвращения частичного обновления данных, эти биты данных уже контролируются и управляются в рабочем решении. Это новое решение представляет собой обновление существующего решения, для которого не требуется связь C#/Python. Большое спасибо за Вашу помощь.
Вы понимаете, что указатель, возвращенный из
getPoint, сразу (может быть) недействителен?fixedработает только для текстовых объектов внутри него.