在结构上DEVMODE)麻烦Marshal.PtrToStructure(和字符数组 [英] Trouble with Marshal.PtrToStructure() and char arrays in structure DEVMODE

查看:1445
本文介绍了在结构上DEVMODE)麻烦Marshal.PtrToStructure(和字符数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用Marshal.PtrToStructure()来了一个指针的数据提取到类型DEVMODE的结构的问题。 这里是一个MSDN链接。进入上DEVMODE结构



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

  [StructLayout(LayoutKind.Sequential,字符集= CharSet.Ansi)] 
公共结构DEVMODE
{
公共const int的CCHDEVICENAME = 32;
公共const int的CCHFORMNAME = 32;

公共不安全固定炭dmDeviceName [CCHDEVICENAME]
公众的Int16 dmSpecVersion;
公众的Int16 dmDriverVersion;
公众的Int16 dmSize;
公众的Int16 dmDriverExtra;
公共DM_FIELD_TYPE dmFields中;

公众的Int16 dmOrientation;
公众的Int16 dmPaperSize;
公众的Int16 dmPaperLength;
公众的Int16 dmPaperWidth;
公众的Int16 dmScale;
公众的Int16 dmCopies;
公众的Int16 dmDefaultSource;
公众的Int16 dmPrintQuality;

公共POINTL dmPosition;
公众的Int32 dmDisplayOrientation;
公众的Int32 dmDisplayFixedOutput;

公共短dmColor;
公共短dmDuplex;
公共短dmYResolution;
公共短dmTTOption;
公共短dmCollat​​e;

公共不安全固定炭dmFormName [CCHFORMNAME]
公众的Int16 dmLogPixels;
公众的Int32 dmBitsPerPel;
公众的Int32 dmPelsWidth;
公众的Int32 dmPelsHeight;
公众的Int32 dmDisplayFlags;
公众的Int32 dmNup;
公众的Int32 dmDisplayFrequency;
公众的Int32 dmICMMethod;
公众的Int32 dmICMIntent;
公众的Int32 dmMediaType;
公众的Int32 dmDitherType;
公众的Int32 dmReserved1;
公众的Int32 dmReserved2;
公众的Int32 dmPanningWidth;
公众的Int32 dmPanningHeight;

公共DEVMODE(字节[]数据)
{
不安全
{
固定(字节*包=&放大器;数据[0])
{
本= *(* DEVMODE)数据包;
}
}
}

}

[旗()]
公共枚举DM_FIELD_TYPE:INT
{
/ *字段选择位* /
DM_ORIENTATION = 00000001,
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
}

公共结构POINTL
{
公众的Int32 X;
公众的Int32ÿ;
}

在此结构中有2个字符数组dmDeviceName和dmFormName。两者都是32个字符长。问题是,当我试图从一个指针元帅出来的结构DEVMODE,这些字符数组没有被正确填写。例如,dmDeviceName只会有实际的设备名称的第一个字符。数组项的其余部分将只是'\0。代码我是该行正在做封送处理如下:

  DEVMODE DEVMODE =(DEVMODE)Marshal.PtrToStructure( ADATA [I] .NotifyData.Data.pBuf的typeof(DEVMODE)); 



ADATA [I] .NotifyData.Data.pBuf是一个有效的指针类型的结构DEVMODE。我知道这有2个原因。




  1. 此结构是()命名FindNextPrinterChangeNotification打印机驱动程序调用返回的信息的子集。我用这个来捕捉打印作业的信息,当我捕捉打印作业,并使用上述代码来封出DEVMODE对象时,dmCopies字段总是被印在作业的份数完全正确。因此,它有点奇怪怎么当一些那些之前它似乎不是在一个结构中的12件得到封正确。


  2. 我用元帅。 ReadByte()来强行读取ADATA [I] .NotifyData.Data.pBuf指着,果然第一个100字节,字节的首部作品是打印机设备的完整确切名称。所以我知道的信息是存在的。




无论出于何种原因,当我使用Marshal.PtrToStructure()它似乎并没有能够正确填写该字符数组。我敢肯定大部分的其他变量是正确的,但我有我的,因为数组问题的疑虑。有谁知道什么是怎么回事



- 编辑 -
按照要求,这里是填充ADATA []数组的代码:

 私人PRINTER_NOTIFY_INFO_DATA [] MarshalOutPrinterNotifyInfoDataArray(IntPtr的ppPrinterNotifyInfo)
{
//提领ppPrinterNotifyInfo并设置NotifyInfoStruct它。
PRINTER_NOTIFY_INFO NotifyInfoStruct =(PRINTER_NOTIFY_INFO)Marshal.PtrToStructure(ppPrinterNotifyInfo的typeof(PRINTER_NOTIFY_INFO));

//创建点到点到PRINTER_NOTIFY_INFO,然后将其移动到那里的
// ADATA []构件将开始该结构的端部。
INT PADATA =(int)的ppPrinterNotifyInfo + Marshal.SizeOf(typeof运算(PRINTER_NOTIFY_INFO));

//创建一个数组来保存我们的ADATA阵列中的所有元素。
PRINTER_NOTIFY_INFO_DATA []数据=新PRINTER_NOTIFY_INFO_DATA [NotifyInfoStruct.Count]

//穿过ADATA成员的所有PRINTER_NOTIFY_INFO_DATA elments循环并将其添加到我们的本地阵列。
为(UINT I = 0; I< NotifyInfoStruct.Count;我++)
{
//提取出单PRINTER_NOTIFY_INFO_DATA项目,它在我们当地数组
数据存储[ I] =(PRINTER_NOTIFY_INFO_DATA)Marshal.PtrToStructure((IntPtr的)PADATA的typeof(PRINTER_NOTIFY_INFO_DATA));

//把我们的指针指向下一个PRINTER_NOTIFY_INFO_DATA项目
PADATA + = Marshal.SizeOf(typeof运算(PRINTER_NOTIFY_INFO_DATA));
}

返回数据;
}



私人无效SomeRoutine()
{
/////////////// ///
///一些代码在这里
//////////////////

//检索有关的信息最多用于打印机
FindNextPrinterChangeNotification相关(m_ManualResetEvent.SafeWaitHandle.DangerousGetHandle(),出pdwChangeFlags,空出ppPrinterNotifyInfo)的变化通知对象最近的更改通知;

//需要提取我们PRINTER_NOTIFY_INFO_DATA阵列出PRINTER_NOTIFY_INFO结构
PRINTER_NOTIFY_INFO_DATA [] = ADATA MarshalOutPrinterNotifyInfoDataArray(ppPrinterNotifyInfo)的;

//////////////////
///一些代码在这里
////////// ////////
}


解决方案

使用的字符串:

  [的MarshalAs(UnmanagedType.ByValTStr,SizeConst = CCHDEVICENAME)] 
公共字符串dmDeviceName;

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



修改张贴我,因为我是专注于字符串(并有一个在结构的开始,这意味着结构元素的其余部分可以在不影响事物)快速响应的时候我没有注意到,但正如其他人所指出的那样,你有在DEVMODE结构工会的一个大问题。在联合元件不依次躺在而是占用在存储器中的相同的空间:只有联合元件中的一个可以在一个时间使用。例如:

 工会{
DWORD dmDisplayFlags;
DWORD dmNup;
};



意味着dmDisplayFlags和dmNup基本上为相同的内存块不同的名字。即dmDisplayFlags和dmNup都存储在一个从该结构的开始的116个字节的偏移。更改dmNup导致dmDisplayFlags也改变,反之亦然的价值。使用C#代码,如:

 公众的Int32 dmDisplayFlags; 
公众的Int32 dmNup;



意味着他们得到顺序存储,即在偏移116和120这打乱整个布局结构体。为了解决这个问题,你需要使用一个明确的布局和手动定义字段偏移。看看我以前在pinvoke.net了有关如何做,在这个特殊结构的例子的链接。请注意如何dmDisplayFlags和dmNup有字段中抵消。由于C#本身并不支持工会,这是处理那些特殊的互操作场景像这样的需要它的工会有点-笨拙的方法。



我建议修复你的工会问题,然后使用字符串与ByValTStr最初建议(在总结,用的是上pinvoke.net)。看它是否让你更好的结果。



也有人认为它是一个Unicode的问题,但我不认为这是它是什么。该文件说,FindNextPrinterChangeNotification是不能用于支持Unicode。如果您在字节级检测结构,并说这不是Unicode的 - 我绝对相信你。



从文档:



ByValTStr :用于在网上,出现在结构内的固定长度的字符数组与ByValTStr使用的字符类型由的System.Runtime.InteropServices.CharSet参数确定在System.Runtime.InteropServices.StructLayoutAttribute应用于包含结构始终使用MarshalAsAttribute.SizeConst字段,以指示数组的大小。



.NET框架ByValTStr行为类型像C风格,在结构内部固定大小的字符串(例如,char S [5])。将在托管代码行为不同于在Microsoft Visual Basic 6.0的行为,这不是空终止(例如,MyString中作为字符串* 5)。



似乎非常清楚,我认为这应该是普遍接受这样做的正确的。


I'm having an issue with using Marshal.PtrToStructure() to extract data out of a pointer to a structure of type DEVMODE. Here is a link to an MSDN entry on the DEVMODE structure.

My C# implementation for this structure is as follows:

[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;
}

In this structure there are 2 character arrays "dmDeviceName" and "dmFormName". Both are 32 characters long. The problem is, when I attempt to marshal out the structure DEVMODE from a pointer, those character arrays are not being filled properly. For example, dmDeviceName will only have the first character of the actual device name. The rest of the array entries will be just '\0'. The line of code I'm that is doing the marshaling is as follows:

DEVMODE devMode = (DEVMODE)Marshal.PtrToStructure(aData[i].NotifyData.Data.pBuf, typeof(DEVMODE));

"aData[i].NotifyData.Data.pBuf" is a valid pointer to a structure of type DEVMODE. I know this for 2 reasons.

  1. This structure is a subset of information returned from a printer driver call named FindNextPrinterChangeNotification(). I use this to capture print job information, and when I capture a print job and use the above code to marshal out the DEVMODE object, the "dmCopies" field is always exactly correct with the number of copies that were printed in that job. So its a bit strange how the 12th member in a structure is getting marshaled correctly when some of the ones before it appear to not be.

  2. I used Marshal.ReadByte() to forcibly read the first 100 bytes that aData[i].NotifyData.Data.pBuf was pointing to, and sure enough, the first collection of bytes were the full exact name of the printer device. So I know the information is there.

For whatever reason, when I use Marshal.PtrToStructure() it doesn't seem to be able to correctly fill the character arrays. I'm pretty sure most of the other variables are correct, but I have my doubts because of the array issue. Does anyone know whats going on here.

--EDIT -- As requested, here is the code that populates the aData[] array:

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
    //////////////////
}

解决方案

Use a string:

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string dmDeviceName;

See: http://pinvoke.net/default.aspx/Structures/DEVMODE.html

EDIT: I didn't notice when posting my quick response since I was focusing on the strings (and there is one at the very beginning of the struct, meaning that the rest of the struct elements can't affect things), but as others have pointed out you have a big problem with the unions in the DEVMODE structure. Elements in a union do not lie sequentially but rather occupy the same space in memory: only one of the union elements may be used at a time. For example:

union {
    DWORD dmDisplayFlags;
    DWORD dmNup;
};

means that dmDisplayFlags and dmNup are essentially different names for the same block of memory. I.e. dmDisplayFlags and dmNup are both stored at an offset of 116 bytes from the beginning of the structure. Changing dmNup causes the value of dmDisplayFlags to also change, and vice versa. Using C# code like:

public Int32 dmDisplayFlags;
public Int32 dmNup;

means that they get stored sequentially, i.e. at offsets 116 and 120. That upsets the layout of the entire structure. To fix that, you need to use an explicit layout and manually define the field offsets. Look at the link I previously gave at pinvoke.net for an example of how to do that on this particular structure. Notice how dmDisplayFlags and dmNup have the same field offset. Since C# doesn't natively support unions, this is a somewhat-clumsy way to handle unions for those special interop scenarios like this one that require it.

I'd suggest fixing your union issues, and then use strings with ByValTStr as originally suggested (in summary, use what is on pinvoke.net). See if it gets you better results.

Others suggest that it is a Unicode issue, but I don't think that's what it is. The documentation says FindNextPrinterChangeNotification is not available in Unicode. If you examined the structure at byte-level and say it's not Unicode - I definitely believe you.

From the docs:

"ByValTStr: Used for in-line, fixed-length character arrays that appear within a structure. The character type used with ByValTStr is determined by the System.Runtime.InteropServices.CharSet argument of the System.Runtime.InteropServices.StructLayoutAttribute applied to the containing structure. Always use the MarshalAsAttribute.SizeConst field to indicate the size of the array.

.NET Framework ByValTStr types behave like C-style, fixed-size strings inside a structure (for example, char s[5]). The behavior in managed code differs from the Microsoft Visual Basic 6.0 behavior, which is not null terminated (for example, MyString As String * 5)."

Seems pretty clear to me that this should be the generally-accepted "correct" way of doing this.

这篇关于在结构上DEVMODE)麻烦Marshal.PtrToStructure(和字符数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆