Управление деструкторами управляемых (C #) и неуправляемых (C ++) объектов - PullRequest
4 голосов
/ 22 декабря 2009

У меня есть управляемый объект в c # dll, который поддерживает анонимный целочисленный дескриптор неуправляемого объекта в c ++ dll. Внутри dll c ++ анонимное целое число используется в std :: map для извлечения неуправляемого объекта c ++. Благодаря этому механизму я могу поддерживать слабую связь между управляемым и неуправляемым объектом с помощью анонимного целочисленного дескриптора.

В методе finalize (деструкторе) управляемого объекта у меня есть вызов в неуправляемую dll для удаления неуправляемого объекта.

Все хорошо, так как программа на c # работает, но у меня проблема при выходе из программы. Поскольку у меня нет контроля над порядком операций удаления на управляемой стороне, неуправляемая dll удаляется из памяти ДО любого управляемого объекта. Таким образом, когда вызывается деструктор управляемого объекта (который, в свою очередь, вызывает неуправляемый деструктор [по крайней мере косвенно]), неуправляемый объект уже удален, и программа вылетает.

Итак, как я могу безопасно удалить неуправляемый объект во внешнем dll c ++, который связан с управляемым объектом в программе на c #.

Спасибо

Andrew

Ответы [ 4 ]

8 голосов
/ 22 декабря 2009

Финализатор любого управляемого объекта почти всегда должен использоваться только как отказоустойчивый. Как правило, если у вас есть логика финализатора, то ваш объект, вероятно, должен реализовать IDisposable. Основной шаблон для реализации IDisposable (скажем, имя класса MyClass):

public class MyClass : IDisposable
{
    private int extHandle;

    public MyClass()
    {
        extHandle = // get the handle
    }

    public void Dispose()
    {
        Dispose(true);

        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if(disposing)
        {
            // call dispose() on any managed objects you might have
        }

        // release the handle
    }

    ~MyClass()
    {
        Dispose(false);
    }
}

Это также означает, что любой код, создающий и использующий этот объект, должен иметь возможность управлять временем жизни объекта. Самый простой способ - заключить экземпляр в блок using, например:

using(MyClass c = new MyClass())
{
    // do things with c
}

Блок using автоматически вызывает Dispose для объекта, когда он выходит из области видимости в конце блока. Вещи, конечно, усложняются, когда объект должен существовать вне одной функции. В любом случае, всякий раз, когда объект заканчивается с Dispose, необходимо вызывать.

3 голосов
/ 22 декабря 2009

Вы можете решить эту проблему быстро, проверив Environment.HasShutdownStarted в финализаторе вашего объекта C # (и не вызывая C ++ DLL / удаляя объект C ++, если HasShutdownStarted имеет значение true). Если вы не находитесь в основном AppDomain, вам, возможно, придется проверить AppDomain.Current.IsFinalizingForUnload (на самом деле это может быть безопаснее в целом).

Обратите внимание, что это просто позволяет избежать вызова освобожденной библиотеки (т. Е. Избежать запуска неуправляемого деструктора): если неуправляемая библиотека содержала ресурс, который не будет автоматически освобожден при завершении процесса, тогда этот ресурс мог бы быть пропущен. (Большинство ресурсов ОС освобождаются при отключении процессов, поэтому это часто не вызывает проблем.) И, как отмечает Адам, финализатор CLR задуман как отказоустойчивый: вы действительно хотите освобождать ресурсы более детерминистически. Поэтому, если это конструктивно возможно, предложение Игоря реализовать IDisposable в классе C # и детерминистически удалить объект было бы предпочтительным.

1 голос
/ 22 декабря 2009

Обычный способ сделать это - извлечь управляемый объект из IDisposable

Я всегда пытаюсь вызвать object.Dispose явно, когда я закончу с объектом, но я не уверен, что это будет необходимо в вашем случае. В документации, которую я прочитал, неясно, будет ли гарантировано, что Dispose () будет вызываться до того, как ваша dll выгружается или нет.

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

1 голос
/ 22 декабря 2009

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

Так что, если вы усердны с вами Распоряжаться вызовами (и используете блоки using), у вас не должно быть аварийных остановов.

Редактировать: Я думаю, что проблема в том, что на самом деле неуправляемая DLL выгружается до запуска финализатора. Ye old "Как только приложение закрывается, нет никаких гарантий относительно того, в каком порядке он выгружается".

Возможно, вы можете поэкспериментировать с неуправляемыми ресурсами в управляемой сборке C ++? Таким образом, вы знаете, что DLL не сработает, пока вы не закончили с этим, и вам не нужно делать уродливые вещи P / Invoke.

Вот пример из MSDN:

ref struct A {
   // destructor cleans up all resources
   ~A() {
      // clean up code to release managed resource
      // ...
      // to avoid code duplication 
      // call finalizer to release unmanaged resources
      this->!A();
   }

   // finalizer cleans up unmanaged resources
   // destructor or garbage collector will
   // clean up managed resources
   !A() {
      // clean up code to release unmanaged resource
      // ...
   }
};

Подробнее здесь http://msdn.microsoft.com/en-us/library/ms177197.aspx

Выше приведен тот же шаблон, что и в C #, за исключением того, что вы можете избежать использования неуправляемых ресурсов в управляемой сборке C ++. Если вы действительно ДОЛЖНЫ иметь их в неуправляемой DLL (не в статической неуправляемой библиотеке), то вы застряли, у вас будут те же проблемы с завершением работы.

...