c# p/invoke трудности с упорядочиванием указателей

Я пытаюсь вызвать родную .dll из С#, используя p/invoke. Я могу сделать вызов (без сбоев, функция возвращает значение), но код возврата указывает: «Параметр указателя не указывает на доступную память». Я прибегал к пробам и ошибкам, чтобы решить эту проблему, но пока не добился никакого прогресса.

Вот подпись нативной функции, которую я вызываю:

LONG extern WINAPI MyFunction ( LPSTR lpszLogicalName, //input
                                   HANDLE hApp,           //input  
                                   LPSTR lpszAppID,       //input 
                                   DWORD dwTraceLevel,    //input
                                   DWORD dwTimeOut,       //input
                                   DWORD dwSrvcVersionsRequired, //input
                                   LPWFSVERSION lpSrvcVersion, //WFSVERSION*, output
                                   LPWFSVERSION lpSPIVersion,  //WFSVERSION*, output
                                   LPHSERVICE lphService       //unsigned short*, output
                                 );

Вот импортированная подпись на С#:

 [DllImport("my.dll")]
 public static extern int MyFunction( [MarshalAs(UnmanagedType.LPStr)] 
                                      string logicalName, 
                                      IntPtr hApp, 
                                      [MarshalAs(UnmanagedType.LPStr)] 
                                      string appID, 
                                      int traceLevel, 
                                      int timeout, 
                                      int srvcVersionsRequired, 
                                      [Out] WFSVersion srvcVersion, 
                                      [Out] WFSVersion spiVersion,
                                      [Out] UInt16 hService
                                    );

Вот определение C для WFSVERSION:

typedef struct _wfsversion
{
    WORD            wVersion;
    WORD            wLowVersion;
    WORD            wHighVersion;
    CHAR            szDescription[257];
    CHAR            szSystemStatus[257];
} WFSVERSION, * LPWFSVERSION;

Вот определение C# WFSVersion:

[StructLayout(LayoutKind.Sequential)]
public class WFSVersion
{
    public Int16 wVersion;
    public Int16 wLowVersion;
    public Int16 wHighVersion;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 257)]
    public char[] szDescription;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 257)]
    public char[] szSystemStatus;
}

Вот вызов MyFunction из C#:

WFSVersion srvcVersionInfo = new WFSVersion();
WFSVersion spiVersionInfo = new WFSVersion();


 UInt16 hService = 0;
 IntPtr hApp = IntPtr.Zero;
 string logicalServiceName = tbServiceName.Text;
 int openResult = MyFunction(logicalServiceName, hApp, null, 0,  
                              XFSConstants.WFS_INDEFINITE_WAIT,
                              0x00000004, srvcVersionInfo, spiVersionInfo, 
                              hService);

Как я уже сказал, этот вызов возвращает значение, но возвращаемое значение представляет собой код ошибки, указывающий на «Параметр-указатель не указывает на доступную память». Должно быть, я делаю что-то не так с параметрами 1, 3, 7, 8 или 9. Однако я успешно вызывал другие функции в этой .dll, которые требовали WFSVERSION* в качестве параметров, поэтому я не думаю, что параметры 7 или 8 являются проблемой здесь.

Я был бы признателен за любые ваши мысли о причине этой проблемы или за любую конструктивную критику моего кода. Это мой первый опыт работы с P/Invoke, поэтому я не знаю, с чего начать. Есть ли способ сузить проблему, или пробная ошибка - мой единственный вариант?


person Odrade    schedule 02.06.2009    source источник
comment
pinvoke.net может оказаться полезным   -  person kenny    schedule 02.06.2009
comment
У вас есть нулевой указатель в третьем аргументе - это правильный код?   -  person Mike    schedule 02.06.2009
comment
Да, нулевой указатель допустим в качестве третьего аргумента.   -  person Odrade    schedule 02.06.2009


Ответы (6)


У вас тут две очевидные ошибки. В определении структуры вы должны использовать byte[] вместо char[] для szDescription и szSystemStatus.

Также последний параметр в вашем вызове pInvoke не является указателем. Когда вы делаете вызов MyFunction, hService равен нулю и, следовательно, является недопустимым указателем в отношении функции. [Out] — это директива маршалинга, сообщающая среде выполнения, когда и куда копировать данные, а не индикатор того, что параметр является указателем. Что вам нужно, так это изменить [Out] на out или ref, это сообщит среде выполнения, что hService является указателем:

[DllImport("my.dll")]
public static extern int MyFunction( [MarshalAs(UnmanagedType.LPStr)] 
                                  string logicalName, 
                                  IntPtr hApp, 
                                  [MarshalAs(UnmanagedType.LPStr)] 
                                  string appID, 
                                  int traceLevel, 
                                  int timeout, 
                                  int srvcVersionsRequired, 
                                  [Out] WFSVersion srvcVersion, 
                                  [Out] WFSVersion spiVersion,
                                  out UInt16 hService);
person Stephen Martin    schedule 03.06.2009
comment
Спасибо, это была проблема. Однако, узнав об этом, мне интересно, неправильно ли маршалируются параметры [Out] WFSVersion. - person Odrade; 03.06.2009
comment
Теперь я понял: WFSVersion — это ссылочный тип, а не тип значения. Атрибут [Out] даже не нужен для этих параметров; это просто немного увеличивает эффективность звонков. - person Odrade; 03.06.2009

Некоторые идеи:

  • Класс C# WFSVersion, вероятно, должен быть классом struct. Я не знаю, волнует ли маршаллер P/Invoke, но я всегда видел, как используются структуры.

  • Размер символов может быть проблемой.

    CHAR в C имеет ширину 8 бит (ANSI), а System.Char в .Net — 16 бит (Unicode). Чтобы предоставить маршаллеру как можно больше информации для правильного преобразования, попробуйте добавить «CharSet = CharSet.Ansi» к атрибутам DllImport и StructLayout и изменить объявления строк в WFSVersion:

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string szDescription;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string szSystemStatus;
    
  • Другой проблемой может быть выравнивание данных в структурах. Если выравнивание не было указано при компиляции структуры C, элементы данных в структуре, вероятно, были выровнены по границе одного или двух байтов. Попробуйте использовать Pack в атрибуте StructLayout WFSVersion:

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    // Try pack values of 2, 4 and 8 if 1 doesn't work.
    

И несколько вопросов:

  • Была ли MyFunction предназначена для вызова из кода, отличного от C? Первоначальный автор мог написать код, который предполагает, что передаваемые данные выделяются диспетчером памяти среды выполнения C.

  • Использует ли код в C DLL переданные ему указатели для последующей обработки после возврата MyFunction? Если это так - и если предположить, что в такой ситуации возможно/разумно/разумно двигаться вперед, может потребоваться "закрепить" структуры, переданные в MyFunction, с помощью ключевого слова fixed. Плюс, вероятно, есть проблемы с безопасностью, с которыми нужно иметь дело.

person Chris R. Timmons    schedule 03.06.2009
comment
Спасибо за информацию об обработке строк C. Хотя это не было источником проблемы, о которой я сообщил, у меня были некоторые проблемы с этим, которые я еще не заметил. - person Odrade; 03.06.2009

Я не уверен точно, в чем проблема с этим, но, надеюсь, это будет отправной точкой.

Попробуйте просмотреть параметры атрибута DllImport, это может быть связано с упорядочиванием строк.

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]

Я думаю, вам может понадобиться опция CharSet.

person Peter    schedule 02.06.2009
comment
Я пробовал CharSet.Auto, CharSet.Ansi и Charset.Unicode, но проблема осталась. - person Odrade; 02.06.2009
comment
Есть ли какая-либо дополнительная документация на веб-сайте PInvoke для dll, которую вы используете, или это пользовательская dll? pinvoke.net - person Peter; 03.06.2009

Я нашел AppVerifier полезным для отслеживания P /Вызывать маршалы раньше. Это бесплатно и от Microsoft.

person Maurice Flanagan    schedule 02.06.2009

Я предполагаю, что один из ваших указателей (lpSrvcVersion, lpSPIVersion или lphService) недоступен из вашего приложения .NET. Можете ли вы попробовать изменить DLL (если она ваша) и посмотреть, сможете ли вы заставить ее работать без указателей? (Я знаю, что вам придется добавить их позже, но, по крайней мере, вы можете сузить местонахождение проблемы.)

person Jon Tackabury    schedule 02.06.2009
comment
К сожалению, у меня нет возможности модифицировать dll. - person Odrade; 03.06.2009

Вы уверены, что это не hApp? Это похоже на входной параметр, дескриптор запрашивающего приложения. Быстро погуглите... да, есть функция для создания дескриптора приложения и параметр по умолчанию, который вы можете использовать.

person R Ubben    schedule 02.06.2009