Architect's Log

I'm a Cloud Architect. I'm highly motivated to reduce toils with driving DevOps.

WININET.DLLの外部メソッド呼び出しで使用されるハンドルが確実に解放されるように実装する

どういうこと?

.NET Compact Frameworkでは、FtpWebRequestクラスやFtpWebResponseクラスが提供されていません。そのためWININET.DLLの外部メソッド呼び出しが必要ですが、使用されるハンドルは自動的には解放されません。IDisposableを実装したラッパーを作成することで、確実に解放されるようにできます。

どうして?

Disposeメソッドでハンドルを解放するように実装すれば、using句のスコープを抜けたときに確実にハンドルが解放されるようになります。

どうすれば?

ソースコードを記載します。

using System;
using System.Runtime.InteropServices;

namespace Shared.Net {

    /// <summary>
    /// WININET.DLLの外部メソッド呼び出しで使用されるハンドル(<see cref="System.IntPtr"/>)を
    /// ラップします。
    /// </summary>
    public class InternetHandle : IDisposable {

        [DllImport(
            "WININET", 
             EntryPoint = "InternetCloseHandle", 
             SetLastError = true, 
             CharSet = CharSet.Auto)]
        static extern bool InternetCloseHandle(IntPtr hInternet);

        /// <summary>
        /// ハンドルが0かどうかを示します。
        /// </summary>
        /// <value>ハンドルが0で初期化されている場合はtrue。それ以外はfalse。</value>
        public bool IsZero {
            get {
                return this.pointer == IntPtr.Zero;
            }
        }

        /// <summary>
        /// このクラスの内部で保持しているハンドルです。
        /// </summary>
        /// <value>ハンドルが0の場合は無効なハンドル、それ以外の場合は有効なハンドルです。</value>
        public IntPtr Pointer {
            get {
                return this.pointer;
            }
        }
        IntPtr pointer = IntPtr.Zero;
    
        /// <summary>
        /// InternetHandleクラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="pointer">ハンドル</param>
        public InternetHandle(IntPtr pointer) {
            this.pointer = pointer;
        }

        #region 終了処理

        /// <summary>
        /// ガベージ コレクションによってクリアされる前に、アンマネージ リソースを解放し、
        /// その他のクリーンアップ操作を実行します。 
        /// </summary>
        ~InternetHandle() {
            Dispose();
        }

        /// <summary>
        /// 既にDisposeが呼ばれたかどうかを示します。
        /// </summary>
        /// <value>既にDisoseが呼ばれた場合はtrue。まだ呼ばれていない場合はfalse。</value>
        public bool Disposed {
            get { return this.disposed; }
        }
        private bool disposed = false;

        /// <summary>
        /// アンマネージ リソースの解放および終了処理を実行します。
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// アンマネージ リソースの解放および終了処理を実行します。
        /// </summary>
        /// <param name="disposing">
        /// マネージ リソースとアンマネージ リソースの両方を解放する場合は true。
        /// アンマネージ リソースだけを解放する場合は false。
        /// </param>
        private void Dispose(bool disposing) {
            if (disposed) return;

            // アンマネージリソースを解放する。
            if (this.IsZero) InternetHandle.InternetCloseHandle(this.pointer);
            this.pointer = IntPtr.Zero;

            disposed = true;
        }

        #endregion 終了処理

    }
}