Какой наиболее эффективный способ преобразовать структуры C ++ в C #?
-
22-08-2019 - |
Вопрос
Я собираюсь начать читать тонны двоичных файлов, каждый из которых содержит 1000 или более записей.Новые файлы добавляются постоянно, поэтому я пишу службу Windows для мониторинга каталогов и обработки новых файлов по мере их получения.Файлы были созданы с помощью программы на c ++.Я воссоздал определения структуры в c # и могу нормально считывать данные, но я обеспокоен тем, что то, как я это делаю, в конечном итоге приведет к завершению работы моего приложения.
using (BinaryReader br = new BinaryReader(File.Open("myfile.bin", FileMode.Open)))
{
long pos = 0L;
long length = br.BaseStream.Length;
CPP_STRUCT_DEF record;
byte[] buffer = new byte[Marshal.SizeOf(typeof(CPP_STRUCT_DEF))];
GCHandle pin;
while (pos < length)
{
buffer = br.ReadBytes(buffer.Length);
pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
record = (CPP_STRUCT_DEF)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(CPP_STRUCT_DEF));
pin.Free();
pos += buffer.Length;
/* Do stuff with my record */
}
}
Я не думаю, что мне нужно использовать GCHandle, потому что на самом деле я не общаюсь с приложением C ++, все делается с помощью управляемого кода, но я не знаю альтернативного метода.
Решение
Для вашего конкретного приложения только одна вещь даст вам окончательный ответ:Составьте его профиль.
При этом вот уроки, которые я извлек, работая с крупными решениями PInvoke.Наиболее эффективный способ упорядочить данные - это упорядочить поля, которые являются заполняемыми.Это означает, что среда CLR может просто выполнять то, что равносильно memcpy для перемещения данных между собственным и управляемым кодом.Проще говоря, извлеките все нестрочные массивы и строки из ваших структур.Если они присутствуют в собственной структуре, представьте их с помощью IntPtr и маршалируйте значения по требованию в управляемый код.
Я никогда не описывал разницу между использованием Marshal.PtrToStructure противналичие собственного API разыменовывает значение.Вероятно, это то, во что вам следует инвестировать, если PtrToStructure будет выявлена как узкое место с помощью профилирования.
Для больших иерархий маршалирование по требованию по сравнениюперенос всей структуры в управляемый код за один раз.Больше всего я сталкивался с этой проблемой, когда имел дело с большими древовидными структурами.Маршалинг отдельного узла выполняется очень быстро, если он доступен для обмена данными и с точки зрения производительности позволяет маршалировать только то, что вам нужно в данный момент.
Другие советы
Используя Marshal.PtrToStructure
это довольно медленно.Я нашел следующую статью о CodeProject, в которой сравниваются (и сравниваются) различные способы чтения двоичных данных, очень полезную:
В дополнение к исчерпывающему ответу JaredPar, вам не нужно использовать GCHandle
, вместо этого вы можете использовать небезопасный код.
fixed(byte *pBuffer = buffer) {
record = *((CPP_STRUCT_DEF *)pBuffer);
}
Вся цель этого GCHandle
/fixed
инструкция заключается в том, чтобы закрепить / зафиксировать конкретный сегмент памяти, делая память неподвижной с точки зрения GC.Если бы память была подвижной, любое перемещение сделало бы ваши указатели недействительными.
Хотя не уверен, какой способ быстрее.
Возможно, это выходит за рамки вашего вопроса, но я был бы склонен написать небольшую сборку на управляемом C ++, которая выполняла fread() или что-то аналогично быстрое для чтения в структурах.Как только вы их прочитаете, вы можете использовать C #, чтобы делать с ними все остальное, что вам нужно.
вот небольшой класс, который я сделал некоторое время назад, играя со структурированными файлами.это был самый быстрый метод, который я смог придумать в то время, боясь стать небезопасным (именно его я пытался заменить и поддерживать сопоставимую производительность).)
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace PersonalUse.IO {
public sealed class RecordReader<T> : IDisposable, IEnumerable<T> where T : new() {
const int DEFAULT_STREAM_BUFFER_SIZE = 2 << 16; // default stream buffer (64k)
const int DEFAULT_RECORD_BUFFER_SIZE = 100; // default record buffer (100 records)
readonly long _fileSize; // size of the underlying file
readonly int _recordSize; // size of the record structure
byte[] _buffer; // the buffer itself, [record buffer size] * _recordSize
FileStream _fs;
T[] _structBuffer;
GCHandle _h; // handle/pinned pointer to _structBuffer
int _recordsInBuffer; // how many records are in the buffer
int _bufferIndex; // the index of the current record in the buffer
long _recordPosition; // position of the record in the file
/// <overloads>Initializes a new instance of the <see cref="RecordReader{T}"/> class.</overloads>
/// <summary>
/// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
/// </summary>
/// <param name="filename">filename to be read</param>
public RecordReader(string filename) : this(filename, DEFAULT_STREAM_BUFFER_SIZE, DEFAULT_RECORD_BUFFER_SIZE) { }
/// <summary>
/// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
/// </summary>
/// <param name="filename">filename to be read</param>
/// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param>
public RecordReader(string filename, int streamBufferSize) : this(filename, streamBufferSize, DEFAULT_RECORD_BUFFER_SIZE) { }
/// <summary>
/// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
/// </summary>
/// <param name="filename">filename to be read</param>
/// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param>
/// <param name="recordBufferSize">size of record buffer, in records.</param>
public RecordReader(string filename, int streamBufferSize, int recordBufferSize) {
_fileSize = new FileInfo(filename).Length;
_recordSize = Marshal.SizeOf(typeof(T));
_buffer = new byte[recordBufferSize * _recordSize];
_fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, streamBufferSize, FileOptions.SequentialScan);
_structBuffer = new T[recordBufferSize];
_h = GCHandle.Alloc(_structBuffer, GCHandleType.Pinned);
FillBuffer();
}
// fill the buffer, reset position
void FillBuffer() {
int bytes = _fs.Read(_buffer, 0, _buffer.Length);
Marshal.Copy(_buffer, 0, _h.AddrOfPinnedObject(), _buffer.Length);
_recordsInBuffer = bytes / _recordSize;
_bufferIndex = 0;
}
/// <summary>
/// Read a record
/// </summary>
/// <returns>a record of type T</returns>
public T Read() {
if(_recordsInBuffer == 0)
return new T(); //EOF
if(_bufferIndex < _recordsInBuffer) {
// update positional info
_recordPosition++;
return _structBuffer[_bufferIndex++];
} else {
// refill the buffer
FillBuffer();
return Read();
}
}
/// <summary>
/// Advances the record position without reading.
/// </summary>
public void Next() {
if(_recordsInBuffer == 0)
return; // EOF
else if(_bufferIndex < _recordsInBuffer) {
_bufferIndex++;
_recordPosition++;
} else {
FillBuffer();
Next();
}
}
public long FileSize {
get { return _fileSize; }
}
public long FilePosition {
get { return _recordSize * _recordPosition; }
}
public long RecordSize {
get { return _recordSize; }
}
public long RecordPosition {
get { return _recordPosition; }
}
public bool EOF {
get { return _recordsInBuffer == 0; }
}
public void Close() {
Dispose(true);
}
void Dispose(bool disposing) {
try {
if(disposing && _fs != null) {
_fs.Close();
}
} finally {
if(_fs != null) {
_fs = null;
_buffer = null;
_recordPosition = 0;
_bufferIndex = 0;
_recordsInBuffer = 0;
}
if(_h.IsAllocated) {
_h.Free();
_structBuffer = null;
}
}
}
#region IDisposable Members
public void Dispose() {
Dispose(true);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator() {
while(_recordsInBuffer != 0) {
yield return Read();
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion
} // end class
} // end namespace
для использования:
using(RecordReader<CPP_STRUCT_DEF> reader = new RecordReader<CPP_STRUCT_DEF>(path)) {
foreach(CPP_STRUCT_DEF record in reader) {
// do stuff
}
}
(здесь что-то новенькое, надеюсь, этого было не слишком много для публикации...просто вставил в класс, не вырезал комментарии или что-то еще, чтобы сократить его.)
Похоже, это не имеет ничего общего ни с C ++, ни с сортировкой.Вы знаете структуру, что еще вам нужно.
Очевидно, вам нужен простой код, который будет считывать группу байтов, представляющих одну структуру, а затем использовать BitConverter для размещения байтов в соответствующих полях C #..