在C#应用程序中收到封送结构后不久堆损坏

在C#应用程序中收到封送结构后不久堆损坏,c#,pinvoke,marshalling,projfs,C#,Pinvoke,Marshalling,Projfs,我正在尝试用C#包装Windows 10的新功能。第一步,工作正常:我将一个目录设置为我的虚拟化根目录,然后注册我的投影提供程序,包括它的回调,如下所示: static void Main(string[] args) { // Create and mark clean directory as virtualization root. const string directory = @"C:\test_projfs"; // Warning: Deleted to get

我正在尝试用C#包装Windows 10的新功能。第一步,工作正常:我将一个目录设置为我的虚拟化根目录,然后注册我的投影提供程序,包括它的回调,如下所示:

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;
本机声明如下所示(很抱歉,这个代码块可以滚动-基本内容已经有很多需要包装了):

但是,我似乎在编组从操作系统传递到回调的结构时出错了,因为这会导致托管应用程序立即停止,而不会出现托管异常,并且只在Windows事件查看器中记录堆损坏错误代码

详细地说,我收到回调(
PrjStartDirectoryEnumerationCb
,在我的代码中)开始枚举我的目录。它沿着(
PrjCallbackData
在我的代码中)传递指向结构的指针

现在,虽然我显然在托管回调中很好地接收到了结构,所有的值一直到最后一个成员
InstanceContext
,但应用程序在尝试返回值0时立即崩溃(
S_OK

我试图确定我的错误所在,但由于调试立即毫无例外地停止(我没有过滤掉任何错误),我没有走多远。我意识到,当我将回调更改为默认接受
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);
从逻辑上讲,如果没有我可以访问的重要信息,这不会让我走得很远

我在这里漏了一步吗?这样简单的结构映射是不是不可能直接实现

如果感兴趣,事件查看器条目如下所示。我正在一个新型的C#项目文件中运行带有.NET Framework 4.6的应用程序(这应该解释“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: 

请注意,如果您想尝试上面的示例代码,您必须在Windows 10 1809中安装投影文件系统功能,并将其编译为x64(x86/AnyCPU配置没有本机库)。

正如Simon评论的那样,只有签名中有一个
IntPtr
,而不是结构,然后使用
Marshal.PtrToStructure(callbackData)
检索传递给我的回调的结构副本就可以了

或者,Hans使用
IntPtr
处理结构中的字符串字段(并将结构保留在签名中)的解决方案也可以工作,但我无法简单地访问字符串数据

幸运的是,我不必写任何东西回这个结构,否则我会遇到问题,写回原始结构,而不是结构的副本,所以Simon的解决方案在这里就足够了


如果您对完整的代码覆盖感兴趣,则会找到存储库。

如果您能实现此功能,请确保在Show a Please上记录它。这不是一个友好的api。这个结构中的字符串是一个很棘手的问题。您必须首先将它们声明为IntPtr。MSDN文档不足以猜测您应该如何分配它们。我唯一合理的猜测是,你在一个
列表中跟踪它们,并在完成后销毁它们。StringToCoTaskMemUni()生成IntPtr。对适当MCVE的需求确实相当明显。@HansPassant的确,用
IntPtr
替换字符串字段可以修复崩溃。PRJ_CALLBACK_数据的大小是可变的(这就是它有一个大小字段的原因),因此您必须在发现后在回调中将其声明为IntPtr。然后可以使用
var data=Marshal.PtrToStructure(data)
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);
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: