1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

P/Invoke で __in_opt の引数を wrap するクラス

Last updated at Posted at 2012-06-07

region ながい前置き

Win32 API で、関数の __in_opt の引数に NULL 値を渡したいことが稀によくある。

ボクがぶち当たったのは SetFileTime。
SetFileTime はこんなプロトタイプをしてる。

BOOL WINAPI SetFileTime(
  __in      HANDLE hFile,
  __in_opt  const FILETIME *lpCreationTime,
  __in_opt  const FILETIME *lpLastAccessTime,
  __in_opt  const FILETIME *lpLastWriteTime
);

たとえば、ファイル作成日付だけを更新したい場合は lpCreationTime にだけ設定する日付を指定して
後の二つには NULL を指定すればよい。

とりあえず、何も考えずに C# に移植してみたわけだけど。

//
[DllImport("kernel32.dll", SetLastError = true)]
static extern int SetFileTime(SafeFileHandle  hFile, ref FILETIME lpCreationTime, ref FILETIME lpLastAccessTime, ref FILETIME lpLastWriteTime);

void setFileTime(SafeFileHandle hFile, DateTime creationTime) {
    FILETIME ftCreationTime = ToFileTime(creationTime);

    SetFileTime(hFile, ref ftCreationTime, null, null);             //  コンパイルエラー
    SetFileTime(hFile, ref ftCreationTime, ref null, ref null);     //  コンパイルエラー
}

ref の引数に null が渡せないじゃないですかー。やだー!

ボクは、ファイル作成日付だけを変更したいのにこれだと他の日付も必ず何かの値を指定しなくてはいけない。
さきに他の日付もとっておいて、一緒に指定しろ? 問題はそこじゃないだろ。

さて、こまった。

調べてみたら、Marshal.AllocHGlobal を中継して渡せばやりたいことが出来るらしい。

// このコメントを取り除くと、単語が意味不明な赤枠で囲まれるけど、コメントをつけることで回避できる。
// Qiita Markdown はよくわからないな。
[DllImport("kernel32.dll", SetLastError = true)]
static extern int SetFileTime(SafeFileHandle  hFile, IntPtr lpCreationTime, IntPtr lpLastAccessTime, IntPtr lpLastWriteTime);

void setFileTime(SafeFileHandle hFile, DateTime creationTime) {
    FILETIME ftCreationTime = ToFileTime(creationTime);

    #region 記憶だけで書いてるからコンパイルエラーが出ると思いますし。

    IntPtr pCreationTime = Marshal.AllocHGlobal(Marshal.Sizeof(typeof(FILETIME)));
    Marshal.StructureToPtr(ftCreationTime, pCreationTime, false);

    //SetFileTime(hFile, ref ftCreationTime, null, null);               //  コンパイルエラー    //  さっきの
    SetFileTime(hFile, pCreationTime, IntPtr.Zero, IntPtr.Zero);        //  ok.
    
    Marshal.FreeHGLobal(pCreationTime);

    #endregion
}

これ、__in_opt 引数の数だけ書くんですか? ・・・うぇ。

っつーわけで、この IntPtr に変換する作業をまとめたクラスをざざっと作りる。

endregion // ながい前置き

// Demand unmanaged code permission to use this class.
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
sealed class NullablePInvokeParam<T> : SafeHandleZeroOrMinusOneIsInvalid where T : struct {

    // Set ownsHandle to true for the default constructor.
    internal NullablePInvokeParam(T? ptr) : base(true) {
        Value = ptr;
    }

    internal T? Value {
        get {
            if (this.handle != NULL) {
                T temp = default(T);
                Marshal.PtrToStructure(handle, temp);
                return temp;
            }
            else {
                T? value = new T?();
                return value;
            }
        }
        set {
            ReleaseHandle();

            if (value != null && value.HasValue) {
                IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(T)));
                Marshal.StructureToPtr(value.Value, p, false);

                SetHandle(p);
            }
        }
    }

    // Perform any specific actions to release the
    // handle in the ReleaseHandle method.
    // Often, you need to use Pinvoke to make
    // a call into the Win32 API to release the
    // handle. In this case, however, we can use
    // the Marshal class to release the unmanaged
    // memory.
    override protected bool ReleaseHandle() {
        // "handle" is the internal value for the IntPtr handle.

        // If the handle was set,
        // free it. Return success.
        if (handle != IntPtr.Zero) {
            // Free the handle.
            Marshal.FreeHGlobal(handle);

            // Set the handle to zero.
            handle = IntPtr.Zero;

            // Return success.
            return true;

        }

        // Return false.
        return false;
    }
}

こんな風に使う。ん、すっきり。
IntPtr への キャストオペレータを用意すればもっとすっきりするかもだけど、キャストオペレータ嫌いなので。

int SetFileTime(
    SafeFileHandle hFile,
    FILETIME? lpCreationTime,
    FILETIME? lpLastAccessTime,
    FILETIME? lpLastWriteTime
    )
{
    using (var 
        pCreationTime   = new NullablePInvokeParam<FILETIME>(lpCreationTime),
        pLastAccessTime = new NullablePInvokeParam<FILETIME>(lpLastAccessTime),
        pLastWriteTime  = new NullablePInvokeParam<FILETIME>(lpLastWriteTime))
    {
    return SetFileTime(
        hFile,
        pCreationTime.DangerousGetHandle(),
        pLastAccessTime.DangerousGetHandle(),
        pLastWriteTime.DangerousGetHandle()
        );
    }
}

FileInfo.CreationTime を使え?
そんなことは PathTooLongException を出さないようになってから言ってください。

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?