如何在C#中编组WCHAR *

时间:2016-06-11 11:14:53

标签: c# .net winapi pinvoke

我正在尝试按下function来收到一些WCHAR <paramName>[1]个参数。

从我在多个地方读过的内容来看,在C / C ++中你实际上无法将数组传递给函数,而是将它们转换为包含数组第一个元素的指针,这意味着数组长度变得无关紧要因此WCHAR <paramName>[1]WCHAR* <paramName>相同。

通常我会在C#中将其声明为StringBuilder并将其编组为LPWStr,但在这种特殊情况下会抛出各种错误。

基本上,上面的函数接收GET_VIRTUAL_DISK_INFO,而Accessor又包含一些结构和其他松散字段的并集。并且联盟中的一些结构会收到WCHAR <paramName>[1]

当我尝试将其整理为LPWStr并将我的字段声明为StringBuilderstring时,我收到错误[...] contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

我也尝试将其声明为IntPtr,然后使用Marshal.PtrToStringAuto(<IntPtr>),但我得到一个空字符串。

我的C#代码包含所有结构,枚举等:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Permissions;

namespace ConsoleApplication1
{
    class Program
    {
        public static Guid VirtualStorageTypeVendorMicrosoft = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B");

        static void Main(string[] args)
        {
            var handle = new VirtualDiskSafeHandle();
            var storageType = new VirtualStorageType
            {
                DeviceId = VirtualStorageDeviceType.Vhdx,
                VendorId = VirtualStorageTypeVendorMicrosoft
            };

            var parameters = new OpenVirtualDiskParameters
            {
                Version = OpenVirtualDiskVersion.Version2
            };

            var result = OpenVirtualDisk(ref storageType, "D:\\Test2.vhdx", VirtualDiskAccessMask.None, OpenVirtualDiskFlag.None,
                ref parameters, ref handle);

            if (result != 0)
            {
                throw new Win32Exception((int) result);
            }

            var info = new GetVirtualDiskInfo {Version = GetVirtualDiskInfoVersion.PhysicalDisk};
            var infoSize = (uint) Marshal.SizeOf(info);
            uint sizeUsed = 0;

            result = GetVirtualDiskInformation(handle, ref infoSize, ref info, ref sizeUsed);

            if (result != 0)
            {
                throw new Win32Exception((int) result);
            }

            Console.WriteLine($"LogicalSizeSector = {info.Union.PhysicalDisk.LogicalSectorSize}");
            Console.WriteLine($"PhysicalSizeSector = {info.Union.PhysicalDisk.PhysicalSectorSize}");
            Console.ReadLine();
        }

        [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
        public static extern uint OpenVirtualDisk(
            [In]        ref VirtualStorageType virtualStorageType,
            [In]            string path,
            [In]            VirtualDiskAccessMask virtualDiskAccessMask,
            [In]            OpenVirtualDiskFlag flags,
            [In]        ref OpenVirtualDiskParameters parameters,
            [In, Out]   ref VirtualDiskSafeHandle handle);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(
            [In]            IntPtr hObject);

        [DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
        public static extern uint GetVirtualDiskInformation(
            [In]            VirtualDiskSafeHandle virtualDiskHandle,
            [In, Out]   ref uint virtualDiskInfoSize,
            [In, Out]   ref GetVirtualDiskInfo virtualDiskInfo,
            [In, Out]   ref uint sizeUsed);

        [SecurityPermission(SecurityAction.Demand)]
        public class VirtualDiskSafeHandle : SafeHandle
        {
            public VirtualDiskSafeHandle() : base(IntPtr.Zero, true) { }

            public override bool IsInvalid => IsClosed || (handle == IntPtr.Zero);

            public bool IsOpen => !IsInvalid;

            protected override bool ReleaseHandle()
            {
                return CloseHandle(handle);
            }

            public override string ToString()
            {
                return handle.ToString();
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct OpenVirtualDiskParameters
        {
            public OpenVirtualDiskVersion Version; //OPEN_VIRTUAL_DISK_VERSION
            public OpenVirtualDiskParametersUnion Union;
        }

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
        public struct OpenVirtualDiskParametersUnion
        {
            [FieldOffset(0)]
            public OpenVirtualDiskParametersVersion1 Version1;

            [FieldOffset(0)]
            public OpenVirtualDiskParametersVersion2 Version2;

            [FieldOffset(0)]
            public OpenVirtualDiskParametersVersion3 Version3;
        }

        /// <summary>
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct OpenVirtualDiskParametersVersion1
        {
            public uint RWDepth; //ULONG
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct OpenVirtualDiskParametersVersion2
        {
            public bool GetInfoOnly; //BOOL
            public bool ReadOnly; //BOOL
            public Guid ResiliencyGuid; //GUID 
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct OpenVirtualDiskParametersVersion3
        {
            public bool GetInfoOnly; //BOOL
            public bool ReadOnly; //BOOL
            public Guid ResiliencyGuid; //GUID 
            public Guid SnapshotId; //GUID
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct GetVirtualDiskInfo
        {
            public GetVirtualDiskInfoVersion Version; //GET_VIRTUAL_DISK_INFO_VERSION
            public GetVirtualDiskInfoUnion Union;
        }

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
        public struct GetVirtualDiskInfoUnion
        {
            [FieldOffset(0)] public GetVirtualDiskInfoSize Size;
            [FieldOffset(0)] public Guid Identifier; //GUID
            [FieldOffset(0)] public GetVirtualDiskInfoParentLocation ParentLocation;
            [FieldOffset(0)] public Guid ParentIdentifier; //GUID
            [FieldOffset(0)] public uint ParentTimestamp; //ULONG
            [FieldOffset(0)] public VirtualStorageType VirtualStorageType; //VIRTUAL_STORAGE_TYPE
            [FieldOffset(0)] public uint ProviderSubtype; //ULONG
            [FieldOffset(0)] public bool Is4kAligned; //BOOL
            [FieldOffset(0)] public bool IsLoaded; //BOOL
            [FieldOffset(0)] public GetVirtualDiskInfoPhysicalDisk PhysicalDisk;
            [FieldOffset(0)] public uint VhdPhysicalSectorSize; //ULONG
            [FieldOffset(0)] public ulong SmallestSafeVirtualSize; //ULONGLONG
            [FieldOffset(0)] public uint FragmentationPercentage; //ULONG
            [FieldOffset(0)] public Guid VirtualDiskId; //GUID
            [FieldOffset(0)] public GetVirtualDiskInfoChangeTrackingState ChangeTrackingState;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct GetVirtualDiskInfoSize
        {
            public ulong VirtualSize; //ULONGLONG
            public ulong PhysicalSize; //ULONGLONG
            public uint BlockSize; //ULONG
            public uint SectorSize; //ULONG
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct GetVirtualDiskInfoParentLocation
        {
            public bool ParentResolved; //BOOL
            public IntPtr ParentLocationBuffer; //WCHAR[1]
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct GetVirtualDiskInfoPhysicalDisk
        {
            public uint LogicalSectorSize; //ULONG
            public uint PhysicalSectorSize; //ULONG
            public bool IsRemote; //BOOL
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct GetVirtualDiskInfoChangeTrackingState
        {
            public bool Enabled; //BOOL
            public bool NewerChanges; //BOOL
            public IntPtr MostRecentId; //WCHAR[1]
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct VirtualStorageType
        {
            public VirtualStorageDeviceType DeviceId; //ULONG
            public Guid VendorId; //GUID
        }

        public enum GetVirtualDiskInfoVersion
        {
            Unspecified = 0,
            Size = 1,
            Identifier = 2,
            ParentLocation = 3,
            ParentIdentifier = 4,
            ParentTimestamp = 5,
            VirtualStorageType = 6,
            ProviderSubtype = 7,
            Is4KAligned = 8,
            PhysicalDisk = 9,
            VhdPhysicalSectorSize = 10,
            SmallestSafeVirtualSize = 11,
            Fragmentation = 12,
            IsLoaded = 13,
            VirtualDiskId = 14,
            ChangeTrackingState = 15
        }

        public enum VirtualStorageDeviceType
        {
            Unknown = 0,
            Iso = 1,
            Vhd = 2,
            Vhdx = 3,
            Vhdset = 4
        }

        public enum VirtualDiskAccessMask
        {
            None = 0x00000000,
            AttachRo = 0x00010000,
            AttachRw = 0x00020000,
            Detach = 0x00040000,
            GetInfo = 0x00080000,
            Create = 0x00100000,
            Metaops = 0x00200000,
            Read = 0x000d0000,
            All = 0x003f0000,
            Writable = 0x00320000
        }

        [Flags]
        public enum OpenVirtualDiskFlag
        {
            None = 0x00000000,
            NoParents = 0x00000001,
            BlankFile = 0x00000002,
            BootDrive = 0x00000004,
            CachedIo = 0x00000008,
            CustomDiffChain = 0x00000010,
            ParentCachedIo = 0x00000020,
            VhdsetFileOnly = 0x00000040
        }

        public enum OpenVirtualDiskVersion
        {
            Unspecified = 0,
            Version1 = 1,
            Version2 = 2,
            Version3 = 3,
        }
    }
}

请注释掉该行:

[FieldOffset(0)] public GetVirtualDiskInfoChangeTrackingState ChangeTrackingState;

如果不这样,结构结果将完全搞砸,仍然试图找出原因。

1 个答案:

答案 0 :(得分:2)

将函数调用中的结构编组为IntPtr。你将需要使用Marshal.AllocHGlobal或其他类似的技术来获得一块非托管内存,因为元帅不会为你做这件事。然后,您可以手动加载尺寸成员,或使用Marshal.StructureToPtr

从那里使用Marshal.OffsetOf获取Union成员的偏移量。完成后,使用Marshal.ReadMarshal.PtrToStringUni来获取数据。例如,使用父位置信息:

IntPtr raw = Marshal.AllocHGlobal(1024);

// This is the GetVirtualDiskInfo from your provided code.
GetVirtualDiskInfo info = new GetVirtualDiskInfo();
info.Version = GetVirtualDiskInfoVersion.ParentLocation;
Marshal.StructureToPtr(info, raw, true);

Class1.Test(raw); // Replace this with your call to the function,
                  // This is a call to a C++/CLI method I wrote to stuff data
                  // into the structure.

IntPtr offsetToUnion = Marshal.OffsetOf(typeof(GetVirtualDiskInfo), "Union");
IntPtr data = raw + offsetToUnion.ToInt32();

bool parentResolved = Marshal.ReadInt32(data) != 0;
string parentLocationBuffer = Marshal.PtrToStringUni(data + 4);

Marshal.FreeHGlobal(raw); // Don't forget this!

这是C ++ / CLI中加载数据以进行测试的方法:

static void Test(IntPtr ptr)
{
    GET_VIRTUAL_DISK_INFO* info = (GET_VIRTUAL_DISK_INFO*)ptr.ToPointer();
    info->ParentLocation.ParentResolved = TRUE;
    memcpy(info->ParentLocation.ParentLocationBuffer, L"123456789", 20);
}