Трабл с Marshal.PtrToStructure () и массивами символов в структуре DEVMODE - PullRequest
4 голосов
/ 09 сентября 2011

У меня проблема с использованием Marshal.PtrToStructure () для извлечения данных из указателя на структуру типа DEVMODE. Здесь - это ссылка на запись MSDN в структуре DEVMODE.

Моя реализация 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 () для принудительного чтения первых 100 байтов, на которые указывал aData [i] .NotifyData.Data.pBuf, и, конечно же, первая коллекция байтов представляла собой полное точное имя устройство принтера. Так что я знаю, что информация там.

По какой-то причине, когда я использую Marshal.PtrToStructure (), кажется, что он не может правильно заполнить массивы символов. Я уверен, что большинство других переменных верны, но у меня есть сомнения из-за проблемы с массивом. Кто-нибудь знает, что здесь происходит.

- РЕДАКТИРОВАТЬ - В соответствии с запросом, вот код, который заполняет массив 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
    //////////////////
}

1 Ответ

7 голосов
/ 09 сентября 2011

Используйте строку:

[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). Посмотрите, принесет ли он вам лучшие результаты.

Другие предполагают, что это проблема Юникода, но я не думаю, что это так. В документации говорится, что FindNextPrinterChangeNotification недоступно в Юникоде. Если вы исследовали структуру на уровне байтов и сказали, что это не Unicode - я вам определенно верю.

Из документов:

" ByValTStr : Используется для встроенных символьных массивов фиксированной длины, которые появляются внутри структуры. Тип символов, используемый с ByValTStr, определяется аргументом System.Runtime.InteropServices.CharSet системы. .Runtime.InteropServices.StructLayoutAttribute, примененный к содержащей структуре. Всегда используйте поле MarshalAsAttribute.SizeConst, чтобы указать размер массива.

.NET Framework Типы ByValTStr ведут себя как строки C-стиля, фиксированного размера внутри структуры (например, char s [5]). Поведение в управляемом коде отличается от поведения в Microsoft Visual Basic 6.0, которое не завершается нулем (например, MyString As String * 5). "

Мне кажется довольно ясным, что это должен быть общепринятый "правильный" способ сделать это.

...