Правильный способ экспорта упорядоченной структуры

#c# #marshalling

#c# #сортировка

Вопрос:

Я пишу управляемую библиотеку DLL, которая использует стороннюю собственную библиотеку DLL, заголовок которой содержит:

 typedef struct {
   Bool isMounted;
   char *symbolicLink;
} VolumeInfo;

ErrorCode GetVolumeInfo(VolumeHandle handle, VolumeInfo **info);
void FreeVolumeInfo(VolumeInfo *info);
  

Я упорядочил это как:

 [StructLayout(LayoutKind.Sequential]
internal struct NativeVolumeInfo
{
    public bool IsMounted;
    public IntPtr SymbolicLink;
}

static extern ErrorCode GetVolumeInfo(IntPtr volumeHandle, out IntPtr volumeInfoPointer);
static extern void FreeVlumeInfo(IntPtr volumeInfoPointer);
  

Обоснование таково:
VolumeInfo.SymbolicLink должно быть IntPtr вместо string , потому что его размер динамический, и я не знаю, какова кодировка. Кроме того, предполагается, что машинный код не должен устанавливать его, если isMounted есть false , но это не всегда так, и иногда я получал тарабарщину из неуправляемой памяти.
— Мне нужно получить указатель на volumeInfoHandle из native, а затем передать его обратно для освобождения. Поэтому я не могу просто передать управляемую структуру как out on GetVolumeInfo и полагаться на то, что маршаллер заполнит ее для меня.

Моя цель — предоставить информацию об этом томе конечным пользователям моей DLL. Итак, у меня есть этот класс API, который использует собственные методы за кулисами и возвращает более удобную для пользователя структуру:

 public struct VolumeInfo
{
  public bool IsMounted {get;set;}
  public string SymbolicLink {get;set;}
}

public class PublicApi
{
  public VolumeInfo GetVolumeInfo(Volume volume)
  {
    IntPtr volumeInfoPointer;
    var errorCode = NativeMethods.GetVolumeInfo(volume.Handle, out volumeInfoPointer);
    if (errorCode == 0)
    {
      var nativeVolumeInfo = (NativeVolumeInfo)Marshal.PtrToStructure(volumeInfoPointer, typeof(NativeVolumeInfo));
      try
      {
        var volumeInfo = new VolumeInfo { IsMounted = nativeVolumeInfo.IsMounted };
        if (volumeInfo.IsMounted)
        {
          volumeInfo.SymbolicLink = Marshal.PtrToStringAnsi(nativeVolumeInfo.SymbolicLink);
        }
        return volumeInfo;
      }
      finally
      {
        NativeMethods.FreeVolumeInfo(volumeInfoPointer);
      }           
    }
    throw new InvalidOperationException("Didn't work");
  }
}
  

На данный момент я обеспокоен тем, что для каждой собственной структуры, которую мне нужно маршалировать вручную, я должен написать 2 управляемые структуры: одну для маршалинга, а другую для экспорта конечным пользователям.

Есть ли какой-либо способ упростить это?

Комментарии:

1. На самом деле, вам, вероятно, следует вместо этого представить управляемую структуру как класс с Dispose методом и завершителем. В .NET каждый класс должен иметь возможность очистки после себя. Кроме того, обратитесь к документации API — char * недостаточно информации для правильной реализации этого. Неуправляемый код очень небезопасен, поэтому вы должны быть намного осторожнее.

2. Вы беспокоитесь не о той проблеме. В этом коде скрыта проблема с управлением памятью. Кому принадлежит строка? Как она должна быть выпущена? Происходит ли сбой модульного теста в ООМ при вызове этой функции миллиард раз? Скрытие таких неприятных деталей является основным требованием к коду взаимодействия. Если для этого требуется отдельная структура взаимодействия, то пусть будет так.

3. @HansPassant: Насколько я понимаю, строка выделяется в неуправляемой памяти. Мне нужно только позвонить FreeVolumeInfo , когда я закончу, и это должно позаботиться о его выпуске. Мне не нужно использовать Marshal.FreeXXX , потому что я не владею этим, верно?