解決するもんだね
夜中も気温が、19度ほどあって、驚き。
朝は、ちょっとだけ雨が降ったけれど、曇り。
お昼頃から雨。
昨日の続き。
Win32 APIでファイルやフォルダの日時を変更出来ないのが解せない。
改めてネット検索してみるか、と思って、検索。
前は、「Win32 api SetFileTime vb.net」で検索。
やはり、VB.NETの情報は少ないだろうから、「Win32 api SetFileTime folder c#」に変えて検索してみた。
すると、お、これは?というページに行き当たり、そこに書かれていることを、AIに教えてもらったものに当てはめてみた。
なんと、これで出来るようになった!
まさかこんな形で解決するとは思わなかった。(笑)
需要はあるかどうかわからないけれど、VB.NETに置き換えた最終形を残しておこう。
Class Form1
'ファイル/フォルダの日時更新用Win32 APIの宣言
'FILETIME構造体の定義
<StructLayout(LayoutKind.Sequential)>
Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger
End Structure
Private Const FILE_ACCESS_GENERIC_READ As Integer = &H80000000 '元は、UIntegerだったのがエラーなので、Integerに変更
Private Const FILE_ACCESS_GENERIC_WRITE As UInteger = &H40000000
Private Const FILE_FLAG_BACKUP_SEMANTICS As Integer = &H2000000
Private Const OPEN_EXISTING As Integer = 3
Private Const INVALID_HANDLE_VALUE As Integer = -1
'日時を設定するWin32 API
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function SetFileTime(ByVal hFile As IntPtr, ByRef lpCreationTime As FILETIME, ByRef lpLastAccessTime As FILETIME, ByRef lpLastWriteTime As FILETIME) As Boolean
End Function
'ハンドルを閉じるためのWin32 API
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function CloseHandle(ByVal hObject As IntPtr) As Boolean
End Function
Private Sub SetTimestamp()
If System.IO.File.Exists("ファイルのフルパス") OrElse System.IO.Directory.Exists("フォルダのフルパス") Then
Using filefolderHandle As New FileFolderHandleWrapper("ファイルかフォルダのフルパス", FILE_ACCESS_GENERIC_READ Or FILE_ACCESS_GENERIC_WRITE, FileShare.ReadWrite, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero)
If filefolderHandle.Handle<> IntPtr.Zero AndAlso filefolderHandle.Handle <> INVALID_HANDLE_VALUE Then
Try
Dim ftCreation As FILETIME
Dim ftLastAccess As FILETIME
Dim ftLastWrite As FILETIME
Dim newCreationTime As DateTime = DateTime.Now.AddDays(-1) '新しい作成日時 この3つを希望のものに変更すること
Dim newLastAccessTime As DateTime = DateTime.Now.AddDays(-1) '新しいアクセス日時
Dim newLastWriteTime As DateTime = DateTime.Now.AddDays(-1) '新しい更新日時
Dim newCreationDateTime As Long = newCreationTime.ToFileTime()
ftCreation.dwLowDateTime = newCreationDateTime And UInteger.MaxValue
ftCreation.dwHighDateTime = newCreationDateTime >> 32
Dim newLastAccessDateTime As Long = newLastAccessTime.ToFileTime()
ftLastAccess.dwLowDateTime = newLastAccessDateTime And UInteger.MaxValue
ftLastAccess.dwHighDateTime = newLastAccessDateTime >> 32
Dim newLastWriteDateTime As Long = newLastWriteTime.ToFileTime()
ftLastWrite.dwLowDateTime = newLastWriteDateTime And UInteger.MaxValue
ftLastWrite.dwHighDateTime = newLastWriteDateTime >> 32
'SetFileTime関数でファイル/フォルダの日時を変更
'ハンドルを確実に開放するためのラッパー・クラス
Dim rt = SetFileTime(filefolderHandle.Handle, ftCreation, ftLastAccess, ftLastWrite)
Catch ex As Exception
MsgBox(ex.Message)
Finally
'ハンドルを閉じる
CloseHandle(filefolderHandle.Handle)
End Try
End If
End Using
End If
End Sub
End Class
Public Class FileFolderHandleWrapper
Implements IDisposable
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Private Shared Function CreateFile(ByVal lpFileName As String, ByVal dwDesiredAccess As FileAccess, ByVal dwShareMode As FileShare, ByVal lpSecurityAttributes As IntPtr, ByVal dwCreationDisposition As FileMode, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", SetLastError:=True)>
Private Shared Function CloseHandle(ByVal hObject As IntPtr) As Boolean
End Function
Private Const INVALID_HANDLE_VALUE As Integer = -1
Private _handle As IntPtr
Public Sub New(filePath As String, desiredAccess As Integer, shareMode As FileShare, securityAttributes As IntPtr, creationDisposition As FileMode, flagsAndAttributes As Integer, templateFile As IntPtr)
_handle = CreateFile(filePath, desiredAccess, shareMode, securityAttributes, creationDisposition, flagsAndAttributes, templateFile)
If _handle = INVALID_HANDLE_VALUE Then
Throw New IOException("Failed to create file/folder handle.")
End If
End Sub
Public ReadOnly Property Handle As IntPtr
Get
Return _handle
End Get
End Property
Public Sub Dispose() Implements IDisposable.Dispose
If Not _handle = IntPtr.Zero Then
CloseHandle(_handle)
_handle = IntPtr.Zero
End If
End Sub
End Class
参考にさせてもらったのがここ。
Checking if a directory is writable in C# | GoTask
C#で書かれているのを、VB.NETにコンバートして、AIが作ってくれたものに付け足し・修正して、上記が出来た。
基本的な流れは、AIの出してくれたものでよかったのだけれど、致命的だったのが、「CreateFile()」関数の、第2引数以下の値が、宣言されていなかった。
このページに、それが宣言されていて、それを追加し、「&H80000000」がエラーになったので、AIに聞いて、「Integer」に変更することによって、機能した。
エラーでダメだったのは、引数が不完全だったからのようだ。
上記の変更する日時は、暫定的に、1日前のものになっているので、ここを、自分の好みに変えればいい。
これでやってみると、以前のように、Explorerのロックに引っ掛からないで、ファイルやフォルダの日時を変更することが出来た。
想像していた通り、VB.NETが提供しているメソッドだと、階層が浅いところのものだったので、Explorerのロックに引っ掛かっていたようだ。
このWin32 APIであれば、もっと深い階層で動作するようで、Explorerのそれほどシビアではないロックに引っ掛かることなく、問題なく日時の変更が出来るようだ。
別のソフトウェアで問題なかったのは、そのソフトウェアも、このAPIを使っているからだろう。
あと、これのいいところは、ファイル、フォルダの区別なく、日時の更新が出来るところ。
VB.NETに用意されているメソッド、「IO.File.SetCreationTime」「IO.Directory.SetCreationTime」(他にも、更新日時、アクセス日時のものがある)は、ファイルやフォルダで別々に処理しないといけない。
その面倒がなくなるのがとてもいい。
CreateFile()のハンドルの解放を確実にするためには、Using句を使うのがいいようだ。
FileFolderHandleWrapperクラスを作成し、そちらで行っている。
よって、上記は、2つのクラスに分かれて書かれているので、お気を付けて。
Using句の書き方がわからなかったので、AIに聞いて、足りない情報を追加して、Using句も実装出来、上記の最終形となった。
解決しないかな、と思っていたことが、こんな形で解決して、いい意味で、拍子抜けしたのである。(笑)
しつこく探っていけば、解決するもんだね!
そして、スッキリである。
Pythonで作ったものが、無駄になってしまった。(苦笑)
もちろん、Pythonの勉強になったから、それはそれでいいんだけれど。
| 固定リンク
「パソコン・インターネット」カテゴリの記事
- もっと早くやっておけばよかった(2025.04.20)
- ブックマークの並びが(2025.02.28)
- 一安心(2025.02.12)
- 準備が整ってしまった(2025.02.11)
- 隙あらば(2025.02.10)
コメント