Search code examples
c#uwpkernel32

DllImport kernel32.dll code working in console app but not in UWP - FindFirstFileW returns INVALID_HANDLE_VALUE


I'm attempting to use low level Windows API's (specifically FindFirstFileW / fileapi.h) in a UWP app for the first time.

I have proof of concept code running successfully in a .Net console app, and now want to try it in a UWP app (personal hobby project). The import statement uses SetLastError = true:

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATAW lpFindFileData);

The UWP code compiles fine, and runs without throwing any error, but FindFirstFileW(lpFileName, out findData) returns INVALID_HANDLE_VALUE, whereas the console app returns file system data.

  • I am using the same folder path as the target in both applications.
  • https://learn.microsoft.com/en-us/uwp/win32-and-com/win32-apis seems to indicate that FindFirstFileW is available to be called.
  • My UWP app targets v 1803 (10.0; build 17134), min version Creators Update (10.0; build 15063).
  • Settings -> Privacy -> File system is on.

I have not yet added <rescap:Capability Name="broadFileSystemAccess" /> as described in https://learn.microsoft.com/en-us/windows/uwp/files/file-access-permissions#accessing-additional-locations because I get a warning:

The element 'Capabilities' in namespace 'http://schemas.microsoft.com/appx/manifest/foundation/windows10' has invalid child element 'Capability' in namespace 'http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities'.

Happy to provide more info.

Update - example project that replicates error:

Full NativeDirectoryScanner.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace FileAPITest
{
    public static class FILETIMEExtensions
    {
        public static DateTime ToDateTime(this System.Runtime.InteropServices.ComTypes.FILETIME filetime)
        {
            long highBits = filetime.dwHighDateTime;
            highBits = highBits << 32;
            return DateTime.FromFileTimeUtc(highBits + (long)filetime.dwLowDateTime);
        }
    }

    public class NativeDirectoryScanner
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATAW lpFindFileData);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData);

        [DllImport("kernel32.dll")]
        public static extern bool FindClose(IntPtr hFindFile);

        [DllImport("kernel32.dll")]
        static extern uint GetLastError();

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct WIN32_FIND_DATAW
        {
            public FileAttributes dwFileAttributes;
            internal System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
            internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
            internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
            public int nFileSizeHigh;
            public int nFileSizeLow;
            public int dwReserved0;
            public int dwReserved1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string cFileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
            public string cAlternateFileName;
        }

        public class Info
        {
            public bool IsDirectory;
            public string Path;
            public DateTime ModifiedDate;
            public DateTime CreatedDate;
        }

        public static List<Info> RecursiveScan2(string directory)
        {
            IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
            WIN32_FIND_DATAW findData;
            IntPtr findHandle = INVALID_HANDLE_VALUE;
            //FolderInfo folderInfo;
            string lpFileName = string.Empty;

            //var info = new List<FolderInfo>();
            var list = new List<Info>();
            try
            {
                //lpFileName = directory + @"\*";
                lpFileName = directory + "*";

                list.Add(new Info()
                {
                    CreatedDate = DateTime.Now,
                    ModifiedDate = DateTime.Now,
                    IsDirectory = false,
                    Path = string.Format("RecursiveScan2.lpFileName (80): {0}", lpFileName)
                });



                findHandle = FindFirstFileW(lpFileName, out findData);

                list.Add(new Info()
                {
                    CreatedDate = DateTime.Now,
                    ModifiedDate = DateTime.Now,
                    IsDirectory = false,
                    Path = string.Format("RecursiveScan2.GetLastError: {0}", GetLastError().ToString())
                });

                if (findHandle != INVALID_HANDLE_VALUE)
                {

                    do
                    {
                        if (findData.cFileName == "." || findData.cFileName == "..") continue;

                        string fullpath = directory + (directory.EndsWith("\\") ? "" : "\\") + findData.cFileName;

                        bool isDir = false;

                        if ((findData.dwFileAttributes & FileAttributes.Directory) != 0)
                        {
                            isDir = true;
                            list.AddRange(RecursiveScan2(fullpath));
                        }

                        list.Add(new Info()
                        {
                            CreatedDate = findData.ftCreationTime.ToDateTime(),
                            ModifiedDate = findData.ftLastWriteTime.ToDateTime(),
                            IsDirectory = isDir,
                            Path = fullpath
                        });
                    }
                    while (FindNextFile(findHandle, out findData));

                }
            }
            catch (Exception ex)
            {
                list.Add(new Info()
                {
                    CreatedDate = DateTime.Now,
                    ModifiedDate = DateTime.Now,
                    IsDirectory = false,
                    Path = string.Format("RecursiveScan2.lpFileName (131): {0}", lpFileName)
                });
                list.Add(new Info()
                {
                    CreatedDate = DateTime.Now,
                    ModifiedDate = DateTime.Now,
                    IsDirectory = false,
                    Path = string.Format("RecursiveScan2.Error: {0}", ex.Message)
                });
            }
            finally
            {
                if (findHandle != INVALID_HANDLE_VALUE) FindClose(findHandle);
            }
            return list;
        }


    }
}

===========================================

Xaml fragment:

<RelativePanel>
    <Button x:Name="btnGo" Content="Go" Margin="5" Tapped="BtnGo_Tapped" />
    <TextBox x:Name="txtInput" Margin="5" RelativePanel.RightOf="btnGo" RelativePanel.AlignRightWithPanel="True" Text="C:/" />
    <TextBlock x:Name="txtoutput" Margin="5,0,5,5" RelativePanel.Below="btnGo" RelativePanel.AlignLeftWithPanel="True" RelativePanel.AlignRightWithPanel="True" RelativePanel.AlignBottomWithPanel="True" />
</RelativePanel>

===========================================

xaml.cs

private void BtnGo_Tapped(object sender, TappedRoutedEventArgs e)
{
    StringBuilder sb = new StringBuilder();

    try
    {
        List<Info> list = RecursiveScan2(txtInput.Text);

        for(int i = 0; i < list.Count; i++)
        {
            sb.AppendFormat("Item: {2} - {1}{0}", Environment.NewLine, list[i].Path, i);
        }
    }
    catch(Exception ex)
    {
        sb.AppendFormat("{0}Error: {1}{0}Stacktrace: {2}", Environment.NewLine, ex.Message, ex.StackTrace);
    }

    txtoutput.Text = sb.ToString();
}

Solution

  • I tried the code, the code returns the same result as you mentioned. The document of FindFirstFileW mentions this API could be used in UWP apps but it will still be limited by the UWP apps as the UWP are running in the sandbox.

    If you are trying to find a file and its handle, there is another way that you could do that. You could get a brokered file HANDLE from a StorageFile with the IStorageItemHandleAccess interface or from a StorageFolder with the IStorageFolderHandleAccess interface. After you've got the handle, you should be able to use the handle in some win32 APIs to do what you want.

    Besides, if you don't mind using the desktop bridge, you could do it in the desktop apps, and then package it together with the UWP app. Use the UWP app to launch the desktop app which calls the win32 APIs to do the work.