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 を出さないようになってから言ってください。