Если структура маршализуема без пользовательской обработки, я очень предпочитаю последний подход, где вы объявляете функцию 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
<pedantry>
Кстати, в вашем примере, передавая указатель как IntPtr
, вы использовали неправильный Alloc
. SendMessage
не является функцией COM, поэтому не следует использовать распределитель COM. Используйте Marshal.AllocHGlobal
и Marshal.FreeHGlobal
. Они плохо названы; имена имеют смысл, только если вы занимались программированием Windows API, а может быть, даже тогда. AllocHGlobal
вызывает GlobalAlloc
в kernel32.dll, что возвращает HGLOBAL
. Этот использовал , чтобы отличаться от HLOCAL
, возвращаемого LocalAlloc
в 16-битные дни, но в 32-битных Windows они одинаковы.
Использование термина HGLOBAL
для обозначения блока (нативной) пользовательской памяти просто застряло, я полагаю, и люди, разрабатывающие класс Marshal
, не должны были подумать о как это не интуитивно понятно для большинства разработчиков .NET. С другой стороны, большинству разработчиков .NET не нужно выделять неуправляемую память, поэтому ....
</pedantry>
Редактировать
Вы упоминаете, что получаете исключение TypeLoadException при использовании класса вместо структуры, и запрашиваете образец. Я сделал быстрый тест, используя CHARFORMAT2
, так как похоже, что вы пытаетесь его использовать.
Первая азбука 1 :
[StructLayout(LayoutKind.Sequential)]
abstract class NativeStruct{} // simple enough
Требуется атрибут StructLayout
, или вы получите исключение TypeLoadException.
Теперь CHARFORMAT2
класс:
[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
s. Я опять упущен для (относительной) краткости.
Я проверяю успех с:
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 на самом деле не маршалируются. Они просто закреплены в памяти - это удерживает GC от их перемещения - и адрес их расположения в управляемой памяти передается собственной функции.
Неблизкие типы должны маршалироваться. CLR выделяет неуправляемую память и копирует данные между управляемым объектом и его неуправляемым представлением, выполняя необходимые преобразования между форматами в процессе работы.
Структура CHARFORMAT2
не является близаемой из-за члена string
. CLR не может просто передать указатель на объект .NET string
, где ожидается массив символов фиксированной длины. Таким образом, структура 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 th и часть 53 rd выступают за видимое поле, отображаемое на линейке.
Предполагается, что у вас есть настройки со стандартным 96 DPI (обычные шрифты). Если вы используете 120 DPI (большие шрифты), вы не увидите этой проблемы, хотя, по-видимому, в этом случае вы неправильно определяете размер элемента управления. Вы также вряд ли увидите это на распечатанной странице.
Что здесь происходит? Проблема в том, что 0,1 дюйма (ширина одного символа) составляет 9,6 пикселей (опять же, используя 96 DPI). GDI не разделяет символы, используя числа с плавающей точкой, поэтому округляет до 10 пикселей. Таким образом, 55 символов занимают 55 * 10 = 550 пикселей / 96 точек на дюйм = 5,7291666 ... дюймов, тогда как мы ожидали 5,5 дюймов.
Хотя это, вероятно, будет менее заметно в обычном случае использования программы текстового процессора, есть вероятность случаев, когда перенос слов происходит в разных местах экрана по сравнению с страницей, или что вещи не совпадают однажды напечатанный, как они сделали на экране. Это может оказаться проблемой для вас, если вы работаете с коммерческим приложением.
К сожалению, решить эту проблему нелегко. Это означает, что вам придется отказаться от расширенного элемента управления текстовым полем, что означает огромные хлопоты по реализации всего, что он для вас делает, что довольно много. Это также означает, что код рисования текста, который вам придется реализовать, становится довольно сложным. У меня есть код, который делает это, но он слишком сложен, чтобы публиковать здесь. Однако вы можете найти этот пример или этот полезным.
Удачи!
1 Абстрактный базовый класс