簡體   English   中英

Marshal.PtrToStructure()和結構DEVMODE中的char數組的問題

[英]Trouble with Marshal.PtrToStructure() and char arrays in structure DEVMODE

我在使用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;
}

在這種結構中有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類型結構的有效指針。 我知道這有兩個原因。

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

使用字符串:

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

請參閱: http//pinvoke.net/default.aspx/Structures/DEVMODE.html

編輯:我沒有注意到發布我的快速響應,因為我專注於字符串(並且在結構的最開始有一個,這意味着其余的結構元素不會影響事物),但作為其他人已經指出你在DEVMODE結構中的工會有一個大問題。 聯合中的元素不是按順序排列,而是占據內存中的相同空間:一次只能使用一個聯合元素。 例如:

union {
    DWORD dmDisplayFlags;
    DWORD dmNup;
};

意味着dmDisplayFlags和dmNup對於同一塊內存本質上是不同的名稱。 即dmDisplayFlags和dmNup都存儲在距結構開頭116字節的偏移處。 更改dmNup會導致dmDisplayFlags的值也發生變化,反之亦然。 使用C#代碼:

public Int32 dmDisplayFlags;
public Int32 dmNup;

意味着它們被順序存儲,即在偏移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)。“

我似乎很清楚,這應該是普遍接受的“正確”方式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM