Я надеюсь, что кто-то может взглянуть на это и объяснить мне, что мне не хватает.
Краткая версия: я создаю управляемый объект в функции и вызываю его члена. До того, как эта функция-член вернулась, GC завершает объект прямо из-под меня из другого потока.
Вот код C #, который создает объект и вызывает функцию C ++ / CLI
var integrator = Integrator.Create();
_logger.Debug("Integrating normal map. Res=" + model.ResolutionMmpp);
var hm = integrator.IntegrateNormalMap(nm, model.ResolutionMmpp);
// *** 'integrator' gets finalized before I even get here. How? ***
_logger.Debug("Back from integrate");
Вот реализация IntegrateNormalMap
(код C ++ / CLI). Он обращается к своему неуправляемому эквивалентному коду C ++.
HeightMap^ Integrator::IntegrateNormalMap(NormalMap^ nrm, double res)
{
// Note: 'm_psi' is a pointer to the valid, unmanaged C++ object
return gcnew HeightMap(m_psi->integrateNormalMap(nrm->sdkMap(), res));
}
Я установил точку останова на финализаторе класса Integrator
(т.е. Integrator::!Integrator
), и я могу видеть сборщик мусора, вызывающий финализатор моего объекта C ++ / CLI из другого потока.
Вот стек вызовов вызываемого финализатора
Sdk::Integrator::~Integrator() Line 26 C++
Sdk::Integrator::Dispose() C++
Sdk::Integrator::Dispose() C++
Sdk::Integrator::!Integrator() Line 38 C++
Sdk::Integrator::Dispose() C++
[Native to Managed Transition]
00007ffeee324034() Unknown
00007ffeee473691() Unknown
Но в то же время эта функция IntegrateNormalMap()
все еще работает в исходном потоке.
gs::detail::PoissonIntegratorV2014::reconstructNormals(normals={...}) Line 79 C++
gs::detail::PoissonIntegratorV2014::integrateNormalMap(nrm, res) Line 35 C++
[Managed to Native Transition]
Sdk::Integrator::IntegrateNormalMap(nrm, res) Line 45 C++
Mobile.ViewModels.ScanVm.Generate3d(ffcEnum, token) Line 595 C#
Mobile.ViewModels.ScanVm.Generate3d(token) Line 642 C#
Capture.ViewModels.NormalScanJob.Generate3d() Line 60 C#
Capture.ViewModels.CaptureVm.get_Mesh.AnonymousMethod__27_4(job = {Capture.ViewModels.NormalScanJob}) Line 371 C#
System.Threading.Tasks.Dataflow.TransformBlock<Capture.ViewModels.NormalScanJob, Capture.ViewModels.NormalScanJob>.ProcessMessage(transform, messageWithId = {[{Capture.ViewModels.NormalScanJob}, 0]}) Unknown
System.Threading.Tasks.Dataflow.TransformBlock<System.__Canon, System.__Canon>..ctor.AnonymousMethod__3(messageWithId) Unknown
System.Threading.Tasks.Dataflow.Internal.TargetCore<Capture.ViewModels.NormalScanJob>.ProcessMessagesLoopCore() Unknown
System.Threading.Tasks.Task.Execute() Unknown
System.Threading.ExecutionContext.RunInternal(executionContext, callback, state, preserveSyncCtx) Unknown
System.Threading.ExecutionContext.Run(executionContext, callback, state, preserveSyncCtx) Unknown
System.Threading.Tasks.Task.ExecuteWithThreadLocal(currentTaskSlot = Id = 2494, Status = Running, Method = Inspecting the state of an object in the debuggee of type System.Delegate is not supported in this context.) Unknown
System.Threading.Tasks.Task.ExecuteEntry(bPreventDoubleExecution) Unknown
System.Threading.ThreadPoolWorkQueue.Dispatch() Unknown
[Native to Managed Transition]
00007ffeee324034() Unknown
00007ffeee473691() Unknown
Обратите внимание, что неуправляемый код C ++ не делает ничего странного.
- В цикле выполняется работа с неуправляемыми переменными в неуправляемом объекте.
- Это ничего не перезаписывает / не портит (этот код много работал для неуправляемых клиентов в течение 6-7 лет).
- Нигде я не делаю что-либо с GC или слабыми ссылками или чем-то подобным Я просто создаю локальный объект, вызываю функцию, и она удаляется из-под меня.
Я наткнулся на странное "исправление", но я ему не доверяю. Я не думаю, что это действительно исправление, и я не понимаю, почему это предотвращает проблему:
Если я поместил фрейм try / catch в управляемую функцию C ++ / CLI IntegrateNormalMap
(чтобы преобразовать любые неуправляемые исключения C ++ в управляемые), проблема исчезнет. Вот оно, переписано с помощью try / catch
HeightMap^ Integrator::IntegrateNormalMap(NormalMap^ nrm, double res)
{
try
{
return gcnew HeightMap(m_psi->integrateNormalMap(nrm->sdkMap(), res));
}
catch (const std::exception& ex)
{
throw gcnew SdkException(ex.what());
}
}
Примечание. Хотя это, очевидно, хорошая общая практика (т.е. предотвращение выхода неуправляемых исключений), лежащий в основе неуправляемый код фактически не выдает никаких исключений ни в одном из случаев. Это все еще обрабатывает.
Я также проверил, что я * компилирую этот класс C ++ / CLI с параметром / clr. Управляется.
Так что теперь я запутался. Действительно ли моя структура исключений "устранила" проблему? Если так, что это было и как этот простой шаг исправляет это?
(я использую Visual Studio 2019 и .NET Framework 4.8)
РЕДАКТИРОВАТЬ: Просто добавьте, чтобы показать деструктор и финализатор моего класса C ++ / CLI Integrator, чтобы уточнить комментарии Ханса Пассанта ниже.
// Take ownership of the given unmanaged pointer.
Integrator::Integrator(std::unique_ptr<gs::Integrator> sip)
: m_psi(sip.release())
{
}
Integrator::~Integrator()
{
// Clean up and null out in case we get called twice
delete m_psi;
m_psi = nullptr;
}
Integrator::!Integrator()
{
this->~Integrator();
}