Вопрос по interop, .net, intptr, winapi, c# – .NET Interop IntPtr против реф

9

Вероятно, вопрос нуб, но взаимодействие неЭто одна из моих сильных сторон.

Помимо ограничения количества перегрузок, есть ли причина, по которой я должен объявить свои DllImports как:

[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

И используйте их так:

IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatrange));
Marshal.StructureToPtr(formatrange, lParam, false);

int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, lParam);

Marshal.FreeCoTaskMem(lParam);

Вместо создания целевой перегрузки:

[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref FORMATRANGE lParam);

И используя это как:

FORMATRANGE lParam = new FORMATRANGE();
int returnValue = User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_FORMATRANGE, wParam, ref lParam);

Перегрузка с помощью ref становится проще в использовании, но ямне интересно, есть ли недостаток, что яЯ не в курсе.

Редактировать:

Пока что много интересной информации, ребята.

@P Папа: У вас есть пример создания класса struct из абстрактного (или любого) класса? Я изменил свою подпись на:

[DllImport("user32.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] CHARFORMAT2 lParam);

Без ,InOut, а такжеMarshalAs SendMessage (EM_GETCHARFORMAT в моем тесте) завершается ошибкой. Приведенный выше пример хорошо работает, но если я изменю его на:

[DllImport("user32.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeStruct lParam);

Я получаю исключение System.TypeLoadException, в котором говорится, что формат CHARFORMAT2 недопустим (япопробую и запечатлю здесь).

Исключение:

Не удалось загрузить типCC.Utilities.WindowsApi.CHARFORMAT2' из собранияCC.Utilities, версия = 1.0.9.1212, культура = нейтральная, PublicKeyToken = 111aac7a42f7965e ' потому что формат недействителен.

Класс NativeStruct: I '

public class NativeStruct
{
}

мы пыталисьabstract, добавивStructLayout атрибут и т. д. и я получаю то же исключение.

[StructLayout(LayoutKind.Sequential)]
public class CHARFORMAT2: NativeStruct
{
    ...
}

Редактировать:

Я не'Следуйте часто задаваемым вопросам, и я задал вопрос, который можно обсудить, но на который нет положительного ответа. Помимо этого было многос проницательной информацией в этой теме. Так что я'Я оставлю это до читателей, чтобы проголосовать за ответ. Первым от 10 до 10 голосов будет ответ. Если в течение двух дней (12/17 по стандартному тихоокеанскому времени) это не отвечаетЯ добавлю свой собственный ответ, который обобщает все вкусные знания в теме :-)

Изменить снова:

Я солгал, принимая P Daddy 'ответ, потому что он человек, и он мне очень помог (у него тоже есть милая маленькая обезьянка :-P)

Класс NativeStruct нуждается вStructLayout атрибут тоже. Я'Выложу свой рабочий пример кода. P Daddy

Ваш Ответ

5   ответов
15

я предпочитаю последний подход, где вы объявляете функцию p / invoke как принимающуюref (указатель на) ваш тип. Кроме того, вы можете объявить ваши типы как классы вместо структур, а затем вы можете передатьnull, также.

[StructLayout(LayoutKind.Sequential)]
struct NativeType{
    ...
}

[DllImport("...")]
static extern bool NativeFunction(ref NativeType foo);

// can't pass null to NativeFunction
// unless you also include an overload that takes IntPtr

[DllImport("...")]
static extern bool NativeFunction(IntPtr foo);

// but declaring NativeType as a class works, too

[StructLayout(LayoutKind.Sequential)]
class NativeType2{
    ...
}

[DllImport("...")]
static extern bool NativeFunction(NativeType2 foo);

// and now you can pass null

Кстати, в вашем примере передача указателя в видеIntPtr, вы'мы использовали неправильно.AllocSendMessage это не функция COM, поэтому вы не должныиспользовать распределитель COM. использованиеMarshal.AllocHGlobal а такжеMarshal.FreeHGlobal, Oни'плохо назван; имена имеют смысл только если вымы занимались программированием Windows API, а может быть, даже тогда.AllocHGlobal звонкиGlobalAlloc в kernel32.dll, которая возвращаетHGLOBAL, этоиспользуемый отличаться отHLOCAL, возвращенныйLocalAlloc назад в 16-битные дни, но в 32-битных Windows они одинаковы.

Использование терминаHGLOBAL Я полагаю, что ссылка на блок (нативной) пользовательской памяти просто застряла, и люди, разрабатывающиеMarshal класс, должно быть, не нашел время подумать о том, насколько неинтуитивно это было бы для большинства разработчиков .NET. С другой стороны, большинство разработчиков .NET неНе нужно выделять неуправляемую память, так что ....

редактировать

Вы упоминаетеполучить исключение TypeLoadException при использовании класса вместо структуры и запросить образец. Я сделал быстрый тест, используяCHARFORMAT2, так как это выглядит такчто тыпытаюсь использовать.

Сначала ABC1:

[StructLayout(LayoutKind.Sequential)]
abstract class NativeStruct{} // simple enough

StructLayout атрибут обязателен, или выбудут получить исключение TypeLoadException.

ТеперьCHARFORMAT2 Класс: I»

[StructLayout(LayoutKind.Sequential, Pack=4, CharSet=CharSet.Auto)]
class CHARFORMAT2 : NativeStruct{
    public DWORD    cbSize = (DWORD)Marshal.SizeOf(typeof(CHARFORMAT2));
    public CFM      dwMask;
    public CFE      dwEffects;
    public int      yHeight;
    public int      yOffset;
    public COLORREF crTextColor;
    public byte     bCharSet;
    public byte     bPitchAndFamily;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
    public string   szFaceName;
    public WORD     wWeight;
    public short    sSpacing;
    public COLORREF crBackColor;
    public LCID     lcid;
    public DWORD    dwReserved;
    public short    sStyle;
    public WORD     wKerning;
    public byte     bUnderlineType;
    public byte     bAnimation;
    public byte     bRevAuthor;
    public byte     bReserved1;
}

мы использовалиusing заявления к псевдонимуSystem.UInt32 как ,DWORDLCID, а такжеCOLORREFи псевдонимSystem.UInt16 какWORD, Я стараюсь, чтобы мои определения P / Invoke соответствовали спецификации SDK, насколько это возможно.CFM а такжеCFE являютсяenums которые содержат значения флага для этих полей. Я'мы оставили их определения для краткости, но можем добавить их в случае необходимости.

мы объявилиSendMessage как:

[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern IntPtr SendMessage(
    HWND hWnd, MSG msg, WPARAM wParam, [In, Out] NativeStruct lParam);

HWND это псевдоним для,System.IntPtrMSG являетсяSystem.UInt32, а такжеWPARAM является .System.UIntPtr

[In, Out] атрибут наlParam требуется, чтобы это работало, в противном случае это неПохоже, что маршалинг в обоих направлениях (до и после вызова нативного кода).

Я называю это с:

CHARFORMAT2 cf = new CHARFORMAT2();
SendMessage(rtfControl.Handle, (MSG)EM.GETCHARFORMAT, (WPARAM)SCF.DEFAULT, cf);

EM а такжеSCF являютсяenumяМы, опять же, опущены для (относительной) краткости.

Я проверяю успех с:

Console.WriteLine(cf.szFaceName);

и я получаю:

Microsoft Sans Serif

Работает как шарм!

Гм или нет, в зависимости от того, сколько ты спишьу тебя было и сколько вещей тыя пытаюсь сделать это сразу, я полагаю.

этобыло бы работать, еслиCHARFORMAT2 былиblittable тип. (Blittable тип - это тип, который имеет такое же представление в управляемой памяти, как и в неуправляемой памяти.) Например,MINMAXINFO типделает работать как описано.

[StructLayout(LayoutKind.Sequential)]
class MINMAXINFO : NativeStruct{
    public Point ptReserved;
    public Point ptMaxSize;
    public Point ptMaxPosition;
    public Point ptMinTrackSize;
    public Point ptMaxTrackSize;
}

Это потому, что типы blittable на самом деле не маршалируются. Oни'просто прикололся в памятьэто удерживает GC от их перемещения -и адрес их расположения в управляемой памяти передается собственной функции.

Неблизкие типы должны маршалироваться. CLR выделяет неуправляемую память и копирует данные между управляемым объектом и его неуправляемым представлением, выполняя необходимые преобразования между форматами в процессе работы.

CHARFORMAT2 структура не является молниеносной из-заstring член. CLR может 'просто передать указатель на .NETstring объект, в котором ожидается массив символов фиксированной длины. ИтакCHARFORMAT2 структура должна быть упорядочена.

Как представляется, для правильного маршалинга функция взаимодействия должна быть объявлена с маршалируемым типом. Другими словами, учитывая приведенное выше определение, CLR должен делать какое-то определение на основе статического типаNativeStruct, Я предполагаю, что этоправильно определить, что объект нужно маршалировать, но только тогдасортировочный» объект нулевого байта, размерNativeStruct сам.

Таким образом, чтобы ваш код работал наCHARFORMAT2 (и любые другие неблизкие типы, которые вы можете использовать), вы 'придется вернуться к объявлениюSendMessage как принимаяCHARFORMAT2 объект. Извините, что сбил вас с пути на этом.

Капча для предыдущего редактирования:

Уиппет

Да, хорошо

Cory,

Это не по теме, но я заметил потенциальную проблему для вас в приложении.делает заново.

Элемент управления rich textbox использует стандартные функции измерения текста и рисования текста GDI. Почему это проблема? Потому что, несмотря на заявления о том, что шрифт TrueType на экране выглядит так же, как и на бумаге, GDI точно не размещает символы. Проблема в округлении.

GDI использует целочисленные процедуры для измерения текста и размещения символов. Ширина каждого символа (и высота каждой строки, в этом отношении) округляется до ближайшего целого числа пикселей без исправления ошибок.

Ошибка легко увидеть в вашем тестовом приложении. Установите шрифт Courier New на 12 пунктов. Этот шрифт фиксированной ширины должен содержать символы ровно 10 на дюйм, или 0,1 дюйма на символ. Это должно означать, что при ширине стартовой строки 5,5 дюймов вы должны уместить 55 символов в первой строке до переноса.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123

Но если вы попробуете, вы 'Вы увидите, что перенос происходит только после 54 символов. Какие'больше 54го характер и часть 53й вылет за видимую границу, показанную на линейке линейки.

Это предполагает, что у вас есть настройки со стандартным 96 DPI (обычные шрифты). Если вы используете 120 DPI (большие шрифты), вы выиграетеЯ не вижу этой проблемы, хотя кажется, что в этом случае вы неправильно определяете размер элемента управления. Вы также выигралиВероятно, это можно увидеть на распечатанной странице.

Какие'здесь происходит? Проблема в том, что 0,1 дюйма (ширина одного символа) составляет 9,6 пикселей (опять же, используя 96 DPI). GDI не делаетt пробел символов с использованием чисел с плавающей запятой, так что округляется до 10 пикселей. Таким образом, 55 символов занимают 55 * 10 = 550 пикселей / 96 точек на дюйм = 5,7291666 ... дюймов, тогда как мы ожидали 5,5 дюймов.

Хотя это, вероятно, будет менее заметно в обычном случае использования программы для текстового процессора, существует вероятность случаев, когда перенос слов происходит в разных местах экрана, а не на странице, или чтоПосле печати выведите в линию то же, что и на экране. Это может стать проблемой для вас, если это коммерческое приложение, которое выработает над

К сожалению, решить эту проблему нелегко. Это означает, что выПридется обойтись без элемента управления rich textbox, что означает огромные хлопоты по реализации всего, что он для вас делает, что довольно много. Это также означает, что код рисования текста выПридется реализовать довольно сложно. Я'у нас есть код, который делает это, но этослишком сложно, чтобы оставлять здесь сообщения. Вы можете, однако, найтиэтот пример или жеэтот полезно.

Удачи 1

 Абстрактный базовый класс

Если вы можете помочь и у вас возникли проблемы со мной, вы можете использовать ccharlton_AT_pobox_DOT_com Cory Charlton
Я допустил ошибку. Я устал и пытался сделать слишком много вещей прошлой ночью. Смотрите отредактированный пост. P Daddy
+2 (если это возможно, лол) Ты рок P папочка. ПропажаStructLayout атрибут моего базового класса вызвал исключение TypeLoadException, но тогда проблема заключалась в том, что я использовал[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32] заszFaceName что привело к ошибке сообщения. Изменение наUnmanagedType.ByValTStr (которыйМНОГО более удобный для пользователя) решена ошибка сообщения. Еще раз благодарю вас и спасибо за все замечательные советы! Cory Charlton
хорошая точка в педантичности dkackman
2

вы не можете перегрузить SendMessage и сделать аргумент wparam целым. Это приведет к сбою вашей программы в 64-битной версии операционной системы. Это должен быть указатель, либо IntPtr, легкая ссылка или тип значения out или ref. В противном случае допустима перегрузка типа out / ref.

РЕДАКТИРОВАТЬ: Как указала ОП, это на самом деле не проблема. Соглашение о вызове 64-битной функции передает первые 4 аргумента через регистры, а не через стек. Таким образом, нет опасности неправильного выравнивания стека для аргументов wparam и lparam.

Используется ли он как указатель?Не имеет значения, функция ожидает 8 байтов для передачи в x64. Вы'Я пропущу 4, неправильно совмещая другие аргументы. Hans Passant
У вас есть примеры, где wParam указывается как указатель? Да яЯ только начинаю копаться в этом, но большинство явидел убежищене было указателей на wParam, какmsdn.microsoft.com/en-us/library/bb788026(VS.85).aspx например. Или, может быть, они должны быть указателями, но описание нене могу указать это так, как для lParam. Cory Charlton
Мой комментарий длился долго. В любом случае, когда я читаю ваши комментарии, это звучит как .NETRichTextBox (среди многих других, контроль ясмотрю с отражателем) не должент работает в 64-битной ОС, и я знаю, чтодело не в этом. Cory Charlton
Ах, я знаю почему. Соглашение о вызовах x64 передает первые 4 аргумента в регистры, а не в стек. Таким образом, для wparam нет неправильного выравнивания. Вы'все в порядке с использованием Int. Хитрый Hans Passant
3

ref Guid parent и соответствующая документация гласит:

Указатель на GUID с указанием родителя. Передайте нулевой указатель для использования [вставить какой-то системный элемент]. "

Еслиnull (или жеIntPtr.Zero заIntPtr параметры) действительно неверный параметр, то выхорошо, используяref параметр - может быть, даже лучше, так какОчень ясно, что именно вам нужно передать.

Еслиnull является допустимым параметром, вы можете передатьClassType вместоref StructType, Объекты ссылочного типа (class) передаются как указатель, и они позволяют.null

Хороший вопрос ... +1 Thomas Levesque
1

ref Проще и менее подвержен ошибкам, чем манипулирование указателями вручную, поэтому я не вижу веских причин не использовать его ... Еще одно преимущество использованияref это ты нене нужно беспокоиться об освобождении неуправляемой выделенной памяти

1

Для простого типа и простой структуры достаточно часто использовать by-ref.

IntPtr следует отдавать предпочтение, если структура имеет переменный размер или если вы хотите выполнить пользовательскую обработку.

Похожие вопросы