Click here to Skip to main content
15,885,546 members
Home / Discussions / Visual Basic
   

Visual Basic

 
GeneralRe: FindFirstFileEx() and Unicode Pin
Dave Kreskowiak5-Jun-13 9:46
mveDave Kreskowiak5-Jun-13 9:46 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie5-Jun-13 16:29
treddie5-Jun-13 16:29 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 8:15
treddie6-Jun-13 8:15 
GeneralRe: FindFirstFileEx() and Unicode Pin
Dave Kreskowiak6-Jun-13 10:10
mveDave Kreskowiak6-Jun-13 10:10 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 11:16
treddie6-Jun-13 11:16 
AnswerRe: FindFirstFileEx() and Unicode Pin
Alan N6-Jun-13 13:27
Alan N6-Jun-13 13:27 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 14:13
treddie6-Jun-13 14:13 
AnswerRe: FindFirstFileEx() and Unicode Pin
TnTinMn5-Jun-13 18:14
TnTinMn5-Jun-13 18:14 
Hi treddie,

Based on my understanding of your code, I see no reason to use FindFirstFileEx versus FindFirstFile. Also, the directory filter flag option can fail silently if it is not supported and you need to check the attributes anyways if it was your intent to just recover directories. But it appears as your code is also doing these checks.

Just a word of caution about interop examples you find on the web; many of these were originally done in VB6 and may not have been properly modified for DotNet types.

Here are links to a reference and a tool that you may find useful in working with interop.

Miscellaneous Marshaling (look at the links under "See Also") Samples[^]
PInvoke Interop Assistant[^]

I was bored so I tried putting together a simple TreeView file explorer that bypasses the 260 character limit. It may serve as an example for what you are trying to do. Just place a TreeView control on new WinForm project and paste this code.

I used the command prompt "Subst" command to create a drive reference to a path that reached the 260 character barier and then added some more directories under that substituted drive to get to a file that had 400+ characters in it's path to test this. It appears to work.

Edit: There appears to be a length limit on codeblock code.

XML
Imports System.Runtime.InteropServices

Public Class Form1
   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      TreeView1.Nodes.Add(DriveToTreeNode("c"c))
   End Sub

   Private Sub TreeView1_AfterCollapse(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles TreeView1.AfterCollapse
      e.Node.Nodes.Clear()
      e.Node.Nodes.Add(New TreeNode With {.Tag = New NodeData With {.IsDirectory = True}}) ' add dummy node
   End Sub

   Private Sub TreeView1_BeforeExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) Handles TreeView1.BeforeExpand
      ' only directories can have nodes
      e.Node.Nodes.Clear()
      EnumerateDirectory(e.Node)
   End Sub

   Private Sub TreeView1_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TreeView1.MouseDoubleClick
      If e.Button = Windows.Forms.MouseButtons.Left Then
         Dim hti As TreeViewHitTestInfo = TreeView1.HitTest(TreeView1.PointToClient(MousePosition))
         If CType(hti.Node.Tag, NodeData).IsFile Then
            Dim fullpath As String = CType(hti.Node.Parent.Tag, NodeData).DirectoryPath & hti.Node.Text
            MessageBox.Show("Long Path: " & fullpath & vbCrLf & "Path Length: " & fullpath.Length.ToString() & _
                            vbCrLf & "Short Name: " & GetShortPathName(fullpath), _
                            hti.Node.Text, MessageBoxButtons.OK)
         End If
      End If
   End Sub

#Region "Drive Enumeration"
   Private Function DriveToTreeNode(ByVal drive As Char) As TreeNode
      Dim node As New TreeNode
      node.Text = drive.ToString().ToUpper()
      node.Tag = New NodeData With {.IsDirectory = True, .DirectoryPath = node.Text & ":\"}
      ' recursing all filedirectories would take too long for a drive
      ' so will handle it on each directory node expansion
      EnumerateDirectory(node, recursedirectories:=False)
      Return node
   End Function

   ' see: Naming Files, Paths, and Namespaces
   ' Maximum Path Length Limitation
   ' http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath
   Private Const LongPathPrefix As String = "\\?\"

   Private Sub EnumerateDirectory(ByVal node As TreeNode, Optional ByVal recursedirectories As Boolean = False)
      Dim path As String = CType(node.Tag, NodeData).DirectoryPath
      Dim findData As New WIN32_FIND_DATA

      Dim hFile As Microsoft.Win32.SafeHandles.SafeFileHandle
      hFile = FindFirstFile(LongPathPrefix & path & "*", findData)

      If hFile.DangerousGetHandle.ToInt32 = -1 Then
         ' just ignoring errors for this demo, most common is 5 - Access Denied
         ' System Error Codes
         ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381%28v=vs.85%29.aspx
         'Dim [error] As Int32 = Marshal.GetLastWin32Error()
         'Stop
         Return
      Else
         Do
            If IsFlagSet(findData.dwFileAttributes, IO.FileAttributes.Directory) Then
               If findData.cFileName = "." Then Continue Do
               If findData.cFileName = ".." Then Continue Do
               Dim subdir As New TreeNode With {.Text = findData.cFileName, _
                                                .Tag = New NodeData With {.IsDirectory = True, _
                                                                           .DirectoryPath = path & findData.cFileName & "\" _
                                                                         } _
                                               }
               If recursedirectories Then
                  EnumerateDirectory(subdir, recursedirectories)
               Else
                  ' need to add a dummy node to make it expandable
                  subdir.Nodes.Add(New TreeNode With {.Tag = New NodeData With {.IsDirectory = True}})
               End If
               node.Nodes.Add(subdir)
            Else ' Filename
               node.Nodes.Add(New TreeNode With {.Text = findData.cFileName, .Tag = New NodeData With {.IsDirectory = False}})
            End If

         Loop While FindNextFile(hFile, findData)

         FindClose(hFile) ' make sure to close the handle
      End If

   End Sub

   Private Function IsFlagSet(ByVal value As System.ValueType, ByVal testflag As System.ValueType) As Boolean
      Dim val As UInt32 = CType(value, UInt32)
      Dim flag As UInt32 = CType(testflag, UInt32)
      Return (val And flag) = flag
   End Function

   <DllImport("kernel32.dll", CharSet:=CharSet.Unicode, SetLastError:=True, EntryPoint:="FindFirstFile")> _
   Private Shared Function FindFirstFile(<System.Runtime.InteropServices.In(), MarshalAs(UnmanagedType.LPWStr)> ByVal fileName As String, _
                                         <[In](), Out(), MarshalAs(UnmanagedType.LPStruct)> ByVal data As WIN32_FIND_DATA) As Microsoft.Win32.SafeHandles.SafeFileHandle
   End Function

   <DllImport("kernel32.dll", CharSet:=CharSet.Unicode, SetLastError:=True, EntryPoint:="FindNextFileW")> _
   Private Shared Function FindNextFile(ByVal hFindFile As Microsoft.Win32.SafeHandles.SafeFileHandle, _
                                        <[In](), Out(), MarshalAs(UnmanagedType.LPStruct)> ByVal ByvallpFindFileData As WIN32_FIND_DATA) As Boolean
   End Function

   <DllImport("kernel32.dll")> _
   Private Shared Function FindClose(ByVal hFindFile As Microsoft.Win32.SafeHandles.SafeFileHandle) As Boolean
   End Function

   <DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
   Private Shared Function GetShortPathName(ByVal longPath As String, _
                                            <MarshalAs(UnmanagedType.LPTStr)> ByVal ShortPath As System.Text.StringBuilder, _
                                            <MarshalAs(Runtime.InteropServices.UnmanagedType.U4)> ByVal bufferSize As Integer) As Integer
   End Function


2nd part as text (sorry about format)

Private Shared Function GetShortPathName(ByVal path As String) As String
Dim sb As New System.Text.StringBuilder(1000)
Dim len As Int32 = GetShortPathName(LongPathPrefix & path, sb, sb.Capacity)
If len > sb.Capacity Then
sb.Capacity = len
GetShortPathName(LongPathPrefix & path, sb, sb.Capacity)
End If
Return sb.ToString().Replace(LongPathPrefix, "")
End Function

<structlayout(layoutkind.sequential, charset:="CharSet.Unicode)," bestfitmapping(false)=""> _
Private Class WIN32_FIND_DATA
<marshalas(unmanagedtype.u4)> Public dwFileAttributes As IO.FileAttributes
<marshalas(unmanagedtype.u4)> Private ftCreationTime_dwLowDateTime As UInt32
<marshalas(unmanagedtype.u4)> Private ftCreationTime_dwHighDateTime As UInt32
<marshalas(unmanagedtype.u4)> Private ftLastAccessTime_dwLowDateTime As UInt32
<marshalas(unmanagedtype.u4)> Private ftLastAccessTime_dwHighDateTime As UInt32
<marshalas(unmanagedtype.u4)> Private ftLastWriteTime_dwLowDateTime As UInt32
<marshalas(unmanagedtype.u4)> Private ftLastWriteTime_dwHighDateTime As UInt32
<marshalas(unmanagedtype.u4)> Public nFileSizeHigh As UInt32
<marshalas(unmanagedtype.u4)> Public nFileSizeLow As UInt32
<marshalas(unmanagedtype.u4)> Public dwReserved0 As UInt32
<marshalas(unmanagedtype.u4)> Public dwReserved1 As UInt32

' Note: by changing the cFileName size constant from 260 to 32767 to handle long
' path names, it appears that cAlternateFileName always returns blank
' if the ShortPathName is needed, then use the GetShortPathName function
' http://msdn.microsoft.com/en-us/library/windows/desktop/aa364989%28v=vs.85%29.aspx
<marshalas(unmanagedtype.byvaltstr, sizeconst:="32767)"> Public cFileName As String
<marshalas(unmanagedtype.byvaltstr, sizeconst:="14)"> Public cAlternateFileName As String

Public Function CreationTime() As DateTime
Return DateTime.FromFileTime(MergeToInt64(ftCreationTime_dwLowDateTime, ftCreationTime_dwHighDateTime))
End Function
Public Function LastAccessTime() As DateTime
Return DateTime.FromFileTime(MergeToInt64(ftLastAccessTime_dwLowDateTime, ftLastAccessTime_dwHighDateTime))
End Function
Public Function LastWriteTime() As DateTime
Return DateTime.FromFileTime(MergeToInt64(ftLastWriteTime_dwLowDateTime, ftLastWriteTime_dwHighDateTime))
End Function
Public Function FileSizeInBytes() As UInt64
Return (CULng(nFileSizeHigh) << 32) Or CULng(nFileSizeLow)
End Function

Private Function MergeToInt64(ByVal low As UInt32, ByVal high As UInt32) As Int64
Dim bighigh As UInt64 = high
bighigh <<= 32
bighigh = bighigh Or CULng(low)
Return CLng(bighigh)
End Function
End Class ' WIN32_FIND_DATA
#End Region

Private Class NodeData
Private _IsDirectory As Boolean
Public Property IsDirectory() As Boolean
Get
Return _IsDirectory
End Get
Set(ByVal value As Boolean)
_IsDirectory = value
End Set
End Property
Public ReadOnly Property IsFile() As Boolean
Get
Return Not IsDirectory
End Get
End Property
Public DirectoryPath As String
End Class 'NodeData

End Class

modified 6-Jun-13 0:59am.

GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 8:08
treddie6-Jun-13 8:08 
GeneralRe: FindFirstFileEx() and Unicode Pin
TnTinMn6-Jun-13 8:22
TnTinMn6-Jun-13 8:22 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 8:53
treddie6-Jun-13 8:53 
GeneralRe: FindFirstFileEx() and Unicode Pin
TnTinMn6-Jun-13 9:17
TnTinMn6-Jun-13 9:17 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 10:07
treddie6-Jun-13 10:07 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 14:29
treddie6-Jun-13 14:29 
GeneralRe: FindFirstFileEx() and Unicode Pin
TnTinMn6-Jun-13 15:06
TnTinMn6-Jun-13 15:06 
GeneralRe: FindFirstFileEx() and Unicode Pin
TnTinMn6-Jun-13 15:41
TnTinMn6-Jun-13 15:41 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 16:07
treddie6-Jun-13 16:07 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie6-Jun-13 18:04
treddie6-Jun-13 18:04 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie10-Jun-13 10:02
treddie10-Jun-13 10:02 
GeneralRe: FindFirstFileEx() and Unicode Pin
TnTinMn10-Jun-13 14:44
TnTinMn10-Jun-13 14:44 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie10-Jun-13 16:45
treddie10-Jun-13 16:45 
GeneralRe: FindFirstFileEx() and Unicode Pin
TnTinMn10-Jun-13 17:18
TnTinMn10-Jun-13 17:18 
GeneralRe: FindFirstFileEx() and Unicode Pin
treddie10-Jun-13 18:30
treddie10-Jun-13 18:30 
AnswerRe: FindFirstFileEx() and Unicode Pin
treddie11-Jun-13 13:22
treddie11-Jun-13 13:22 
QuestionApplication with calendar menu Pin
n3814-Jun-13 3:26
n3814-Jun-13 3:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.