#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
, потому что я не владею этим, верно?