AccessViolationException чтение памяти, выделенной в приложении C++ из библиотеки DLL C++/CLI

у меня есть клиент C++ для DLL C++/CLI, который инициализирует серию DLL C#.

раньше это работало. Код, который терпит неудачу, не изменился. Измененный код не вызывается до возникновения исключения. Моя среда компиляции изменилась, но перекомпиляция на машине со средой, подобной моей старой, все еще не удалась. (EDIT: как мы видим в ответе, это не совсем так, я только перекомпилировал библиотеку в старой среде, а не библиотека и клиент вместе. Клиентские проекты были модернизированы и не могли легко вернуться назад.)

кто-то кроме меня перекомпилировал библиотеку, и мы начали получать проблемы с управлением памятью. The pointer passed in as a String must not be in the bottom 64K of the process's address space. Я перекомпилировал его, и все работало хорошо без изменений кода. (Alarm #1) Недавно он был перекомпилирован, и проблемы управления памятью со строками снова появились, и на этот раз они не уходят. Новая ошибка Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

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

определена в библиотеке:

struct Config
{
    std::string aye;
    std::string bee;
    std::string sea;
};

extern "C" __declspec(dllexport) BridgeBase_I* __stdcall Bridge_GetConfiguredDefaultsImplementationPointer(
    const std::vector<Config> & newConfigs, /**< new configurations to apply **/
    std::string configFolderPath, /**< folder to write config files in **/
    std::string defaultConfigFolderPath, /**< folder to find default config files in **/
    std::string & status /**< output status of config parse **/
    );

в клиенте функция:

GatewayWrapper::Config bridge;
std::string configPath("./config");
std::string defaultPath("./config/default");
GatewayWrapper::Config gwtransport;
bridge.aye = "bridged.dll";
bridge.bee = "1.0";
bridge.sea = "";
configs.push_back(bridge);
_bridge = GatewayWrapper::Bridge_GetConfiguredDefaultsImplementationPointer(configs, configPath, defaultPath, status);

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

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

вернувшись в библиотеку, в первой подфункции, где отладчик не светит, я разбил оператор failing на несколько консольных отпечатков.

System::String^ temp
List<CConfig^>^ configs = gcnew List<CConfig ^>((INT32)newConfigs.size());
for( int i = 0; i< newConfigs.size(); i++)
{
  std::cout << newConfigs[i].aye<< std::flush; // prints
  std::cout << newConfigs[i].aye.c_str() << std::flush; // prints
  temp = gcnew System::String(newConfigs[i].aye.c_str());
  System::Console::WriteLine(temp); // prints
  std::cout << "Testing string creation" << std::endl; // prints
  std::cout << newConfigs[i].bee << std::flush; // crashes here
}

я получаю то же исключение при доступе bee если я двигаю newConfigs[i].bee выше задание temp или прокомментируйте объявление/назначение списка.

просто для справки, что std:: string в структуре в вектор должен был прибыть в пункт назначения ОК

почему это исключение не поймано мой try/catch

https://stackoverflow.com/a/918891/2091951

общие AccessViolationException связанные вопросы

предложения в вышеуказанных вопросах

  • перейти на .net 3.5, изменить целевую платформу-эти решения могут иметь серьезные проблемы с большим многоцелевым решением.
  • HandleProcessCorruptedStateExceptions-не работает в C++, это украшение для C#, поймать эту ошибку может быть очень плохой идеей во всяком случае
  • изменить legacyCorruptedStateExceptionsPolicy-это о ловле ошибки, не предотвращая его
  • установить .NET 4.5.2-не удается, уже есть 4.6.1. Установка 4.6.2 не помогло. Перекомпиляция на другой машине, на которой не было установлено 4.5 или 4.6, не помогла. (Несмотря на это, используется для компиляции и запуска на моей машине перед установкой Visual Studio 2013, что настоятельно предполагает, что библиотека .NET проблема?)
  • VSDebug_DisableManagedReturnValue-я вижу это только в связи с определенным сбоем в отладчике, и справка от Microsoft говорит, что другие проблемы AccessViolationException, вероятно, не связаны. (http://connect.microsoft.com/VisualStudio/feedbackdetail/view/819552/visual-studio-debugger-throws-accessviolationexception)
  • изменить настройки брандмауэра Comodo - я не использую это программное обеспечение
  • изменить весь код на управляемая память - не вариант. Общий дизайн вызова C# из C++ через C++ / CLI устойчив к изменениям. Меня специально попросили разработать его таким образом, чтобы использовать существующий код C# из существующего кода C++.
  • убедитесь, что выделена память-память должна быть выделена в стеке в клиенте C++. Я попытался сделать вектор не ссылочным параметром, чтобы принудительно скопировать вектор в явно контролируемое библиотекой пространство памяти, не помощь.
  • "нарушения доступа в неуправляемом коде, которые пузырятся до управляемого кода, всегда завернуты в AccessViolationException."- Факт, а не решение.

3 ответов


но это было несоответствие, а не конкретная версия, которая была проблемой

Да, это закон черной Буквы в VS. К сожалению, вы просто пропустили контрмеры, которые были встроены в VS2012, чтобы превратить эту ошибку в диагностируемую ошибку компоновщика. Ранее (и в VS2010) CRT выделил бы свою собственную кучу с HeapAlloc (). Теперь (в VS2013) он использует кучу процессов по умолчанию, возвращаемую GetProcessHeap ().

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

Это не то, где он заканчивается, еще одна существенная проблема заключается в том, что макет объекта std::string не совпадает между версиями. Что-то вы можете обнаружить с помощью небольшой тестовой программы:

#include <string>
#include <iostream>

int main()
{
    std::cout << sizeof(std::string) << std::endl;
    return 0;
}
  • отладки VS2010 : 32
  • VS2010 выпуск: 28
  • VS2013 отладка: 28
  • VS2013 выпуск: 24

у меня есть смутное воспоминание о Стивене Лававее, упоминающем уменьшение размера объекта std::string, очень представленное как функция, но я не могу найти его обратно. Дополнительные 4 байта в сборке отладки вызваны функцией отладки итератора, ее можно отключить с помощью _HAS_ITERATOR_DEBUGGING=0 в определениях препроцессора. Не особенность, которую вы хотели бы быстро выбросить, но это делает смешивание отладки и выпуска сборки EXE и его DLL довольно смертельным.

Излишне говорить, что различные размеры объекта серьезно байт, когда объект Config создается в DLL, построенной с одной версией стандартной библиотеки C++ и используется в другой. Многие неудачи, самая основная из которых заключается в том, что код будет просто читать элемент Config::bee из неправильного смещения. Аве (почти) гарантировано. Намного больше страданий, когда код выделяет небольшой аромат объекта Config, но записывает большой аромат std:: string, который случайным образом повреждает кучу или кадр стека.

не смешивать.


Я считаю, что 2013 ввел много изменений во внутренних форматах данных контейнеров STL, как часть толчка, чтобы уменьшить использование памяти и улучшить perf. Я знаю vector стало меньше, а string в основном прославлен vector<char>.

Microsoft признает несовместимость:

" чтобы включить новые оптимизации и проверки отладки, Visual Studio реализация стандартной библиотеки C++намеренно ломает двоичный совместимость от одной версии к другой. Поэтому, когда C++ Используется стандартная библиотека, объектные файлы и статические библиотеки, которые скомпилированный с использованием разных версий не может быть смешан в одном двоичном файле (EXE или DLL), и объекты стандартной библиотеки C++ не могут быть переданы между двоичные файлы, компилируемые с использованием разных версий."

если вы собираетесь пройти std::* объекты между исполняемыми файлами и / или DLL, вы абсолютно должны убедиться, что они использование той же версии компилятора. Было бы неплохо, чтобы ваш клиент и его библиотеки DLL каким-то образом согласовывались при запуске, сравнивая любые доступные версии (например, версия компилятора + флаги, версия boost, Версия directx и т. д.) так что вы ловите ошибки, как это быстро. Подумайте об этом как о кросс-модуле assert.

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

Я также хотел бы упомянуть, что это, вероятно, плохая идея в первую очередь использовать смарт-контейнеры в вызовах DLL. Если вы не можете гарантировать, что приложение и DLL не будут пытаться освободить или перераспределить внутренние буферы контейнеров другого, вы можете легко столкнуться с проблемами повреждения кучи, так как приложение и DLL имеют свою собственную внутреннюю кучу c++. Я думаю, что такое поведение считается неопределенным в лучшем случае. Даже проходя мимо!--5--> аргументы все равно могут привести к перераспределению в редких случаях, так как const не мешает компилятору возиться с mutable внутренние органы.


Кажется, у вас повреждение памяти. Microsoft Application Verifier бесценен в поиске коррупции. Используйте его, чтобы найти ошибку:

  1. установите его на свой dev-компьютер.
  2. добавьте к нему свой exe.
  3. только выберите Basics\Heaps.
  4. Нажимаем Сохранить. Не имеет значения, если вы держите application verifier открытым.
  5. запустите программу несколько раз.
  6. если он аварийно завершает работу, отлаживайте его, и на этот раз сбой укажет на вашу проблему, а не просто случайное место в вашей программе.

PS: Это отличная идея, чтобы иметь верификатор приложений включен в любое время для вашего проекта разработки.