I'm using C# for WinForms app in VS2010 and I needed to create a directory in which the path was too large for the .NET methods (248 char limit, I believe) and ran across suggestions from google to use the Unicode Win32 CreateDirectory(). I had initially tried calling it using Unicode and passed parameters but after several failed attempts, I've reduced the code and am using EXACTLY the code found here:
http://www.pinvoke.net/default.aspx/Structures/SECURITY_ATTRIBUTES.html
I am still getting the same error:
System.AccessViolationException was caught
Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Admittedly, I don't know anything about calling the Win32 functions, I'm really just pulling what I can find online and trying to learn. Can anyone tell me what I'm doing wrong? Removing non-essential code for the question, I have:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
namespace RFCGenerator
{
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public class RFC
{
[DllImport("kernel32.dll")]
static extern bool CreateDirectory(string lpPathName, SECURITY_ATTRIBUTES lpSecurityAttributes);
protected void CopyDirectory(Uri Source, Uri Destination)
{
SECURITY_ATTRIBUTES lpSecurityAttributes = new SECURITY_ATTRIBUTES();
DirectorySecurity security = new DirectorySecurity();
lpSecurityAttributes.nLength = Marshal.SizeOf(lpSecurityAttributes);
byte[] src = security.GetSecurityDescriptorBinaryForm();
IntPtr dest = Marshal.AllocHGlobal(src.Length);
Marshal.Copy(src, 0, dest, src.Length);
lpSecurityAttributes.lpSecurityDescriptor = dest;
string path = #"C:\Test";
CreateDirectory(path, lpSecurityAttributes);
}
}
}
UPDATE: using Hans' suggestion, I did get this to work locally. However, when I attempt to create a directory using a UNC address, such as passing in:
path = #"\\mydomain.com\foo\bar\newfolder"
I now get:
System.ComponentModel.Win32Exception was caught
Message=The filename, directory name, or volume label syntax is incorrect
I have verified that \\mydomain.com\foo\bar\ does exist.
SOLUTION:
Using Hans' code and a minor modification to check if it's UNC path (reference: http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx, under "Maximum Path Length Limitation"):
string UnicodePath = (path.StartsWith(#"\\")) ? #"\\?\UNC\" + (path.Remove(0, 2)) : #"\\?\" + path;
if (!CreateDirectory(UnicodePath, IntPtr.Zero))
throw new System.ComponentModel.Win32Exception();
You are not using the Unicode version, that requires CharSet = CharSet.Unicode in the [DllImport] declaration. Furthermore, creating directories with long names has nothing to do with the security attribute. You have to prefix the name with #"\\?\". Thus:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);
...
if (!CreateDirectory(#"\\?\" + path, IntPtr.Zero))
throw new Win32Exception();
Related
I am using Hybridizer for the first time. I am using Windows 10, Visual Studio 2019, CUDA 10.1 and Hybridizer 1.3.0 "Released Sep. 5th 2019". Although I have followed their steps, I keep getting the same error:
Dll load error when loading Hello_World_CUDA.dll: 126
whenever I try to make any simple code to test it like this:
using System;
using Hybridizer.Runtime.CUDAImports;
public class Hello_World
{
[EntryPoint]
public static void Hello()
{
Console.Out.Write("Hello from GPU");
}
static void Main()
{
cuda.DeviceSynchronize();
HybRunner runner = HybRunner.Cuda().SetDistrib(1, 2);
runner.Wrap(new Hello_World()).Hello();
}
}
even when using their examples without any change.
How can I solve this problem?
To troubleshoot please follow the three following steps:
Check file exists and can be loaded from current directory
Make sure dll can be loaded using LoadLibrary
Verify symbol can be found using GetProcAddress
To facilitate your investigation, please find below code to run these assertions
using System;
using System.IO;
using System.Runtime.InteropServices;
using Hybridizer.Runtime.CUDAImports;
public class Hello_World
{
[EntryPoint]
public static void Hello()
{
Console.Out.Write("Hello from GPU");
}
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryA", SetLastError = true)]
static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string name);
[DllImport("kernel32.dll", EntryPoint = "FormatMessage", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int FormatMessage(int dwFlags, IntPtr lpSource, int dwMessageId, int dwLanguageId, [MarshalAs(UnmanagedType.LPArray)] char[] data, uint dwSize, IntPtr args);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress", SetLastError = true, CharSet = CharSet.Ansi)]
static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string symbol);
static unsafe string ErrorToString(int er)
{
char[] buffer = new char[2048];
int res = FormatMessage(0x00001000, // FORMAT_MESSAGE_FROM_SYSTEM
IntPtr.Zero, er,
0x0409, // US language -- in case of issue, replace with 0
buffer, 2048, IntPtr.Zero);
if (res == 0)
throw new ApplicationException(string.Format("Cannot format message - Error : {0}", res));
string resstring;
fixed (char* ptr = &buffer[0])
{
resstring = new string(ptr);
}
return resstring;
}
static void Main()
{
// Trouble-shooting
// print execution directory
Console.Out.WriteLine("Current directory : {0}", Environment.CurrentDirectory);
Console.Out.WriteLine("Size of IntPtr = {0}", Marshal.SizeOf(IntPtr.Zero));
// first, make sure file exists
string path = #"Troubleshooting_CUDA.dll"; // replace with actual dll name - you can read that on the output of the build
if (!File.Exists(path))
{
Console.Out.WriteLine("Dll could not be found in path, please verify dll is located in the appropriate directory that LoadLibrary may find it");
Environment.Exit(1);
}
// make sure it can be loaded -- open DLL in depends to missing troubleshoot dependencies (may be long to load)
IntPtr lib = LoadLibrary(path);
if (lib == IntPtr.Zero)
{
int code = Marshal.GetLastWin32Error();
string er = ErrorToString(code);
Console.Out.WriteLine("Dll could not be loaded : {0}", er);
Environment.Exit(2);
}
// finally try to get the proc address -- open DLL in depends to see list of symbols (may be long to load)
IntPtr procAddress = GetProcAddress(lib, "Hello_Worldx46Hello_ExternCWrapper_CUDA");
if (procAddress == IntPtr.Zero)
{
int code = Marshal.GetLastWin32Error();
string er = ErrorToString(code);
Console.Out.WriteLine("Could not find symbol in dll : {0}", er);
Environment.Exit(3);
}
cuda.DeviceSynchronize();
HybRunner runner = HybRunner.Cuda().SetDistrib(1, 2);
runner.Wrap(new Hello_World()).Hello();
}
}
If it fails at first step, you may need to change output directory of the CUDA satellite project, and/or execution directory of your application.
Should it fail at second step, you want to verify you execute in x64 (size of IntPtr should be 8), and that dll loads. Dependency walker, you may find here, is a great tool for this purpose, you want the x64 version. NOTE: loading may be very long.
Should it fail at third step, look-up the symbol name with depends, maybe your CUDA satellite project is not up to date.
I'm attempting to write a simple program that would take a CSV consisting of two columns, and given a keyword from the first column, return the corresponding value from the second one. The problem is that I need the CSV to be in an alternate data stream to make the program as portable as possible (I want to make it so that when the user drops a CSV file on the executable, the CSV is overwritten). That is why I'm trying to use WinAPI's CreateFile function -- .NET doesn't support alternate data streams. Unfortunately, I'm failing miserably.
In the current state, the program is supposed to read a file named test.csv. I want to do CreateFile on it, convert the intPtr handle to a SafeFileHandle and then pass the SafeFileHandle to a FileStream constructor. The best I've been able to achieve is an empty stream though. It doesn't seem to me that the program is actually getting the right handle. When I try "CREATE_ALWAYS" or "CREATE_NEW" instead of "OPEN_ALWAYS", I'm getting an "Invalid Handle" error, no matter what I do with the rest of the parameters. With "OPEN_ALWAYS", when I check the value of "stream.Name", I'm getting "Unknown".
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Searcher
{
public partial class Searcher : Form
{
public static List<string> keywords;
public static List<string> values;
public Searcher()
{
InitializeComponent();
//byte[] path = Encoding.UTF8.GetBytes("C:\\Users\\as\\Documents\\test.csv");
SafeFileHandle safeADSHandle = NativeMethods.CreateFileW("test.csv",
NativeConstants.GENERIC_READ,
NativeConstants.FILE_SHARE_READ,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
//var safeADSHandle = new SafeFileHandle(handle, true);
if (safeADSHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
var stream = new FileStream(safeADSHandle, FileAccess.Read);
MessageBox.Show(stream.Name);
var reader = new StreamReader(stream);
Searcher.keywords = new List<string>();
Searcher.values = new List<string>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
Searcher.keywords.Add(values[0]);
Searcher.values.Add(values[1]);
}
cbKeyword.DataSource = Searcher.keywords;
cbKeyword.AutoCompleteSource = AutoCompleteSource.ListItems;
}
private void btnSearch_Click(object sender, EventArgs e)
{
tbResult.Text = Searcher.values[cbKeyword.SelectedIndex];
}
}
}
public partial class NativeMethods
{
[DllImportAttribute("kernel32.dll", SetLastError = true, EntryPoint = "CreateFile")]
public static extern SafeFileHandle CreateFileW(
[InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
}
public partial class NativeConstants
{
/// GENERIC_WRITE -> (0x40000000L)
public const int GENERIC_WRITE = 1073741824;
public const uint GENERIC_READ = 2147483648;
/// FILE_SHARE_DELETE -> 0x00000004
public const int FILE_SHARE_DELETE = 4;
/// FILE_SHARE_WRITE -> 0x00000002
public const int FILE_SHARE_WRITE = 2;
/// FILE_SHARE_READ -> 0x00000001
public const int FILE_SHARE_READ = 1;
/// OPEN_ALWAYS -> 4
public const int OPEN_ALWAYS = 4;
public const int CREATE_NEW = 1;
}
Edit I've changed the code above slightly to reflect the changes after the comments. Now I'm not converting from IntPtr to SafeFileHandle. One thing to mention perhaps is that I before this change I tried reading the value of handle.ToString() to see if it was changing and it was -- it's a random number.
The reason this is not working is because you have specified an entry point without a character set. When you don't specify a character set in DllImport, the default is CharSet.Ansi, which means platform invoke will search for the function as follows:
First for an unmangled exported function, i.e. the name CreateFile from the entry point that you specified (which does not exist in kernel32.dll)
Next if the unmangled name was not found, it will search for the mangled name based on the charset, so it will search for CreateFileA
So it will find the CreateFileA exported function, which assumes any strings passed in to it are in 1-byte character ANSI format. However, you are marshalling the string as a wide string. Note that the wide-character version of the function is called CreateFileW (this distinction between ANSI and wide character versions of functions is common in the Windows API).
To fix this you just need to make sure that the marshalling of your parameters matches up with what the function you are importing expects. So you could remove the EntryPoint field, in which case it will use the C# method name to find the exported function instead (so it will find CreateFileW).
However to make it even clearer, I would write your platform invoke code as follows:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
I took this from the pinvoke.net website, which should be your go-to for writing pinvoke code rather than trying to bash it out yourself (especially if you aren't familiar with the Windows API or marshalling :).
The reason there was no error is probably because CreateFile was creating the file since it wouldn't have been able to find it.
The file name will appear as "[Unknown]" though. I suspect this is because the code to get a file name from a handle is non-trivial.
I having trouble of copying some folder 260+ chars (for example: F:\NNNNNNNNNNNNNNNN\NNNNNNNNNNN\ROOT\$RECYCLE.BIN\S-1-5-21-3299053755-4209892151-505108915-1000\$RMSL3U8\NNNNNNNNN NNNNNNNN\NNNNNNNNNNN\NNNNNNNNNN\NNNNNNNNNN\publish\Application Files\TNNNNNNNNNNNN_1_0_0_0\NNNNNNNNNNNN.exe.manifest) to some other place with standart DrectoryInfo.Create(); adding \?\ or \?\UNC\ (like "\\?\UNC\") just throw another ArgumentException.
What am i doing wrong? What else i can do without using Directory.SetCurrentDirectory() ?
Actually you need to call win32 from c#. We have done this
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public static class LongPath
{
static class Win32Native
{
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr pSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateDirectory(string lpPathName, SECURITY_ATTRIBUTES lpSecurityAttributes);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
}
public static bool CreateDirectory(string path)
{
return Win32Native.CreateDirectory(String.Concat(#"\\?\", path), null);
}
public static FileStream Open(string path, FileMode mode, FileAccess access)
{
SafeFileHandle handle = Win32Native.CreateFile(String.Concat(#"\\?\", path), (int)0x10000000, FileShare.None, null, mode, (int)0x00000080, IntPtr.Zero);
if (handle.IsInvalid)
{
throw new System.ComponentModel.Win32Exception();
}
return new FileStream(handle, access);
}
}
A sample code:
string path = #"c:\".PadRight(255, 'a');
LongPath.CreateDirectory(path);
path = String.Concat(path, #"\", "".PadRight(255, 'a'));
LongPath.CreateDirectory(path);
string filename = Path.Combine(path, "test.txt");
FileStream fs = LongPath.Open(filename, FileMode.CreateNew, FileAccess.Write);
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine("abc");
}
There's a great library on Microsoft TechNet for overcoming the long filenames problem, it's called Delimon.Win32.IO Library (V4.0) and it has its own versions of key methods from System.IO
For example, you would replace:
System.IO.Directory.GetFiles
with
Delimon.Win32.IO.Directory.GetFiles
which will let you handle long files and folders.
From the website:
Delimon.Win32.IO replaces basic file functions of System.IO and
supports File & Folder names up to up to 32,767 Characters.
This Library is written on .NET Framework 4.0 and can be used either
on x86 & x64 systems. The File & Folder limitations of the standard
System.IO namespace can work with files that have 260 characters in a
filename and 240 characters in a folder name (MAX_PATH is usually
configured as 260 characters). Typically you run into the
System.IO.PathTooLongException Error with the Standard .NET Library.
Yes, using the standard APIs will give you this kind of limitations (255 chars IIRC).
From .NET you can use the AlphaFS project which lets you use very long paths (using the "\\?\" style) and mimics the System.IO namespace.
You will probably be able to use this library just as if you were using System.IO, for example : AlphaFS.Win32.Filesystem.File.Copy() instead System.IO.File.Copy()
If you don't want or cannot use AlphaFS you'll have to pinvoke the Win32 API
I have few images embedded in my executable in resource section.
I followed these steps to create my executable:
Generated .resx file for all the images (.jpg) in a directory using some utility. The images are named image1.jpg, image2.jpg and so on.
created .resources file from .resx file using: resgen myResource.resx
Embedded the generated .resource file using /res flag as: csc file.cs /res:myResource.resources
4 I am accessing these images as:
ResourceManager resources = new ResourceManager("myResource", Assembly.GetExecutingAssembly());
Image foo = (System.Drawing.Image)(resources.GetObject("image1"));
This all is working fine as expected. Now I want to change embedded images to some new images. This is what I am currently doing:
class foo
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, string wLanguage, Byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static void Main(string[] args)
{
IntPtr handle = BeginUpdateResource(args[0], false);
if (handle.ToInt32() == 0)
throw new Exception("File Not Found: " + fileName + " last err: " + Marshal.GetLastWin32Error());
byte[] imgData = File.ReadAllBytes("SetupImage1.jpg");
int fileSize = imgData.Length;
Console.WriteLine("Updaing resources");
if (UpdateResource(handle, "Image", "image1", "image1", imgData, (uint)fileSize))
{
EndUpdateResource(handle, false);
Console.WriteLine("Update successfully");
}
else
{
Console.WriteLine("Failed to update resource. err: {0}", Marshal.GetLastWin32Error());
}
}
}
The above code is adding a new resource for the specified image (inside IMAGE title with some random number, as seen in Resource hacker), but I want to modify the existing resource data for image1.
I am sure that I am calling UpdateResource with some invalid arguments.
Could any one help pointing that out?
I am using .NET version 2
Thank you,
Vikram
I think you are making a confusion between .NET resources, and Win32 resources. The resources you add embedding with the /res argument to csc.exe are .NET resources that you can successfully read using you ResourceManager snippet code.
Win32 resources are another beast, that is not much "compatible" with the managed .NET world in general, athough you can indeed add them to a .NET exe using the /win32Res argument - note the subtle difference :-)
Now, if you want to modify embedded .NET resources, I don't think there are classes to do it in the framework itself, however you can use the Mono.Cecil library instead. There is an example that demonstrates this here: C# – How to edit resource of an assembly?
And if you want to modify embedded Win32 resources, you code needs some fixes, here is a slightly modified version of it, the most important difference lies in the declaration of UpdateResource:
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, short wLanguage, byte[] lpData, int cbData);
[DllImport("kernel32.dll")]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static void Main(string[] args)
{
IntPtr handle = BeginUpdateResource(args[0], false);
if (handle == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error()); // this will automatically throw an error with the appropriate human readable message
try
{
byte[] imgData = File.ReadAllBytes("SetupImage1.jpg");
if (!UpdateResource(handle, "Image", "image1", (short)CultureInfo.CurrentUICulture.LCID, imgData, imgData.Length))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
EndUpdateResource(handle, false);
}
}
This is impossible. You cant modify compiled file that you are running.
I believe you can add new images in run time but can't update a resource that is essentially just held in memory.
If you add a resource in run time, it exists but I don't think it is compiled and therefore I don't think it is accessible to you.
Is there a reason you aren't using content instead?
I want to write a program that shows the files of another drive with hard links.
I want to keep both hardlinks consistent in filename and other things, so I have to get a function/method where I can list all current hard links of a file.
For example:
I have a file C:\file.txt and a second hard link to D:\file.txt.
Then I rename D:\file.txt to D:\file_new.txt.
I now want to be able to also rename the hardlink on the C drive as well.
So I need a function which returns for D:\file_new.txt that there are the following hardlinks:
C:\file.txt
D:\file_new.txt
then I can rename the hard link on C:\ also to get D:\file_new.txt
So I need to get all hard links of a physical file.
Or: All hard links of a file addressed with a hard link.
Hope somebody can help!
Edit:
Oliver noticed that hard links can't be used on different disks. thanks... So I extend the question to: What do I need? Junction Points? Symbolic Links? It should also work with files not only with folders!
the following code should work well (originally postet by Peter provost on PowerShell Code Repository):
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
namespace HardLinkEnumerator
{
public static class Kernel32Api
{
[StructLayout(LayoutKind.Sequential)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
string lpFileName,
[MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
IntPtr lpSecurityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetFileInformationByHandle(SafeFileHandle handle, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(SafeHandle hObject);
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
static extern IntPtr FindFirstFileNameW(
string lpFileName,
uint dwFlags,
ref uint stringLength,
StringBuilder fileName);
[DllImport("kernel32.dll", SetLastError = true, CharSet=CharSet.Unicode)]
static extern bool FindNextFileNameW(
IntPtr hFindStream,
ref uint stringLength,
StringBuilder fileName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr fFindHandle);
[DllImport("kernel32.dll")]
static extern bool GetVolumePathName(string lpszFileName,
[Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore);
public static int GetFileLinkCount(string filepath)
{
int result = 0;
SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION();
if (GetFileInformationByHandle(handle, out fileInfo))
result = (int)fileInfo.NumberOfLinks;
CloseHandle(handle);
return result;
}
public static string[] GetFileSiblingHardLinks(string filepath)
{
List<string> result = new List<string>();
uint stringLength = 256;
StringBuilder sb = new StringBuilder(256);
GetVolumePathName(filepath, sb, stringLength);
string volume = sb.ToString();
sb.Length = 0; stringLength = 256;
IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb);
if (findHandle.ToInt32() != -1)
{
do
{
StringBuilder pathSb = new StringBuilder(volume, 256);
PathAppend(pathSb, sb.ToString());
result.Add(pathSb.ToString());
sb.Length = 0; stringLength = 256;
} while (FindNextFileNameW(findHandle, ref stringLength, sb));
FindClose(findHandle);
return result.ToArray();
}
return null;
}
}
}
Maybe i misunderstand your questions, but hardlinks can't go from one drive to another. They can only exist on a single drive.
Within the .Net framwork there is no support to get these informations. But the Win32 API can provide you with these informations.
Take a look at this article. It may help you.
Update
As far as i know it is not possible to do it between different drives. Junction Points are definitely not your friend cause it only works on foldes. But after reading this wikipedia article it seems that you can do it on Vista and Win7 with symbolic links. There is also a link to this shell extension which seems to cover everything you can do with these NTFS special features. Maybe with this you can check if your goal is reachable and maybe afterwards check the MSDN for the desired Win32 API function.
Note:
Hard links can only be files on the same volume, which contradicts the requirements of the question, which led to a follow-up question in the question body that the OP himself answered.
Given the title of the question, however, users who find this post by googling are most likely interest in a solution to the problem as stated in the title: given a file, how can I find all hard links to it (which are by definition all on the same volume).
The solution below is a streamlined and modernized adaptation of Marcel Nolte's helpful answer.
Its behavior and constraints are:
For a given input file, its array of hard links is returned, as full file paths, which includes the input file's path itself.
If the file has only one hard link (itself), or you specify a directory, only that file's / directory's full path is returned.
If the path refers to a volume that doesn't support hard links, or the path doesn't exist, null is returned.
NiKiZe notes that you cannot query hardlinks via a CIFS/SMB connection (network drive).
The following is a self-contained Windows console application that you should be able to compile and run as-is; the method of interest is HardLinkHelper.GetHardLinks():
using System;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace demo
{
public static class Program
{
public static void Main()
{
// Sample file that is known to have (one) hard link.
var file = Environment.ExpandEnvironmentVariables(#"%SYSTEMROOT%\explorer.exe");
foreach (var link in HardLinkHelper.GetHardLinks(file) ?? new string[] { "n/a" })
{
Console.WriteLine(link);
}
}
}
public static class HardLinkHelper
{
#region WinAPI P/Invoke declarations
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, StringBuilder LinkName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, StringBuilder LinkName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
public const int MAX_PATH = 65535; // Max. NTFS path length.
#endregion
/// <summary>
//// Returns the enumeration of hardlinks for the given *file* as full file paths, which includes
/// the input path itself.
/// </summary>
/// <remarks>
/// If the file has only one hardlink (itself), or you specify a directory, only that
/// file's / directory's full path is returned.
/// If the path refers to a volume that doesn't support hardlinks, or the path
/// doesn't exist, null is returned.
/// </remarks>
public static string[] GetHardLinks(string filepath)
{
StringBuilder sbPath = new StringBuilder(MAX_PATH);
uint charCount = (uint)sbPath.Capacity; // in/out character-count variable for the WinAPI calls.
// Get the volume (drive) part of the target file's full path (e.g., #"C:\")
GetVolumePathName(filepath, sbPath, (uint)sbPath.Capacity);
string volume = sbPath.ToString();
// Trim the trailing "\" from the volume path, to enable simple concatenation
// with the volume-relative paths returned by the FindFirstFileNameW() and FindFirstFileNameW() functions,
// which have a leading "\"
volume = volume.Substring(0, volume.Length - 1);
// Loop over and collect all hard links as their full paths.
IntPtr findHandle;
if (INVALID_HANDLE_VALUE == (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) return null;
List<string> links = new List<string>();
do
{
links.Add(volume + sbPath.ToString()); // Add the full path to the result list.
charCount = (uint)sbPath.Capacity; // Prepare for the next FindNextFileNameW() call.
} while (FindNextFileNameW(findHandle, ref charCount, sbPath));
FindClose(findHandle);
return links.ToArray();
}
}
}
I found a solution:
First I don't have to use hard links (since they can't point to an other disk). I have to use symbolic links instead. So I have one hard linked file on the original disk and symbolic links on other disks to this file. The limitation is OS must be Vista or newer.
Second I have to be able to find out where the symbolic link is pointing to.
Here I found a good example how to find out the information I need:
http://www.codeproject.com/KB/vista/ReparsePointID.aspx
The only thing I don't managed is to find all symbolic links from a specific file (hard link). I guess there is no out of the box solution and I have to recurse all symbolic links and test the target. But in my case that's no problem.
I hope that can help others!
I used the HardLinkHelper class in a project, and it works great for finding hard links on Windows NTFS drives.
Here's my version of HardLinkHelper class with the following changes:
Does not use StringBuilder, because Microsoft recommends to avoid using StringBuilder on pinvokes.
It has the member variables (INVALID_HANDLE_VALUE & MAX_PATH) private.
Null is never returned, and instead empty list is returned for non-existing path or unsupported path. This allows safe use of foreach on return value.
Added ReturnEmptyListIfOnlyOne input variable which when set to true, allows calling function to use it in a foreach, where the foreach loop will only be entered if the file has multiple shared hard links.
Example usage:
foreach (var link in HardLinkHelper.GetHardLinks(entry.Path, true))
public static class HardLinkHelper {
#region WinAPI P/Invoke declarations
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, char[] LinkName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, char[] LinkName);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FindClose(IntPtr hFindFile);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetVolumePathName(string lpszFileName, [Out] char[] lpszVolumePathName, uint cchBufferLength);
private static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff;
private const int MAX_PATH = 65535; // Max. NTFS path length.
#endregion
/// <summary>
/// Checks for hard links on a Windows NTFS drive associated with the given path.
/// </summary>
/// <param name="filepath">Fully qualified path of the file to check for shared hard links</param>
/// <param name="ReturnEmptyListIfOnlyOne">Set true, to return populated list only for files having multiple hard links</param>
/// <returns>
/// Empty list is returned for non-existing path or unsupported path.
/// Single hard link paths returns empty list if ReturnEmptyListIfOnlyOne is true. If false, returns single item list.
/// For multiple shared hard links, returns list of all the shared hard links.
/// </returns>
public static List<string> GetHardLinks(string filepath, bool ReturnEmptyListIfOnlyOne = false) {
List<string> links = new List<string>();
try {
Char[] sbPath = new Char[MAX_PATH + 1];
uint charCount = (uint)MAX_PATH;
GetVolumePathName(filepath, sbPath, (uint)MAX_PATH); // Must use GetVolumePathName, because Path.GetPathRoot fails on a mounted drive on an empty folder.
string volume = new string(sbPath).Trim('\0');
volume = volume.Substring(0, volume.Length - 1);
Array.Clear(sbPath, 0, MAX_PATH); // Reset the array because these API's can leave garbage at the end of the buffer.
IntPtr findHandle;
if (INVALID_HANDLE_VALUE != (findHandle = FindFirstFileNameW(filepath, 0, ref charCount, sbPath))) {
do {
links.Add((volume + new string(sbPath)).Trim('\0')); // Add the full path to the result list.
charCount = (uint)MAX_PATH;
Array.Clear(sbPath, 0, MAX_PATH);
} while (FindNextFileNameW(findHandle, ref charCount, sbPath));
FindClose(findHandle);
}
}
catch (Exception ex) {
//Logger.Instance.Info($"GetHardLinks: Exception, file: {filepath}, reason: {ex.Message}, stacktrace {ex.StackTrace}");
}
if (ReturnEmptyListIfOnlyOne && links.Count < 2)
links.Clear();
return links;
}
}
try:
using System.IO;
string[] filePathsC = Directory.GetFiles(#"c:\");
string[] filePathsD = Directory.GetFiles(#"d:\");
and loop through the arrays, find the files and change the name
EDIT:
By reading the comments I know that I answered before I knew what a hardlink is. I realise now that this answer is not helping.