.NET Interop IntPtr против касательно

Вероятно, вопрос о новичке, но interop еще не одна из моих сильных сторон.

Кроме ограничения количества перегрузок там любая причина, как которая я должен объявить свой 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);

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

Править:

Много большой информации до сих пор парни.

Папа @P: у Вас есть пример базирования класса структуры от резюме (или кто-либо) класс? Я изменил свою подпись на:

[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);

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

Исключение:

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

Класс NativeStruct:

public class NativeStruct
{
}

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

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

Править:

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

Отредактируйте снова:

Я лгал, принимая P ответ Папы, потому что он - человек и был большой помощью (у него есть милая небольшая обезьяна также :-P)

10
задан Cory Charlton 16 December 2009 в 05:58
поделиться

5 ответов

Если структура является маршалируемой без специальной обработки, я очень предпочитаю второй подход, когда вы объявляете функцию 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 . Они плохо названы; имена имеют смысл только в том случае, если вы занимались программированием Windows API, а может и не тогда. AllocHGlobal вызывает GlobalAlloc в kernel32.dll, который возвращает HGLOBAL . Этот использовал , чтобы отличаться от HLOCAL , возвращенного LocalAlloc еще в 16-битные дни, но в 32-битной Windows они такие же.

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


Edit

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

Сначала ABC 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;
}

Я использовал , используя операторы для псевдоним System.UInt32 как DWORD , LCID и COLORREF , а псевдоним System.UInt16 как ] СЛОВО . Я стараюсь, чтобы мои определения P / Invoke соответствовали спецификации SDK, насколько это возможно. CFM и CFE - это перечисления , которые содержат значения флагов для этих полей. Я оставил их определения для краткости, но могу добавить их, если необходимо.

I '

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

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

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

EM и SCF are enum s Я, опять же, опущен для (относительной) краткости.

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

Console.WriteLine(cf.szFaceName);

и получаю:

Microsoft Sans Serif

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


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

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

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

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

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

Структура CHARFORMAT2 не является непреобразуемой из-за строка член. CLR не может просто передать указатель на объект .NET string , где ожидается символьный массив фиксированной длины. Таким образом, структура CHARFORMAT2 должна быть маршалирована.

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

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


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

the whippet

Да, взбейте его хорошо!


Кори,

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

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

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

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

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123

Но если вы попытаетесь, вы увидите, что перенос выполняется только после 54 символов. Более того, 54 символ и часть 53 -го выступают над видимым полем, показанным на линейке.

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

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

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

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

Удачи!


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

15
ответ дан 3 December 2019 в 17:59
поделиться

No, you cannot overload SendMessage and make the wparam argument an int. That will make your program fail on a 64-bit version of the operating system. It has to be a pointer, either IntPtr, a blittable reference or an out or ref value type. Overloading the out/ref type is otherwise fine.


EDIT: As the OP pointed out, this is not actually a problem. The 64-bit function calling convention passes the first 4 arguments through registers, not the stack. There is thus no danger of stack mis-alignment for the wparam and lparam arguments.

2
ответ дан 3 December 2019 в 17:59
поделиться

У меня было несколько забавных случаев, когда параметр был чем-то вроде ref Guid parent и соответствующая документация гласит:

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

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

.
3
ответ дан 3 December 2019 в 17:59
поделиться

Я не вижу никаких недостатков.

By-ref достаточно часто встречается для простого типа и простой структуры.

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

.
1
ответ дан 3 December 2019 в 17:59
поделиться

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

.
1
ответ дан 3 December 2019 в 17:59
поделиться
Другие вопросы по тегам:

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