Вопрос по interop, .net, intptr, winapi, c# – .NET Interop IntPtr против реф
Вероятно, вопрос нуб, но взаимодействие неЭто одна из моих сильных сторон.
Помимо ограничения количества перегрузок, есть ли причина, по которой я должен объявить свои 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);
Без ,In
Out
, а также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)
StructLayout
атрибут тоже. Я'Выложу свой рабочий пример кода.
я предпочитаю последний подход, где вы объявляете функцию 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
, вы'мы использовали неправильно.Alloc
SendMessage
это не функция 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
как ,DWORD
LCID
, а также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.IntPtr
MSG
является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
Абстрактный базовый класс
StructLayout
атрибут моего базового класса вызвал исключение TypeLoadException, но тогда проблема заключалась в том, что я использовал[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32]
заszFaceName
что привело к ошибке сообщения. Изменение наUnmanagedType.ByValTStr
(которыйМНОГО более удобный для пользователя) решена ошибка сообщения. Еще раз благодарю вас и спасибо за все замечательные советы!
вы не можете перегрузить SendMessage и сделать аргумент wparam целым. Это приведет к сбою вашей программы в 64-битной версии операционной системы. Это должен быть указатель, либо IntPtr, легкая ссылка или тип значения out или ref. В противном случае допустима перегрузка типа out / ref.
РЕДАКТИРОВАТЬ: Как указала ОП, это на самом деле не проблема. Соглашение о вызове 64-битной функции передает первые 4 аргумента через регистры, а не через стек. Таким образом, нет опасности неправильного выравнивания стека для аргументов wparam и lparam.
RichTextBox
(среди многих других, контроль ясмотрю с отражателем) не должент работает в 64-битной ОС, и я знаю, чтодело не в этом.
ref Guid parent
и соответствующая документация гласит:
Указатель на GUID с указанием родителя. Передайте нулевой указатель для использования [вставить какой-то системный элемент]. "
Еслиnull
(или жеIntPtr.Zero
заIntPtr
параметры) действительно неверный параметр, то выхорошо, используяref
параметр - может быть, даже лучше, так какОчень ясно, что именно вам нужно передать.
Еслиnull
является допустимым параметром, вы можете передатьClassType
вместоref StructType
, Объекты ссылочного типа (class
) передаются как указатель, и они позволяют.null