【问题标题】:How to marshal WCHAR* in C#如何在 C# 中编组 WCHAR*
【发布时间】:2016-06-12 06:43:12
【问题描述】:

我正在尝试 pinvoke 接收几个 WCHAR <paramName>[1] 参数的function

根据我在多个地方阅读的内容,在 C/C++ 中,您实际上不能将数组传递给函数,而是将它们转换为保存数组第一个元素的指针,这意味着数组长度变得无关紧要因此WCHAR <paramName>[1]WCHAR* <paramName> 相同。

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

基本上,上面的函数接收一个GET_VIRTUAL_DISK_INFO,它又持有一些结构和其他松散字段的联合。并且它是联合中的几个结构接收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;

如果没有结构结果将完全搞砸,仍然试图找出原因。

【问题讨论】:

  • 正如@DavidHeffernan 最近教我的那样,WCHAR a[1]WCHAR* a相同。在某些情况下,数组会退化为指针,但结构中的数据是内联的。
  • 这很公平,但是WCHAR a[1] 将是一个长度为 1 的数组,这不足以容纳该数组应该包含的内容:父虚拟磁盘的路径。
  • 此外,函数GetVirtualDiskInformation 接收到一个out SizeUsed 参数,SizeUsed 根据父虚拟磁盘的位置而变化,所以我知道它返回的字符或多或少。跨度>
  • WCHAR a[1] 是一个灵活长度的数组,其内容实际上是内联结构的数据。第一个元素&amp;(a[0])地址 相当于一个字符串。另请参阅:Why do some structures end with an array of size 1?
  • 想想吧。一个指针包含一个地址。您想要该字段的地址。您将不得不停止尝试自动编组结构。分配一块内存并将其地址传递给函数。然后手动编组您需要的字段。

标签: c# .net winapi pinvoke


【解决方案1】:

将函数调用中的结构编组为IntPtr。您将需要使用Marshal.AllocHGlobal 或其他类似技术来获取一块非托管内存,因为 Marshal 不会为您执行此操作。然后,您可以手动加载 size 成员,或使用 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);
}

【讨论】:

  • 您能否提供一个更清晰的示例,其中还使用了Marshal.AllocHGlobalMarshal.StructureToPtr,以及如何声明Union?我自己试过了,但还是失败了。
  • 添加了一些代码来演示第一段,以及用于验证它的测试代码。
  • 谢谢你,但我仍然没有让它工作。您能否也发布您的GetVirtualDiskInfo 结构以及GetVirtualDiskInfoParentLocation 结构的外观?
  • 我确实从您的粘贴箱中复制了保险箱手柄下方的所有内容,但没有进行任何更改。
  • 所以 Union 保持不变,[FieldOffset(0)] public GetVirtualDiskInfoParentLocation ParentLocation;GetVirtualDiskInfoParentLocation 结构分别为 bool 和 IntPtr?不能让它那样工作。它分别返回 false 和 null 字符串。我知道我正在使用的磁盘是一个差异磁盘,所以它有一个父磁盘并且应该返回 true(例如,它返回 true 与我最初拥有的代码)
猜你喜欢
  • 2013-02-02
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-01
  • 1970-01-01
相关资源
最近更新 更多