Нарушение прав доступа при вызове C ++ dll - PullRequest
2 голосов
/ 15 ноября 2010

Я создал c ++ dll (используя mingw) из кода, написанного на linux (gcc), но почему-то возникают трудности с его использованием в VC ++. DLL в основном предоставляет только один класс, я создал для него чистый виртуальный интерфейс, а также функцию фабрики, которая создает объект (единственный экспорт), который выглядит следующим образом:

extern "C" __declspec(dllexport) DeviceDriverApi* GetX5Driver(); 

Я добавил extern «C» для предотвращения искажения имени, dllexport заменяется на dllimport в реальном коде, где я хочу использовать dll, DeviceDriverApi - чистый виртуальный интерфейс.

Теперь я написал простой код на VC ++, который просто вызывает функцию фабрики, а затем просто пытается удалить указатель. Он компилируется без проблем, но когда я пытаюсь запустить его, я получаю ошибку нарушения доступа. Если я пытаюсь вызвать какой-либо метод объекта, я снова получаю нарушение прав доступа.

Когда я компилирую один и тот же код в MinGW (gcc) и использую ту же библиотеку, он запускается без проблем. Так что должно быть что-то (хе-хе, я думаю, на самом деле много различий :)) между тем, как код VC ++ использует библиотеку и код gcc.

Есть идеи что?

Cheers, Том

Edit: Код:

    DeviceDriverApi* x5Driver = GetX5Driver();


if (x5Driver->isConnected())
    Console::WriteLine(L"Hello World");

delete x5Driver;

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

Ответы [ 4 ]

2 голосов
/ 15 ноября 2010
  • Вы используете один компилятор (mingw) для DLL, а другой (VC ++) для вызывающего кода.
  • Вы вызываете функцию 'C', но возвращаете указатель на объект C ++.

То, что никогда не будет работать, потому что компоновки VTable почти гарантированно несовместимы. Кроме того, DLL и приложение, вероятно, используют разные менеджеры памяти, так что вы делаете new() с одним и delete() с другим. Опять же, это просто не сработает.

Чтобы это работало, оба компилятора должны поддерживать стандартный ABI (двоичный интерфейс приложения). Я не думаю, что такая вещь существует для Windows.

Лучшим вариантом является раскрытие всех методов и свойств объекта DLL с помощью функций C (включая один для удаления объекта). Вы можете повторно обернуть в объект C ++ на вызывающем конце.

1 голос
/ 15 ноября 2010

(1) Я подозреваю, что проблема с сопутствующей связью также существует, хотя простое предложение Лео, похоже, не помогло.

Является ли isConnected виртуальным? Вполне возможно, что MinGW и VC ++ используют разные реализации для VTable, и в этом случае, ну, в общем, неудача.

Попытайтесь увидеть, как далеко вы продвинулись с отладчиком: он падает при вызове или при возврате? Вы получаете неправильный код? (Если вы знаете, как читать ассемблер, это обычно очень помогает в решении этих проблем.)

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

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

Если вы хотите положиться на это поведение, вам необходимо убедиться:

  • Caller и Callee (т.е. в вашем случае DLL и основная программа) скомпилированы с одной и той же версией компилятора sam
  • для всех поддерживаемых компиляторов вы настроили параметры компиляции, чтобы гарантировать, что вызывающий и вызываемый абоненты используют одно и то же общее состояние библиотеки времени выполнения.

Так что лучше всего изменить свой API на:

extern "C" __declspec(dllexport) DeviceDriverApi* GetX5Driver(); 
extern "C" __declspec(dllexport) void FreeDeviceDriver(DeviceDriverApi* driver); 

и, в месте вызова, оберните каким-либо образом (например, в boost::intrusive_ptr).

1 голос
/ 15 ноября 2010

Два разных компилятора могут использовать разные соглашения о вызовах. Попробуйте поместить _cdecl перед именем функции как в коде клиента, так и в коде DLL, и перекомпилировать оба.

Подробнее о соглашениях о вызовах можно узнать здесь: http://en.wikipedia.org/wiki/X86_calling_conventions

РЕДАКТИРОВАТЬ: Вопрос был обновлен более подробно, и, похоже, проблема в том, что Адриан Плиссон описывает в конце своего ответа. Вы создаете объект в одном модуле и освобождаете его в другом, что неправильно.

0 голосов
/ 15 ноября 2010

попробуйте просмотреть импортированные библиотеки как из вашей DLL, так и из исполняемого файла вашего клиента.(вы можете использовать Dependency Viewer или dumpbin или любой другой инструмент, который вам нравится).убедитесь, что и DLL, и клиентский код используют одну и ту же среду выполнения C ++.

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

если это действительно ваша проблема, попробуйте не уничтожать указатель в исполняемом файле клиента, а объявить и экспортировать функцию в вашей DLL, которая позаботится об уничтожении указателя.

...