c# - 无法将 FSCTL_GET_RETRIEVAL_POINTERS 调用移植到 C#

标签 c# c++ winapi memory interop

我在尝试使用 C# 中的 FSCTL_GET_RETRIEVAL_POINTERS 调用 DeviceIOControl 时遇到了一个奇怪的问题。我试过 Jeffrey Walls blog 中的代码以及我自己的代码,但一直遇到同样的问题。问题似乎出在 RETRIEVAL_POINTERS_BUFFER 中的 Extents 结构中。 C++中Extents[0]->NextVcn1Extents[0]->Lcn217550时,在 C# 中,我一直得到 Extents[0]->NextVcn4294967296Extents[0]->Lcn934370135244800 。与RETRIEVAL_POINTERS_BUFFER->StartingVcn一样,当它应该是0时,在C#中它变成了一些其他数字(如1012016 )。

C# 代码(不工作)

        // Get cluster allocation information
        PInvoke.LARGE_INTEGER StartingVCN = new PInvoke.LARGE_INTEGER();
        PInvoke.RETRIEVAL_POINTERS_BUFFER Retrieval;
        IntPtr pDest = IntPtr.Zero;
        uint RetSize;
        uint Extents;
        uint BytesReturned = 0;

        // Grab info one extent at a time, until it's done grabbing all the extent data
        // Yeah, well it doesn't give us a way to ask L"how many extents?" that I know of ...
        // btw, the Extents variable tends to only reflect memory usage, so when we have
        // all the extents we look at the structure Win32 gives us for the REAL count!
        Extents = 10;
        RetSize = 0;

        const uint RETRIEVAL_POINTERS_BUFFER_SIZE = 28;

        StartingVCN.QuadPart = 0;

        GCHandle handle = GCHandle.Alloc(StartingVCN, GCHandleType.Pinned);
        IntPtr StartingVCNPtr = handle.AddrOfPinnedObject();

        do
        {
            Extents *= 2;
            RetSize = RETRIEVAL_POINTERS_BUFFER_SIZE + (uint)((Extents - 1) * Marshal.SizeOf(typeof(PInvoke.LARGE_INTEGER)) * 2);

            if (pDest != IntPtr.Zero)
                pDest = Marshal.ReAllocHGlobal(pDest, (IntPtr)RetSize);
            else
                pDest = Marshal.AllocHGlobal((int)RetSize);

            Result = PInvoke.DeviceIoControl
            (
                Handle,
                PInvoke.FSConstants.FSCTL_GET_RETRIEVAL_POINTERS,
                StartingVCNPtr,
                (uint)Marshal.SizeOf(typeof(PInvoke.LARGE_INTEGER)),
                pDest,
                RetSize,
                ref BytesReturned,
                IntPtr.Zero
            );

            if (!Result)
            {
                if (Marshal.GetLastWin32Error() != PInvoke.ERROR_MORE_DATA)
                {
                    Debug.WriteLine("Error #{0} occurred trying to get retrieval pointers for file '{1}", Marshal.GetLastWin32Error(), FullName);

                    Info.Clusters = 0;
                    Info.Attributes.AccessDenied = true;
                    Info.Attributes.Process = false;
                    Info.Fragments.Clear();
                    PInvoke.CloseHandle(Handle);
                    Marshal.FreeHGlobal(pDest);

                    return false;
                }

                Extents++;
            }
        } while (!Result);

        Retrieval = new PInvoke.RETRIEVAL_POINTERS_BUFFER(pDest);

        // Readjust extents, as it only reflects how much memory was allocated and may not
        // be accurate
        Extents = (uint)Retrieval.ExtentCount;

        // Ok, we have the info. Now translate it. hrmrmr

        Info.Fragments.Clear();
        for (int i = 0; i < Extents; i++)
        {
            Extent Add;

            Add.StartLCN = (ulong)Retrieval.Extents[(int)i].Lcn.QuadPart;
            if (i != 0)
                Add.Length = (ulong)Retrieval.Extents[(int)i].NextVcn.QuadPart - (ulong)Retrieval.Extents[(int)i - 1].NextVcn.QuadPart;
            else
                Add.Length = (ulong)Retrieval.Extents[(int)i].NextVcn.QuadPart - (ulong)Retrieval.StartingVcn;

            Info.Fragments.Add(Add);
        }

P/调用代码

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool DeviceIoControl(
        IntPtr hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        [Out] IntPtr lpOutBuffer,
        uint nOutBufferSize,
        ref uint lpBytesReturned,
        IntPtr lpOverlapped);

    [StructLayout(LayoutKind.Explicit, Size = 8)]
    internal struct LARGE_INTEGER
    {
        [FieldOffset(0)]
        public UInt64 QuadPart;
        [FieldOffset(0)]
        public UInt32 LowPart;
        [FieldOffset(4)]
        public Int32 HighPart;
    }
    internal struct Extent
    {
        public LARGE_INTEGER NextVcn;
        public LARGE_INTEGER Lcn;

        public Extent(IntPtr ptr)
        {
            IntPtr extentPtr = ptr;

            NextVcn = (LARGE_INTEGER)Marshal.PtrToStructure(extentPtr, typeof(LARGE_INTEGER));
            Lcn = (LARGE_INTEGER)Marshal.PtrToStructure(IntPtr.Add(extentPtr, 8), typeof(LARGE_INTEGER));
        }
    }

    internal struct RETRIEVAL_POINTERS_BUFFER
    {
        public int ExtentCount;
        public Int64 StartingVcn;
        public List<Extent> Extents;

        public RETRIEVAL_POINTERS_BUFFER(IntPtr ptr)
        {
            this.ExtentCount = (Int32)Marshal.PtrToStructure(ptr, typeof(Int32));

            ptr = IntPtr.Add(ptr, 4); 

            // StartingVcn
            this.StartingVcn = (Int64)Marshal.PtrToStructure(ptr, typeof(Int64));

            ptr = IntPtr.Add(ptr, 8); 

            this.Extents = new List<Extent>();

            for (int i = 0; i < this.ExtentCount; i++)
            {
                this.Extents.Add(new Extent(ptr));

                ptr = IntPtr.Add(ptr, 16);
            }
        }
    }

    /// <summary>
    /// constants lifted from winioctl.h from platform sdk
    /// </summary>
    internal class FSConstants
    {
        const uint FILE_DEVICE_DISK = 0x00000007;
        const uint IOCTL_DISK_BASE = FILE_DEVICE_DISK;
        const uint FILE_DEVICE_FILE_SYSTEM = 0x00000009;

        const uint METHOD_NEITHER = 3;
        const uint METHOD_BUFFERED = 0;

        const uint FILE_ANY_ACCESS = 0;
        const uint FILE_SPECIAL_ACCESS = FILE_ANY_ACCESS;

        public static uint FSCTL_GET_VOLUME_BITMAP = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 27, METHOD_NEITHER, FILE_ANY_ACCESS);
        public static uint FSCTL_GET_RETRIEVAL_POINTERS = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 28, METHOD_NEITHER, FILE_ANY_ACCESS);
        public static uint FSCTL_MOVE_FILE = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 29, METHOD_BUFFERED, FILE_SPECIAL_ACCESS);

        public static uint IOCTL_DISK_GET_DRIVE_GEOMETRY = CTL_CODE(IOCTL_DISK_BASE, 0x0000, METHOD_BUFFERED, FILE_ANY_ACCESS);

        static uint CTL_CODE(uint DeviceType, uint Function, uint Method, uint Access)
        {
            return ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method);
        }
    }

C++ 到 C# 代码(工作)

C#代码

        uint Extents = 0;
        ulong StartingVcn = 0;
        IntPtr ExtentsPtr = IntPtr.Zero;

        bool ret = PInvoke.GetRetrievalPointers(Handle, ref Extents, ref StartingVcn, ref ExtentsPtr);

        IntPtr ptr = ExtentsPtr;

        Info.Fragments.Clear();

        for (uint i = 0; i < Extents; i++)
        {
            PInvoke.Extent Extent = new PInvoke.Extent(ptr);
            Extent Add;

            Add.StartLCN = Extent.Lcn.QuadPart;

            if (i != 0)
            {
                // Get previous extent
                PInvoke.Extent PrevExtent = new PInvoke.Extent(IntPtr.Subtract(ptr, 16));
                Add.Length = Extent.NextVcn.QuadPart - PrevExtent.NextVcn.QuadPart;
            }
            else
            {
                Add.Length = Extent.NextVcn.QuadPart - StartingVcn;
            }

            Info.Fragments.Add(Add);

            ptr = IntPtr.Add(ptr, 16);
        }

P/调用 C# 代码

    [DllImport("DLL.dll", CallingConvention=CallingConvention.Cdecl)]
    internal static extern bool GetRetrievalPointers(
        IntPtr hFile,
        [MarshalAs(UnmanagedType.U4)] ref uint ExtentCount,
        [MarshalAs(UnmanagedType.U8)] ref ulong StartingVcn,
        ref IntPtr ExtentsPtr
        );

C++代码

#ifdef __cplusplus
extern "C" {
#endif
    EXTERNAPI bool GetRetrievalPointers(HANDLE hFile, DWORD &ExtentCount, UINT64 &StartingVcn, void **Extents) {
        // Get cluster allocation information
        STARTING_VCN_INPUT_BUFFER  StartingVCN;
        RETRIEVAL_POINTERS_BUFFER *Retrieval;
        unsigned __int64           RetSize;
        unsigned __int64           ExtentsCount;
        DWORD                      BytesReturned;
        BOOL                       Result;

        // Grab info one extent at a time, until it's done grabbing all the extent data
        // Yeah, well it doesn't give us a way to ask L"how many extents?" that I know of ...
        // btw, the Extents variable tends to only reflect memory usage, so when we have
        // all the extents we look at the structure Win32 gives us for the REAL count!
        ExtentsCount = 10;
        Retrieval = NULL;
        RetSize = 0;
        StartingVCN.StartingVcn.QuadPart = 0;

        do
        {
            ExtentsCount *= 2;
            RetSize = sizeof (RETRIEVAL_POINTERS_BUFFER)+((ExtentsCount - 1) * sizeof (LARGE_INTEGER)* 2);

            if (Retrieval != NULL)
                Retrieval = (RETRIEVAL_POINTERS_BUFFER *)realloc(Retrieval, RetSize);
            else
                Retrieval = (RETRIEVAL_POINTERS_BUFFER *)malloc(RetSize);

            Result = DeviceIoControl
                (
                hFile,
                FSCTL_GET_RETRIEVAL_POINTERS,
                &StartingVCN,
                sizeof (StartingVCN),
                Retrieval,
                RetSize,
                &BytesReturned,
                NULL
                );

            if (Result == FALSE)
            {
                DWORD dwLastError = GetLastError();
                if (dwLastError != ERROR_MORE_DATA)
                {
                    free(Retrieval);

                    return (false);
                }

                ExtentsCount++;
            }
        } while (Result == FALSE);

        // Readjust extents, as it only reflects how much memory was allocated and may not
        // be accurate
        ExtentsCount = Retrieval->ExtentCount;

        // Ok, we have the info. Now translate it. hrmrmr
        ExtentCount = ExtentsCount;
        StartingVcn = Retrieval->StartingVcn.QuadPart;
        *Extents = Retrieval->Extents;

        return true;
    }

#ifdef __cplusplus
};
#endif

其他

我注意到的一件事是,当我将 Extents[0]->NextVcnExtents[0]->Lcn 转换为 LARGE_INTEGER(参见上面的代码)然后 HighPartLowPart 切换,如下所示。但是,用 LowPart 切换 HighPart 不起作用 RETRIEVAL_POINTERS_BUFFER->StartingVcn(因为 HighPart/LowPart 是一个数字,它们应该都是 0)。

错误的 LARGE_INTEGER

LARGE_INTEGER.HighPart = 2;
LARGE_INTEGER.LowPart = 0;

应为 LARGE_INTEGER

LARGE_INTEGER.HighPart = 0;
LARGE_INTEGER.LowPart = 2;

我想知道是否有什么东西正在改变内存,或者我是否没有正确使用内存?有什么想法吗?

最佳答案

Extents[0]->NextVcn is 4294967296 and Extents[0]->Lcn is 934370135244800

这些不是随机数。将它们转换为十六进制,您会发现它们与您在 C++ 代码中获得的值完全相同,只是偏移了 4 个字节。换句话说,您正在读取未对齐的数据。该错误位于此处:

    ptr = IntPtr.Add(ptr, 4); 

您将 RETRIEVAL_POINTERS_BUFFER.StartingVcn 的偏移量硬编码为 4。但是该字段需要对齐到 8,因为它包含 64 位值。所以字段之间有 4 个字节的额外填充。修复:

    ptr = IntPtr.Add(ptr, 8); 

在 32 位和 64 位代码中都可以。

关于c# - 无法将 FSCTL_GET_RETRIEVAL_POINTERS 调用移植到 C#,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23896304/

相关文章:

c# - 验证无效字符串值 Mysql : '\xD9\xBE\xD8\xA7\xDA\xA9

c - 在c中与硬件交互

winapi - 两次调用 CoInitialize()

c++ - Win32 SetForegroundWindow 不可靠

c++ - 在 C++ 中调用 delete/delete[] 时中断调试器

c++ - 将很多符号表达式从 x^2 或 x^3 转换为 pow(x,n)

c# - 如何从 ABBBBBGG GGGRRRRR 两个字节格式中读取颜色分量?

c# - 给定 C# 中的基类,如何实现该类?

c# - 如何从匿名类型列表中迭代?

C++ - 读取文件行