Marshal.PtrToStructure()和结构DEVMODE中的char数组的问题

Ult*_*nks 4 c# pinvoke marshalling

我在使用Marshal.PtrToStructure()从指向DEVMODE类型结构的指针中提取数据时遇到问题.以下是DEVMODE结构上MSDN条目的链接.

我对此结构的C#实现如下:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DEVMODE
{
    public const int CCHDEVICENAME = 32;
    public const int CCHFORMNAME = 32;

    public unsafe fixed char dmDeviceName [CCHDEVICENAME];
    public Int16 dmSpecVersion;
    public Int16 dmDriverVersion;
    public Int16 dmSize;
    public Int16 dmDriverExtra;
    public DM_FIELD_TYPE dmFields;

    public Int16 dmOrientation;
    public Int16 dmPaperSize;
    public Int16 dmPaperLength;
    public Int16 dmPaperWidth;
    public Int16 dmScale;
    public Int16 dmCopies;
    public Int16 dmDefaultSource;
    public Int16 dmPrintQuality;

    public POINTL dmPosition;
    public Int32 dmDisplayOrientation;
    public Int32 dmDisplayFixedOutput;

    public short dmColor;
    public short dmDuplex;
    public short dmYResolution;
    public short dmTTOption;
    public short dmCollate;

    public unsafe fixed char dmFormName [CCHFORMNAME];
    public Int16 dmLogPixels;
    public Int32 dmBitsPerPel;
    public Int32 dmPelsWidth;
    public Int32 dmPelsHeight;
    public Int32 dmDisplayFlags;
    public Int32 dmNup;
    public Int32 dmDisplayFrequency;
    public Int32 dmICMMethod;
    public Int32 dmICMIntent;
    public Int32 dmMediaType;
    public Int32 dmDitherType;
    public Int32 dmReserved1;
    public Int32 dmReserved2;
    public Int32 dmPanningWidth;
    public Int32 dmPanningHeight;

    public DEVMODE(byte[] data)
    {
        unsafe
        {
            fixed (byte* packet = &data[0])
            {
                this = *(DEVMODE*)packet;
            }
        }
    }

}

[Flags()]
public enum DM_FIELD_TYPE : int
{
    /* field selection bits */
    DM_ORIENTATION          = 0x00000001,
    DM_PAPERSIZE            = 0x00000002,
    DM_PAPERLENGTH          = 0x00000004,
    DM_PAPERWIDTH           = 0x00000008,
    DM_SCALE                = 0x00000010,
    DM_POSITION             = 0x00000020,
    DM_NUP                  = 0x00000040,
    DM_DISPLAYORIENTATION   = 0x00000080,
    DM_COPIES               = 0x00000100,
    DM_DEFAULTSOURCE        = 0x00000200,
    DM_PRINTQUALITY         = 0x00000400,
    DM_COLOR                = 0x00000800,
    DM_DUPLEX               = 0x00001000,
    DM_YRESOLUTION          = 0x00002000,
    DM_TTOPTION             = 0x00004000,
    DM_COLLATE              = 0x00008000,
    DM_FORMNAME             = 0x00010000,
    DM_LOGPIXELS            = 0x00020000,
    DM_BITSPERPEL           = 0x00040000,
    DM_PELSWIDTH            = 0x00080000,
    DM_PELSHEIGHT           = 0x00100000,
    DM_DISPLAYFLAGS         = 0x00200000,
    DM_DISPLAYFREQUENCY     = 0x00400000,
    DM_ICMMETHOD            = 0x00800000,
    DM_ICMINTENT            = 0x01000000,
    DM_MEDIATYPE            = 0x02000000,
    DM_DITHERTYPE           = 0x04000000,
    DM_PANNINGWIDTH         = 0x08000000,
    DM_PANNINGHEIGHT        = 0x10000000,
    DM_DISPLAYFIXEDOUTPUT   = 0x20000000
}

public struct POINTL
{
    public Int32 x;
    public Int32 y;
}
Run Code Online (Sandbox Code Playgroud)

在这种结构中有2个字符数组"dmDeviceName"和"dmFormName".两者都是32个字符长.问题是,当我尝试从指针编组结构DEVMODE时,这些字符数组没有正确填充.例如,dmDeviceName将只包含实际设备名称的第一个字符.其余的数组条目只是'\ 0'.我正在进行编组的代码行如下:

DEVMODE devMode = (DEVMODE)Marshal.PtrToStructure(aData[i].NotifyData.Data.pBuf, typeof(DEVMODE));
Run Code Online (Sandbox Code Playgroud)

"aData [i] .NotifyData.Data.pBuf"是指向DEVMODE类型结构的有效指针.我知道这有两个原因.

  1. 此结构是从名为FindNextPrinterChangeNotification()的打印机驱动程序调用返回的信息的子集.我使用它来捕获打印作业信息,当我捕获打印作业并使用上面的代码编组DEVMODE对象时,"dmCopies"字段始终与该作业中打印的副本数完全正确.所以有点奇怪的是,如果一个结构中的第12个成员在它之前的某个部分看起来不是正确的,那么它是如何被正确地编组的.

  2. 我使用Marshal.ReadByte()来强制读取aData [i] .NotifyData.Data.pBuf所指向的前100个字节,果然,第一个字节集合是打印机设备的完整名称.所以我知道那里的信息.

无论出于何种原因,当我使用Marshal.PtrToStructure()时,它似乎无法正确填充字符数组.我很确定大多数其他变量都是正确的,但我怀疑是因为数组问题.有谁知道这里发生了什么.

--EDIT - 根据要求,这是填充aData []数组的代码:

private PRINTER_NOTIFY_INFO_DATA[] MarshalOutPrinterNotifyInfoDataArray(IntPtr ppPrinterNotifyInfo)
{
    //Dereferencing ppPrinterNotifyInfo and setting NotifyInfoStruct to it.
    PRINTER_NOTIFY_INFO NotifyInfoStruct = (PRINTER_NOTIFY_INFO)Marshal.PtrToStructure(ppPrinterNotifyInfo, typeof(PRINTER_NOTIFY_INFO));

    //Creating a point to point to the PRINTER_NOTIFY_INFO and then moving it to the end of the structure where the
    //aData[] member would begin.
    int paData = (int)ppPrinterNotifyInfo + Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO));

    //Creating an array to hold all the elements of our aData array.
    PRINTER_NOTIFY_INFO_DATA[] data = new PRINTER_NOTIFY_INFO_DATA[NotifyInfoStruct.Count];

    //looping through all the PRINTER_NOTIFY_INFO_DATA elments in the aData member and adding them to our local array.
    for (uint i = 0; i < NotifyInfoStruct.Count; i++)
    {
        //extracting out a single PRINTER_NOTIFY_INFO_DATA item and storing it in our local array
        data[i] = (PRINTER_NOTIFY_INFO_DATA)Marshal.PtrToStructure((IntPtr)paData, typeof(PRINTER_NOTIFY_INFO_DATA));

        //moving our pointer to the next PRINTER_NOTIFY_INFO_DATA item
        paData += Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO_DATA));
    }

    return data;
}



private void SomeRoutine()
{
    //////////////////
    /// some code here
    //////////////////

    //retrieving information about the most recent change notification for a change notification object associated with the printer
    FindNextPrinterChangeNotification(m_ManualResetEvent.SafeWaitHandle.DangerousGetHandle(), out pdwChangeFlags, null, out ppPrinterNotifyInfo);

    //Need to extract our PRINTER_NOTIFY_INFO_DATA array out of the PRINTER_NOTIFY_INFO structure
    PRINTER_NOTIFY_INFO_DATA[] aData = MarshalOutPrinterNotifyInfoDataArray(ppPrinterNotifyInfo);

    //////////////////
    /// some code here
    //////////////////
}
Run Code Online (Sandbox Code Playgroud)

Jam*_*ton 7

使用字符串:

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string dmDeviceName;
Run Code Online (Sandbox Code Playgroud)

请参阅:http://pinvoke.net/default.aspx/Structures/DEVMODE.html

编辑: 我没有注意到发布我的快速响应,因为我专注于字符串(并且在结构的最开始有一个,这意味着其余的结构元素不会影响事物),但作为其他人已经指出你在DEVMODE结构中的工会有一个大问题.联合中的元素不是按顺序排列,而是占据内存中的相同空间:一次只能使用一个联合元素.例如:

union {
    DWORD dmDisplayFlags;
    DWORD dmNup;
};
Run Code Online (Sandbox Code Playgroud)

意味着dmDisplayFlags和dmNup对于同一块内存本质上是不同的名称.即dmDisplayFlags和dmNup都存储在距结构开头116字节的偏移处.更改dmNup会导致dmDisplayFlags的值也发生变化,反之亦然.使用C#代码:

public Int32 dmDisplayFlags;
public Int32 dmNup;
Run Code Online (Sandbox Code Playgroud)

意味着它们被顺序存储,即在偏移116和120处存储.这扰乱了整个结构的布局.要解决此问题,您需要使用显式布局并手动定义字段偏移.查看我之前在pinvoke.net上给出的链接,了解如何在此特定结构上执行此操作.请注意dmDisplayFlags和dmNup如何具有相同的字段偏移量.由于C#本身不支持联合,因此对于那些需要它的特殊互操作场景来处理联合会这是一种有点笨拙的方法.

我建议修复你的工会问题,然后按照最初的建议使用ByValTStr的字符串(总之,使用pinvoke.net上的内容).看看它是否能让你获得更好的效果.

其他人认为这是一个Unicode问题,但我不认为它是什么.文档说FindNextPrinterChangeNotification在Unicode中不可用.如果你在字节级检查结构并说它不是Unicode - 我绝对相信你.

来自文档:

" ByValTStr:用于出现在结构中的内嵌式固定长度字符数组.与ByValTStr一起使用的字符类型由应用于该实现的System.Runtime.InteropServices.StructLayoutAttribute的System.Runtime.InteropServices.CharSet参数确定.包含结构.始终使用MarshalAsAttribute.SizeConst字段来指示数组的大小.

.NET Framework ByValTStr类型在结构中的行为类似于C风格的固定大小的字符串(例如,char s [5]).托管代码中的行为与Microsoft Visual Basic 6.0行为不同,后者不是以null结尾(例如,MyString As String*5)."

我似乎很清楚,这应该是普遍接受的"正确"方式.