I have the following function from a c++ header file:
__int16 __stdcall s_em4305_login (HANDLE m_hUSB, int DataRate, UCHAR * password);
When I run the VB equavelent:
Private Declare Function RF_EM4305_Login Lib "SRF32.dll" Alias "s_em4305_login" (ByVal handle As Long, ByVal DataRate As Long, ByRef bytes As Byte) As Integer
I get -1100 back as a value
When I run the c# equavilent:
[DllImport("SRF32.dll", EntryPoint = "s_em4305_login")]
private static extern ushort RF_EM4305_Login(IntPtr handle, int DataRate,byte[] password);
I get a different value. The 3rd paramater is most likely declared incorrectly. Can someone please assist in converting the c++ declaration to c#
Update
I have added some additional declarations
private static extern ushort RF_EM4305_Login(IntPtr handle, int DataRate,IntPtr password);
private static extern ushort RF_EM4305_Login(IntPtr handle, int DataRate,ref byte[] password);
With all 3 c# declarations I get the value "64436" returned.
If your unmanged code treats the password as a Null-terminated ansi string, this two should both work.
[DllImport("SRF32.dll", EntryPoint = "s_em4305_login", CharSet = CharSet.Ansi)]
private static extern short RF_EM4305_Login(IntPtr handle, int DataRate, string password);
[DllImport("SRF32.dll", EntryPoint = "s_em4305_login")]
private static extern short RF_EM4305_Login(IntPtr handle, int DataRate, [MarshalAs(UnmanagedType.LPStr)]string password);
And your C# declaration wasn't wrong either, because ushort 64436 equers short -1100 in memory.
c++ code is
MSIPC_SDK LONG __stdcall Ms_IpcClient_CaptureImage(LONG nUserId, char *sFilePath,
int nPathLen, const char *sDiskPath = NULL);//sDiskPath example: "C: \\".
Affect: Take a snapshoot
Parameters remark:
LONG nUserId: Ms_Ipc_Login()//Return value after login successfully
char *sFilePath: //destination for saving the recording files
int nPathLen: //the length of the path
const char *sDiskPath = NULL: //which disk to be saved
my c# code is:
[DllImport("MsIpcSDK", CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
public static extern int Ms_IpcClient_CaptureImage(
int lUserID,
[MarshalAs(UnmanagedType.LPStr)]
string sFilePath,
int nPathLen,
[MarshalAs(UnmanagedType.LPStr)]
string sDiskPath
);
and using is method:
var ret = Ms_IpcClient_CaptureImage(loginID, "C:\\a.bmp", 10000, "C:\\");
It is working in .Net Framework 2 but does not work in .Net Framework 4.
How can I fix it in .Net Framework 4?
sFilePath is used to pass a string from callee to caller. That's why the type is char* rather than const char*, and that's why there is a parameter for buffer length. That means you need to use StringBuilder rather than string. The p/invoke should be:
[DllImport("MsIpcSDK", CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
public static extern int Ms_IpcClient_CaptureImage(
int lUserID,
StringBuilder sFilePath,
int nPathLen,
string sDiskPath
);
And the call should be:
var filePath = new StringBuilder(260);
var ret = Ms_IpcClient_CaptureImage(loginID, filePath, filePath.Capacity, "C:\\");
Your code has always been wrong and you've just been getting away with it up until now. The fact that you passed a made up buffer length value of 10000 should have set the alarm bells ringing!
I have an API that takes three parameters:
HANDLE Connect(LPCSTR MachineName, LPCSTR ServerName, BOOL EnableDLLBuffering);
How can I use this method in C#?
What is the equivalence of LPCSTR? And what should be use in place of HANDLE?
The HANDLE equivalent is IntPtr (or you could use one of the subclasses of SafeHandle, many of which are defined in the namespace Microsoft.Win32.SafeHandles). The equivalent of LPCSTR is string or StringBuilder (but string is better, because you are passing the string to the method and the method won't modify it). You can even use a byte[] (as I have wrote you in the other response, but you must encode your string in the buffer, and add a \0 at the end... it's quite inconvenient). In the end an LPCSTR is a constant LPSTR that the method won't modify. It's better you set the CharSet.Ansi as in the other response.
[DllImport("YourDll.dll", CharSet = CharSet.Ansi)]
static extern IntPtr Connect(string machineName, string serverName, bool enableDLLBuffering);
and you call it as:
IntPtr ptr = Connect("MyMachine", "MyServer", true);
or, if you really want to do the encoding yourself:
[DllImport("YourDll.dll", CharSet = CharSet.Ansi)]
static extern IntPtr Connect(byte[] machineName, byte[] serverName, bool enableDLLBuffering);
and
public static byte[] GetBytesFromStringWithZero(Encoding encoding, string str)
{
int len = encoding.GetByteCount(str);
// Here we leave a "space" for the ending \0
// Note the trick to discover the length of the \0 in the encoding:
// It could be 1 (for Ansi, Utf8, ...), 2 (for Unicode, UnicodeBE), 4 (for UTF32)
// We simply ask the encoder how long it would be to encode it :-)
byte[] bytes = new byte[len + encoding.GetByteCount("\0")];
encoding.GetBytes(str, 0, str.Length, bytes, 0);
return bytes;
}
IntPtr ptr = Connect(
GetBytesFromStringWithZero(Encoding.Default, "MyMachine"),
GetBytesFromStringWithZero(Encoding.Default, "MyServer"),
true);
This variant is better if you have to call the method many many times always with the same strings, because you can cache the encoded versions of the string and gain something in speed (yes, normally it's an useless optimization)
According to How to map Win32 types to C# types when using P/Invoke?:
LPCSTR (C) - string (C#)
HANDLE (C) - IntPtr (C#)
I use StringBuilder:
[DllImport("user32.dll", CharSet = CharSet.Ansi)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
var sb = new StringBuilder();
var ret = GetClassName(hwnd, sb, 100);
var klass = sb.ToString();
Quick question (hopefully), how do I properly call the win32 function CreateProfile() from C# (managed code)? I have tried to devise a solution on my own with no avail.
The syntax for CreateProfile() is:
HRESULT WINAPI CreateProfile(
__in LPCWSTR pszUserSid,
__in LPCWSTR pszUserName,
__out LPWSTR pszProfilePath,
__in DWORD cchProfilePath
);
The supporting documents can be found in the MSDN library.
The code I have so far is posted below.
DLL Import:
[DllImport("userenv.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int CreateProfile(
[MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,
[MarshalAs(UnmanagedType.LPWStr)] string pszUserName,
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath,
uint cchProfilePath);
Invoking the function:
/* Assume that a user has been created using: net user TestUser password /ADD */
// Get the SID for the user TestUser
NTAccount acct = new NTAccount("TestUser");
SecurityIdentifier si = (SecurityIdentifier)acct.Translate(typeof(SecurityIdentifier));
String sidString = si.ToString();
// Create string buffer
StringBuilder pathBuf = new StringBuilder(260);
uint pathLen = (uint)pathBuf.Capacity;
// Invoke function
int result = CreateProfile(sidString, "TestUser", pathBuf, pathLen);
The problem is that no user profile is ever created and CreateProfile() returns an error code of: 0x800706f7. Any helpful information on this matter is more than welcomed.
Thanks,
-Sean
Update:
Solved!
string buffer for pszProfilePath cannot have a length greater than 260.
For the out parameter you should set the marshalling. More importantly, by passing a StringBuilder you already implicitly have an output parameter. Thus, it should become:
[DllImport("userenv.dll", CharSet = CharSet.Auto)]
private static extern int CreateProfile(
[MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,
[MarshalAs(UnmanagedType.LPWStr)] string pszUserName,
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath,
uint cchProfilePath);
Calling this method:
int MAX_PATH = 260;
StringBuilder pathBuf = new StringBuilder(MAX_PATH);
uint pathLen = (uint)pathBuf.Capacity;
int result = CreateProfile(sidString, "TestUser", pathBuf, pathLen);
It may not be the only problem but you need to add the [Out] attribute to the pszProfilePath parameter in your DLL import declaration.
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.