none
托管代码? RRS feed

  • 问题

  • 代码分析规则

    使用 SafeHandle 封装操作系统资源。不要使用 HandleRef IntPtr 类型的字段。

    我不明白?托管代码,托管内存?

    2010年9月23日 7:51

答案

  • 我来解释一下什么是 Handle 以及 SafeHandle。

    Handle 大家都知道,是一个 32 位 (x64 为 64 位) 的整形指针,在 C++ 描述为 HWND。它用来唯一标识 Windows 中的系统资源,比如文件、Mutex、原子、进程、库、控件、注册表等等。

    但在托管代码中,引用到系统资源后,必须显式去 Dispose 这些资源,否则,就会造成系统资源没有释放,以至于内存泄漏。.NET 提供一套机制,IDisposable 设计模式,来解决这个问题。在一个引用了非托管 Windows 资源的类型中,必须实现 System.IDisposable 来正确释放其资源。

    下面是一个示例实现。

    public class BinaryFileWriter : IDisposable
    {
        private MemoryStream _stream;
        private string _filePath;
        private byte[] _data;

        public BinaryFileWriter(string filePath, byte[] data)
        {
            if (filePath == null) throw new ArgumentNullException("filePath");
            if (data == null) throw new ArgumentNullException("data");
            this._stream = new MemoryStream(data);
            this._filePath = filePath;
            this._data = data;
        }

        public void Save()
        {
            using (StreamWriter writer = new StreamWriter(File.Open(this._filePath))
            {
                writer.WriteBytes(this._data);
            }
        }
       
        public void Dispose()
        {
            this.Dispose(true);
            // 这一句话告诉 GC 此类型已经实现 IDisposable,不要调用其析构函数。
            System.GC.SupressFinalizer(this);
        }

        public void Dispose(bool disposing)
        {
            if (disposing)
            {
                // 释放非托管资源。
                this._stream.Dispose();
            }
        }

        public ~BinaryFileWriter()
        {
            this.Dispose(false);
        }
    }

    可以看到,IDisposable 设计模式中必须有 Dispose() 以及 Dispose(bool) 两个重载决策。并且,由于 Dispose 方法代替析构函数清理非托管资源,如果它抛出异常,则整个 GC 机制将会受到影响而导致无法预期的结果。因此,Dispose 并不期待产生异常同样,析构函数也不期待任何异常

    然而,有些情况下,在设计类型时,不能在代码层次保证所有的资源清理都不产生异常。如果一旦产生异常,特别是在析构函数调用时异常,该类型实例即无法正确清理资源,导致 GC 回收失败,从而导致内存泄漏。为了解决这一问题,Microsoft 引入了几个非常特殊的类型,他们是:

    CriticalFinalizerObject
    SafeHandle
    ZeroOrMinusOneIsInvalid
    ...
    SafeFileHandle
    SafeLibraryHandle
    ...

    CriticalFinalizerObject 能够保证所有继承这个类型的类型实例都能正确被回收。SafeHandle 继承于该类型,用来表示一个安全句柄。在使用它的时候,CLR 保证这些句柄能够被正确清理以及回收。所以,您的代码分析工具要求您使用 SafeHandle 以便代替 HandleRef 或者 IntPtr。

    CLR 对于 CriticalFinalizerObject 有其特殊的处理方式,就像 MarshalByRefObject 一样。

    如果需要了解更多详情,请联系我。


    Mark Zhou
    • 已标记为答案 王基任 2010年9月26日 15:16
    2010年9月24日 9:49

全部回复

  • 我来解释一下什么是 Handle 以及 SafeHandle。

    Handle 大家都知道,是一个 32 位 (x64 为 64 位) 的整形指针,在 C++ 描述为 HWND。它用来唯一标识 Windows 中的系统资源,比如文件、Mutex、原子、进程、库、控件、注册表等等。

    但在托管代码中,引用到系统资源后,必须显式去 Dispose 这些资源,否则,就会造成系统资源没有释放,以至于内存泄漏。.NET 提供一套机制,IDisposable 设计模式,来解决这个问题。在一个引用了非托管 Windows 资源的类型中,必须实现 System.IDisposable 来正确释放其资源。

    下面是一个示例实现。

    public class BinaryFileWriter : IDisposable
    {
        private MemoryStream _stream;
        private string _filePath;
        private byte[] _data;

        public BinaryFileWriter(string filePath, byte[] data)
        {
            if (filePath == null) throw new ArgumentNullException("filePath");
            if (data == null) throw new ArgumentNullException("data");
            this._stream = new MemoryStream(data);
            this._filePath = filePath;
            this._data = data;
        }

        public void Save()
        {
            using (StreamWriter writer = new StreamWriter(File.Open(this._filePath))
            {
                writer.WriteBytes(this._data);
            }
        }
       
        public void Dispose()
        {
            this.Dispose(true);
            // 这一句话告诉 GC 此类型已经实现 IDisposable,不要调用其析构函数。
            System.GC.SupressFinalizer(this);
        }

        public void Dispose(bool disposing)
        {
            if (disposing)
            {
                // 释放非托管资源。
                this._stream.Dispose();
            }
        }

        public ~BinaryFileWriter()
        {
            this.Dispose(false);
        }
    }

    可以看到,IDisposable 设计模式中必须有 Dispose() 以及 Dispose(bool) 两个重载决策。并且,由于 Dispose 方法代替析构函数清理非托管资源,如果它抛出异常,则整个 GC 机制将会受到影响而导致无法预期的结果。因此,Dispose 并不期待产生异常同样,析构函数也不期待任何异常

    然而,有些情况下,在设计类型时,不能在代码层次保证所有的资源清理都不产生异常。如果一旦产生异常,特别是在析构函数调用时异常,该类型实例即无法正确清理资源,导致 GC 回收失败,从而导致内存泄漏。为了解决这一问题,Microsoft 引入了几个非常特殊的类型,他们是:

    CriticalFinalizerObject
    SafeHandle
    ZeroOrMinusOneIsInvalid
    ...
    SafeFileHandle
    SafeLibraryHandle
    ...

    CriticalFinalizerObject 能够保证所有继承这个类型的类型实例都能正确被回收。SafeHandle 继承于该类型,用来表示一个安全句柄。在使用它的时候,CLR 保证这些句柄能够被正确清理以及回收。所以,您的代码分析工具要求您使用 SafeHandle 以便代替 HandleRef 或者 IntPtr。

    CLR 对于 CriticalFinalizerObject 有其特殊的处理方式,就像 MarshalByRefObject 一样。

    如果需要了解更多详情,请联系我。


    Mark Zhou
    • 已标记为答案 王基任 2010年9月26日 15:16
    2010年9月24日 9:49
  • 我来解释一下什么是 Handle 以及 SafeHandle。

    Handle 大家都知道,是一个 32 位 (x64 为 64 位) 的整形指针,在 C++ 描述为 HWND。它用来唯一标识 Windows 中的系统资源,比如文件、Mutex、原子、进程、库、控件、注册表等等。

    但在托管代码中,引用到系统资源后,必须显式去 Dispose 这些资源,否则,就会造成系统资源没有释放,以至于内存泄漏。.NET 提供一套机制,IDisposable 设计模式,来解决这个问题。在一个引用了非托管 Windows 资源的类型中,必须实现 System.IDisposable 来正确释放其资源。

    下面是一个示例实现。

    public class BinaryFileWriter : IDisposable
    {
        private MemoryStream _stream;
        private string _filePath;
        private byte[] _data;

        public BinaryFileWriter(string filePath, byte[] data)
        {
            if (filePath == null) throw new ArgumentNullException("filePath");
            if (data == null) throw new ArgumentNullException("data");
            this._stream = new MemoryStream(data);
            this._filePath = filePath;
            this._data = data;
        }

        public void Save()
        {
            using (StreamWriter writer = new StreamWriter(File.Open(this._filePath))
            {
                writer.WriteBytes(this._data);
            }
        }
       
        public void Dispose()
        {
            this.Dispose(true);
            // 这一句话告诉 GC 此类型已经实现 IDisposable,不要调用其析构函数。
            System.GC.SupressFinalizer(this);
        }

        public void Dispose(bool disposing)
        {
            if (disposing)
            {
                // 释放非托管资源。
                this._stream.Dispose();
            }
        }

        public ~BinaryFileWriter()
        {
            this.Dispose(false);
        }
    }

    可以看到,IDisposable 设计模式中必须有 Dispose() 以及 Dispose(bool) 两个重载决策。并且,由于 Dispose 方法代替析构函数清理非托管资源,如果它抛出异常,则整个 GC 机制将会受到影响而导致无法预期的结果。因此,Dispose 并不期待产生异常同样,析构函数也不期待任何异常

    然而,有些情况下,在设计类型时,不能在代码层次保证所有的资源清理都不产生异常。如果一旦产生异常,特别是在析构函数调用时异常,该类型实例即无法正确清理资源,导致 GC 回收失败,从而导致内存泄漏。为了解决这一问题,Microsoft 引入了几个非常特殊的类型,他们是:

    CriticalFinalizerObject
    SafeHandle
    ZeroOrMinusOneIsInvalid
    ...
    SafeFileHandle
    SafeLibraryHandle
    ...

    CriticalFinalizerObject 能够保证所有继承这个类型的类型实例都能正确被回收。SafeHandle 继承于该类型,用来表示一个安全句柄。在使用它的时候,CLR 保证这些句柄能够被正确清理以及回收。所以,您的代码分析工具要求您使用 SafeHandle 以便代替 HandleRef 或者 IntPtr。

    CLR 对于 CriticalFinalizerObject 有其特殊的处理方式,就像 MarshalByRefObject 一样。

    如果需要了解更多详情,请联系我。


    Mark Zhou

    2010年9月27日 2:47