Повреждение кучи вскоре после получения структуры marshalled в приложении C # - PullRequest
0 голосов
/ 26 октября 2018

Я пытаюсь обернуть новые функции Windows 10 ProjFS в C #. Первые шаги, , как указано в MSDN , работают нормально: я установил каталог в качестве своего корня виртуализации, а затем зарегистрировал своего провайдера проекций, включая его обратные вызовы, следующим образом:

static void Main(string[] args)
{
    // Create and mark clean directory as virtualization root.
    const string directory = @"C:\test_projfs"; // Warning: Deleted to get clean directory.
    if (Directory.Exists(directory))
        Directory.Delete(directory);
    Directory.CreateDirectory(directory);

    Guid guid = Guid.NewGuid();
    Marshal.ThrowExceptionForHR(PrjMarkDirectoryAsPlaceholder(directory, null, IntPtr.Zero,
        ref guid));

    // Set up the callback table for the projection provider.
    PrjCallbacks callbackTable = new PrjCallbacks
    {
        StartDirectoryEnumerationCallback = StartDirectoryEnumerationCallback,
        EndDirectoryEnumerationCallback = EndDirectoryEnumerationCallback,
        GetDirectoryEnumerationCallback = GetDirectoryEnumerationCallback,
        GetPlaceholderInfoCallback = GetPlaceholderInfoCallback,
        GetFileDataCallback = GetFileDataCallback
    };
    // Start the projection provider.
    IntPtr instanceHandle = IntPtr.Zero;
    Marshal.ThrowExceptionForHR(PrjStartVirtualizing(directory, ref callbackTable,
        IntPtr.Zero, IntPtr.Zero, ref instanceHandle));

    // Keep a test console application running.
    Console.ReadLine();
}

// Managed callbacks, simply returning S_OK for now.
static int StartDirectoryEnumerationCallback(ref PrjCallbackData callbackData, ref Guid enumerationId)
{
    return 0;
}
static int EndDirectoryEnumerationCallback(ref PrjCallbackData callbackData, ref Guid enumerationId) => 0;
static int GetDirectoryEnumerationCallback(ref PrjCallbackData callbackData, ref Guid enumerationId, string searchExpression, IntPtr dirEntryBufferHandle) => 0;
static int GetPlaceholderInfoCallback(ref PrjCallbackData callbackData) => 0;
static int GetFileDataCallback(ref PrjCallbackData callbackData, ulong byteOffset, uint length) => 0;

Собственные объявления выглядят следующим образом (извините за тот факт, что этот блок кода можно прокручивать - основы уже достаточно для переноса):

// Methods to mark directory as virtualization root, and start the projection provider.
[DllImport("ProjectedFSLib.dll", CharSet = CharSet.Unicode)]
static extern int PrjMarkDirectoryAsPlaceholder(string rootPathName,
    string targetPathName, IntPtr versionInfo, ref Guid virtualizationInstanceID);
[DllImport("ProjectedFSLib.dll", CharSet = CharSet.Unicode)]
static extern int PrjStartVirtualizing(string virtualizationRootPath,
    ref PrjCallbacks callbacks, IntPtr instanceContext, IntPtr options,
    ref IntPtr namespaceVirtualizationContext);

// Structure configuring the projection provider callbacks.
[StructLayout(LayoutKind.Sequential)]
struct PrjCallbacks
{
    public PrjStartDirectoryEnumerationCb StartDirectoryEnumerationCallback;
    public PrjEndDirectoryEnumerationCb EndDirectoryEnumerationCallback;
    public PrjGetDirectoryEnumerationCb GetDirectoryEnumerationCallback;
    public PrjGetPlaceholderInfoCb GetPlaceholderInfoCallback;
    public PrjGetFileDataCb GetFileDataCallback;
    public PrjQueryFileNameCb QueryFileNameCallback;
    public PrjNotificationCb NotificationCallback;
    public PrjCancelCommandCb CancelCommandCallback;
}

// Callback signatures.
delegate int PrjStartDirectoryEnumerationCb(ref PrjCallbackData callbackData, ref Guid enumerationId);
delegate int PrjEndDirectoryEnumerationCb(ref PrjCallbackData callbackData, ref Guid enumerationId);
delegate int PrjGetDirectoryEnumerationCb(ref PrjCallbackData callbackData, ref Guid enumerationId, string searchExpression, IntPtr dirEntryBufferHandle);
delegate int PrjGetPlaceholderInfoCb(ref PrjCallbackData callbackData);
delegate int PrjGetFileDataCb(ref PrjCallbackData callbackData, ulong byteOffset, uint length);
delegate int PrjQueryFileNameCb(ref PrjCallbackData callbackData);
delegate int PrjNotificationCb(ref PrjCallbackData callbackData, bool isDirectory, int notification, string destinationFileName, IntPtr operationParameters);
delegate int PrjCancelCommandCb(ref PrjCallbackData callbackData);

// Callback data passed to each of the callbacks above.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct PrjCallbackData
{
    public uint Size;
    public uint Flags;
    public IntPtr NamespaceVirtualizationContext;
    public int CommandId;
    public Guid FileId;
    public Guid DataStreamId;
    public string FilePathName;
    public IntPtr VersionInfo;
    public uint TriggeringProcessId;
    public string TriggeringProcessImageFileName;
    public IntPtr InstanceContext;
}

Однако я, похоже, сделал что-то не так, когда распределял структуру, переданную из ОС в обратные вызовы, поскольку это приводит к немедленной остановке управляемого приложения без управляемого исключения и регистрации только кода ошибки повреждения кучи в событии Windows зритель.

Подробно, я получаю ответный вызов PRJ_START_DIRECTORY_ENUMERATION_CB (PrjStartDirectoryEnumerationCb в моем коде), чтобы начать перечисление моих каталогов. Он передает указатель на структуру PRJ_CALLBACK_DATA (в моем коде PrjCallbackData).

Теперь, когда я, очевидно, получаю структуру в свой управляемый обратный вызов, со всеми значениями, имеющими смысл вплоть до последнего члена InstanceContext, приложение сразу падает при попытке вернуть значение 0 (S_OK).

static int StartDirectoryEnumerationCallback(ref PrjCallbackData callbackData, ref Guid enumerationId)
{
    return 0; // Crashes here or when stepping out of this method.
}

Я пытался зафиксировать, где лежит моя ошибка, но из-за немедленной остановки отладки без каких-либо исключений (я не отфильтровываю) я не ушел далеко. Я понял, что когда я изменяю обратный вызов, чтобы тупо принять IntPtr вместо ref PrjCallbackData, приложение не падает.

static int StartDirectoryEnumerationCallback(IntPtr callbackData, ref Guid enumerationId)
{
    return 0; // No crash executing this with IntPtr passed in.
}

delegate int PrjStartDirectoryEnumerationCb(IntPtr callbackData, ref Guid enumerationId);

По логике вещей, это не поможет мне без важной информации, доступной для меня.

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

Если интересно, запись просмотра событий выглядит следующим образом. Я запускаю приложение с .NET Framework 4.6 в файле проекта C # нового стиля (который должен объяснять имя исполняемого файла "dotnet.exe"). Если потребуется дополнительная информация, я с радостью ее предоставлю.

Faulting application name: dotnet.exe, version: 2.1.26919.1, time stamp: 0x5ba1bb46
Faulting module name: ntdll.dll, version: 10.0.17763.1, time stamp: 0xa369e897
Exception code: 0xc0000374
Fault offset: 0x00000000000fb349
Faulting process id: 0xfa8
Faulting application start time: 0x01d46d623aee076d
Faulting application path: C:\Program Files\dotnet\dotnet.exe
Faulting module path: C:\WINDOWS\SYSTEM32\ntdll.dll
Report Id: adcfba5c-dfd4-428d-8eb5-81aceada1983
Faulting package full name: 
Faulting package-relative application ID: 

Обратите внимание, что если вы хотите попробовать приведенный выше пример кода, вам необходимо установить функцию Projected Filesystem в Windows 10 1809 и скомпилировать ее как x64 (нет собственных библиотек для конфигураций x86 / AnyCPU).

1 Ответ

0 голосов
/ 29 октября 2018

Как прокомментировал Саймон, только наличие IntPtr в подписи вместо структуры и затем использование Marshal.PtrToStructure<PrjCallbackData>(callbackData) для извлечения копии структуры, переданной моему обратному вызову, работает нормально.

В качестве альтернативы, решение Ганса с использованием IntPtr для строковых полей в структуре (и сохранение структуры в сигнатуре) также работает, но у меня нет простого доступа к строковым данным.

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

Если интересует полное покрытие кода, хранилище находится здесь .

...