lewisv Ответов: 1

Программа взрывается после вызова неуправляемого обратного вызова


У меня есть библиотека dll c, которую мне нужно вызвать из моего приложения c#.
У меня есть он, связывающийся с dll, но у меня возникли проблемы с обратными вызовами.

В C выглядит так.

typedef void (CDECLCALL_CONV *ConnectedCB) (DPHandle hdl);

typedef struct
{
unsigned int size;	// sizeof(DPcbs)
ConnectedCB connectedCB;
.. more callbacks
} DPcbs;

BPS_DP_API unsigned long CDECLCALL_CONV DPSetCallBacks(DPHandle hdl, DPcbs *DPcbs);


Что я уже пробовал:

Я создал класс интерфейса, который обертывает неуправляемый код.
Это выглядит так

  [System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(System.Runtime.InteropServices.CallingConvention.Cdecl)]
  public delegate void ConnectedCB(ref DocProcHandle hdl);

  [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct DPcbs
{
  public uint size;
  public ConnectedCB connectedCB;
... more callbacks
}
[System.Runtime.InteropServices.DllImportAttribute("DocProc.dll", EntryPoint = "DPSetCallBacks", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
    public static extern uint DPSetCallBacks(ref DocProcHandle hdl, ref DPcbs DPcbs);


Наконец-то мой код вызова c#
{
  ...
  // this code is in a static class in the constructor
  // it should not be garbage collected right?
  m_events = new DPcbs();
  m_events.connectedCB = connected;
  m_events.size = Convert.ToUInt32(Marshal.SizeOf<DPcbs>());

  wrapper.DPSetCallBacks(ref m_handle, ref m_events);
  ...
}
private void connected(ref DocProcHandle hdl)
{
  // do something here
}


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

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

Есть ли что-то, что я упускаю или делаю неправильно?
Есть какие - нибудь предложения о том, что попробовать?

1 Ответов

Рейтинг:
9

Richard Andrew x64

Вы не можете передать делегат неуправляемому коду так, как вы пытаетесь это сделать. Если вы собираетесь передать его как указатель функции внутри структуры, вы должны вызвать:

IntPtr NativeFunctionPointer = Marshal.GetFunctionPointerForDelegate(connected);


Затем проходить NativeFunctionPointer как указатель функции.

Причина в том, что управляемый делегат состоит не только из адреса функции. Он имеет код и данные, как и многие другие классы .NET, поэтому передача его адреса не то же самое, что передача точки входа его функции.


lewisv

Это имеет смысл, так что вот на что я его изменил.

Изменил объявление в структуре на

public struct DPcbs
{
  public uint size;
  public IntPtr connectedCB;
... more callbacks
}


Настройка структуры у меня есть (я сделал элементы переменными-членами так, чтобы GC не избавлялся от них)
      m_c = new ConnectedCB(connected);
      m_p = Marshal.GetFunctionPointerForDelegate(m_c);
      m_events.connectedCB = m_p;


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

Richard Andrew x64

Я в замешательстве по вашей терминологии. Вы говорите: "он стреляет и бежит", затем вы говорите: "он взрывается", "делегат заканчивает".

Возможно, если бы вы могли быть конкретнее. Где именно находится нить, когда она "взрывается"?" Это происходит до, во время или после выполнения функции обратного вызова?

Вы получаете сообщение об ошибке о том, что стек несбалансирован?

Вы прошли через код с помощью отладчика?

lewisv

Мне жаль, если это было немного запутанно. Я запустил его через отладчик.

private void connected(ref DocProcHandle hdl)
{
  // this code works fine
}  
  <--- it crashes here after it leaves my callback method and before anything else in the c# application runs


Нет никакого сообщения об ошибке, программа просто "перестает отвечать", а затем спрашивает меня, хочу ли я отлаживать. Нет выхода из отладчика, Да, он откроет другую visual studio, но затем скажите мне, что он не может отлаживать.
Если я оставлю обратный вызов, приложение продолжит работу без каких-либо проблем. Но, конечно, мне нужно справиться с обратными вызовами.

Я предполагаю здесь, но похоже, что, устанавливая/вызывая функцию обратного вызова, я путаю память. Таким образом, приложение аварийно завершает работу вне приложения c# и внутри библиотеки dll, вызывающей делегат.
Я знаю, что dll работает, у меня есть приложение c, которое вызывает его и работает. Чтобы убедиться, что я ничего не пропустил, я в основном перестраиваю приложение c на c# и вызываю те же методы.

Спасибо

Richard Andrew x64

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

lewisv

Прошло уже 10 лет с тех пор, как я делал какую-либо реальную работу на c/c++.
Но для меня это похоже на cdecl.

#define CDECLCALL_CONV __cdecl

typedef void (CDECLCALL_CONV *ConnectedCB) (DPHandle hdl);
BPS_DP_API unsigned long CDECLCALL_CONV getDPDevices(DPHandle devices[], unsigned long* count);


Что вы подразумеваете под тем, что cdecl подходит для вызова управляемого кода "into"? Есть ли что-то еще, что мне нужно сделать в моем управляемом коде c#?

Richard Andrew x64

Извините. Я не заметил, что вы должным образом украсили делегата с CallingConvention.Атрибут Cdecl. Я был предполагая, что обратного вызова, возможно, требуется конвенции нарушением соглашения о стандартном. Как и вы, прошло уже некоторое время с тех пор, как я делал native/managed interop.

Как выглядит код, вызывающий обратный вызов?

lewisv

Я думаю, что он у меня есть.
Спасибо за помощь.

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

Когда я открыл соединение, я получил IntPtr обратно.
IntPtr m_ptr;
m_ptr = обертка.Открывать(...)
Я превратил это в структуру
m_handle= Маршал.PtrToStructure<docprochandle>(m_ptrp);

Я передавал эту ручку обратно в библиотеку dll.
обертка.DPSetCallBacks(арт m_handle, m_events Реф );

Но обертке нужен был не DocProcHandle, а IntPtr. Когда я заменил его, он прошел через приложение просто отлично.
обертка.DPSetCallBacks(m_ptr, ref m_events);

Может быть, он прекрасно работал с не-обратными вызовами, но с обратными вызовами, потому что он снова был помещен в стек и испортил стек, как вы упомянули.