Skip to content

Instantly share code, notes, and snippets.

@KOZ60
Last active June 10, 2024 14:47
Show Gist options
  • Save KOZ60/8ad4648d80737ff316579d595c9dad47 to your computer and use it in GitHub Desktop.
Save KOZ60/8ad4648d80737ff316579d595c9dad47 to your computer and use it in GitHub Desktop.
WideFileName.cs
using System;
using System.Runtime.InteropServices;
using static System.IO.Path;
/// <summary>
/// 32000 character filename
/// </summary>
/// <remarks>
/// When used as an argument to a Windows API, it will convert the filename to one that is 32K characters long.
/// You can also use the Win32FileName property as an argument to System.IO to support up to 32,000 characters.
/// </remarks>
public class WideFileName : SafeHandle,
IEquatable<WideFileName>,
IEquatable<string>,
IComparable<WideFileName>,
IComparable<string>
{
private const string PREFIX_NARROW_UNC = @"\\";
private const string PREFIX_WIDE_FILE = @"\\?\";
private const string PREFIX_WIDE_UNC = @"\\?\UNC\";
private const string RELATIVE_PATH_PARENT = @"..\";
private readonly string win32FileName;
private readonly string displayFileName;
private readonly GCHandle gcHandle;
private int? hashCode;
private WideFileName(string fileName)
: base(IntPtr.Zero, true) {
displayFileName = ToDisplayFileName(fileName);
win32FileName = ToWin32FileNameInternal(displayFileName);
gcHandle = GCHandle.Alloc(win32FileName, GCHandleType.Pinned);
if (win32FileName == null) {
SetHandle(IntPtr.Zero);
} else {
unsafe {
fixed (char* ptr = win32FileName) {
SetHandle(new IntPtr(ptr));
}
}
}
}
protected override bool ReleaseHandle() {
gcHandle.Free();
return true;
}
public override bool IsInvalid {
get {
return !gcHandle.IsAllocated;
}
}
public static implicit operator WideFileName(string value) {
return new WideFileName(value);
}
public static bool operator ==(WideFileName a, WideFileName b) {
return Compare(a, b) == 0;
}
public static bool operator !=(WideFileName a, WideFileName b) {
return Compare(a, b) != 0;
}
public string Win32FileName {
get {
return win32FileName;
}
}
public string DisplayFileName {
get {
return displayFileName;
}
}
public override int GetHashCode() {
if (!hashCode.HasValue) {
hashCode = displayFileName.ToUpper().GetHashCode();
}
return hashCode.Value;
}
public override bool Equals(object obj) {
if (obj is WideFileName other) {
return Equals(other);
}
if (obj is string str) {
return Equals(str);
}
return false;
}
public override string ToString() {
return displayFileName;
}
public bool Equals(WideFileName other) {
return Compare(this, other) == 0;
}
public bool Equals(string other) {
return Compare(this, new WideFileName(other)) == 0;
}
public int CompareTo(WideFileName other) {
return Compare(this, other);
}
public int CompareTo(string other) {
return Compare(this, new WideFileName(other));
}
public static int Compare(WideFileName x, WideFileName y) {
object ox = x;
object oy = y;
if (ReferenceEquals(ox, oy)) {
return 0;
}
if (ox == null) {
return -1;
}
if (oy == null) {
return 1;
}
// The comparison follows the Explorer's order.
return StrCmpLogicalW(x.displayFileName, y.displayFileName);
}
public static string ToDisplayFileName(string fileName) {
// null
if (fileName == null) {
return null;
}
// Replace '/' with '\'
fileName = fileName.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar);
// If the file name starts with "\\?\", it is a 32,000 character filename so delete it.
if (fileName.StartsWith(PREFIX_WIDE_FILE)) {
// The beginning is "\\?\UNC\"
if (fileName.StartsWith(PREFIX_WIDE_UNC)) {
fileName = PREFIX_NARROW_UNC + fileName.Substring(PREFIX_WIDE_UNC.Length);
} else {
fileName = fileName.Substring(PREFIX_WIDE_FILE.Length);
}
}
// Volume ex) C: → C:\
if (fileName.Length == 2 && fileName[1] == VolumeSeparatorChar) {
return fileName + DirectorySeparatorChar;
}
return GetFullPath(fileName);
}
public static string ToWin32FileName(string fileName) {
return ToWin32FileNameInternal(ToDisplayFileName(fileName));
}
internal static string ToWin32FileNameInternal(string displayName) {
// null
if (displayName == null) {
return null;
}
if (IsUncInternal(displayName)) {
// UNC
return PREFIX_WIDE_UNC + displayName.Substring(PREFIX_NARROW_UNC.Length);
} else {
// local file
return PREFIX_WIDE_FILE + displayName;
}
}
internal static bool IsUncInternal(string fileName) {
var uri = new Uri(fileName);
if (uri.IsUnc) {
return true;
}
return false;
}
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern int StrCmpLogicalW(string x, string y);
}
@KOZ60
Copy link
Author

KOZ60 commented Jun 10, 2024

How to use

WideFileName wf = "a.txt";
var fileinfo = new System.IO.FileInfo(wf.Win32FileName);

[DllImport(ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
public static extern SafeFileHandle CreateFile(
                WideFileName lpFileName,
                int dwDesiredAccess,
                FileShare dwShareMode,
                IntPtr securityAttrs,
                FileMode dwCreationDisposition,
                int dwFlagsAndAttributes,
                IntPtr hTemplateFile
    );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment