I have code a function for get directory tree. I'm currently using OS Api for that. everthing is ok if using with DLLImport but Roslynator has told to me like this :
"Mark the method 'FindFirstFile' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time."
my code :
public partial class WindowsDirectoryTreeTraversal : IDisposable
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern nint FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FindNextFile(nint hFindFile, out WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FindClose(nint hFindFile);
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Dispose managed resources here, if any
}
// Release unmanaged resources (close opened directories, etc.)
// Note: Ensure you call closedir here for any remaining open directories
disposed = true;
}
}
private static bool IsArchive(WIN32_FIND_DATA findFileData)
{
return (findFileData.dwFileAttributes & FileAttributes.Archive) != 0;
}
public async Task<DirectoryNode> GetDirectoryTreeAsync(string path)
{
var directoryNode = new DirectoryNode(path, true);
var tasks = new List<Task>();
var stack = new Stack<(DirectoryNode node, string path)>();
stack.Push((directoryNode, path));
try
{
while (stack.Count > 0)
{
var (currentNode, currentPath) = stack.Pop();
tasks.Add(ProcessDirectoryAsync(currentNode, currentPath));
}
await Task.WhenAll(tasks);
}
catch (Exception e)
{
Console.WriteLine($"Error accessing {path}: {e.Message}");
}
return directoryNode;
}
private async Task ProcessDirectoryAsync(DirectoryNode currentNode, string currentPath)
{
IntPtr hFindFile = FindFirstFile(Path.Combine(currentPath, "*"), out WIN32_FIND_DATA findFileData);
if (hFindFile == IntPtr.Zero)
{
Console.WriteLine($"Error finding files in directory {currentPath}");
return;
}
try
{
do
{
string entryName = findFileData.cFileName;
if (entryName == "." || entryName == "..")
continue;
string entryPath = Path.Combine(currentPath, entryName);
if ((findFileData.dwFileAttributes & FileAttributes.Directory) != 0)
{
var subNode = new DirectoryNode(entryPath, true);
currentNode.Subitems.Add(subNode);
await ProcessDirectoryAsync(subNode, entryPath);
}
else if (IsArchive(findFileData) && ArchiveRegex().IsMatch(entryName[entryName.LastIndexOf(".")..]))
{
currentNode.Subitems.Add(new DirectoryNode(entryPath, false));
}
} while (FindNextFile(hFindFile, out findFileData));
}
catch (Exception e)
{
Console.WriteLine($"Error reading dir {currentPath}: {e.Message}");
}
finally
{
FindClose(hFindFile);
}
}
[GeneratedRegex("\\.(zip|tar|7z|rar|zipx)$")]
private static partial Regex ArchiveRegex();
}
}
but for example if i want to using LibaryImport it will get error like the image 
WIN32_FIND_DATA is in other class
public class AdditionalInfo
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WIN32_FIND_DATA
{
public FileAttributes dwFileAttributes;
public uint ftCreationTime_dwLowDateTime;
public uint ftCreationTime_dwHighDateTime;
public uint ftLastAccessTime_dwLowDateTime;
public uint ftLastAccessTime_dwHighDateTime;
public uint ftLastWriteTime_dwLowDateTime;
public uint ftLastWriteTime_dwHighDateTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
}
I just use LibraryImportAttribute for better performace
LibraryImport doesn't respect the [MarshalAs(UnmanagedType.ByValTStr)] on the WIN32_FIND_DATA, so you'd need to define a custom marshaller and put a [MarshalUsing(typeof(YourCustomMarshaller)] attribute on the lpFindFileData parameter. LibraryImport will only be able to marshal structs that are strictly "blittable" or ones with a custom marshaller that it can use to marshal the code to the native representation.
You could redefine your struct as the following or use it as the native representation in the marshaller
public unsafe struct WIN32_FIND_DATAW
{
public FileAttributes dwFileAttributes;
public uint ftCreationTime_dwLowDateTime;
public uint ftCreationTime_dwHighDateTime;
public uint ftLastAccessTime_dwLowDateTime;
public uint ftLastAccessTime_dwHighDateTime;
public uint ftLastWriteTime_dwLowDateTime;
public uint ftLastWriteTime_dwHighDateTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
public fixed ushort cFileName[256];
public fixed ushort cAlternateFileName[14];
}
You can use Utf16Marshaller to convert cFileName and cAlternateFileName to strings.
Also, as a sidenote DllImport will add an "A" or "W" to the method name called in Kernel32 depending on the CharSet you use, so make sure to change the name of the method to add the suffix, or set the EntryPoint property on the LibraryImport attribute.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With