I've a problem trying to call SetupDiGetDeviceInterfaceDetail from C#. It always returns 1784 error code ("The supplied user buffer is not valid for the requested operation"). This is my C# code:
Guid GUID_DEVINTERFACE_DFU = new Guid(0x3fe809ab, 0xfb91, 0x4cb5, 0xa6, 0x43, 0x69, 0x67, 0x0d, 0x52,0x36,0x6e);
Guid classGuid = GUID_DEVINTERFACE_DFU;
IntPtr hDevInfo = Win32.SetupDiGetClassDevs(ref classGuid, IntPtr.Zero, IntPtr.Zero, Win32.DIGCF_DEVICEINTERFACE | Win32.DIGCF_PRESENT);
if (hDevInfo.ToInt32() == Win32.INVALID_HANDLE_VALUE)
{
Console.WriteLine("read hardware information error");
}
else
{
SP_DEVINFO_DATA devInfoData = new SP_DEVINFO_DATA();
devInfoData.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
devInfoData.classGuid = Guid.Empty;
devInfoData.devInst = 0;
devInfoData.reserved = IntPtr.Zero;
bool result = Win32.SetupDiEnumDeviceInfo(hDevInfo, i, devInfoData);
if (false == result)
{
int error = Marshal.GetLastWin32Error();
if (error != Win32.ERROR_NO_MORE_ITEMS)
throw new Win32Exception(error);
}
SP_DEVICE_INTERFACE_DATA ifData = new SP_DEVICE_INTERFACE_DATA();
ifData.cbSize = (uint)Marshal.SizeOf(ifData);
ifData.Flags = 0;
ifData.InterfaceClassGuid = Guid.Empty;
ifData.Reserved = IntPtr.Zero;
bool result2 = Win32.SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref classGuid, i, ifData);
if(result2 == false)
{
int error = Marshal.GetLastWin32Error();
if (error != Win32.ERROR_NO_MORE_ITEMS)
throw new Win32Exception(error);
}
uint needed;
// This returns: needed=160, result3=false and error=122 ("The data area passed to a system call is too small")
bool result3 = Win32.SetupDiGetDeviceInterfaceDetail(hDevInfo, ifData, null, 0, out needed, null);
if(result3 == false)
{
int error = Marshal.GetLastWin32Error();
}
IntPtr detailDataBuffer = IntPtr.Zero;
SP_DEVICE_INTERFACE_DETAIL_DATA ifDetailsData = new SP_DEVICE_INTERFACE_DETAIL_DATA();
ifDetailsData.devicePath = new byte[needed - 4];
ifDetailsData.cbSize = (uint)Marshal.SizeOf(ifDetailsData);
uint nBytes = needed;
// This returns always: error = 1784
bool result4 = Win32.SetupDiGetDeviceInterfaceDetail(hDevInfo, ifData, ifDetailsData, nBytes, out needed, null);
if (result4 == false)
{
int error = Marshal.GetLastWin32Error();
if (error != Win32.ERROR_NO_MORE_ITEMS)
throw new Win32Exception(error);
}
}
Classe Win32:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace USB_test
{
[StructLayout(LayoutKind.Sequential)]
public class SP_DEVINFO_DATA
{
public uint cbSize;
public Guid classGuid;
public uint devInst;
public IntPtr reserved;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class SP_DEVICE_INTERFACE_DETAIL_DATA
{
public uint cbSize;
public byte[] devicePath;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public class SP_DEVICE_INTERFACE_DATA
{
public uint cbSize;
public Guid InterfaceClassGuid;
public uint Flags;
public IntPtr Reserved;
}
public class Win32
{
public static uint ANYSIZE_ARRAY = 1000;
[DllImport("setupapi.dll", SetLastError = true)]
public static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, uint Flags);
[DllImport("setupapi.dll", SetLastError = true)]
public static extern Boolean SetupDiEnumDeviceInfo(IntPtr lpInfoSet, UInt32 dwIndex, SP_DEVINFO_DATA devInfoData);
[DllImport(#"setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, uint memberIndex, SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
[DllImport(#"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr hDevInfo, SP_DEVICE_INTERFACE_DATA deviceInterfaceData, SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, uint deviceInterfaceDetailDataSize, out uint requiredSize, SP_DEVINFO_DATA deviceInfoData);
public const int DIGCF_PRESENT = 0x02;
public const int DIGCF_DEVICEINTERFACE = 0x10;
public const int SPDRP_DEVICEDESC = (0x00000000);
public const long ERROR_NO_MORE_ITEMS = 259L;
}
}
If it can help someone, this is the solution:
IntPtr detailDataBuffer = Marshal.AllocHGlobal((int)needed);
Marshal.WriteInt32(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
uint nBytes = needed;
bool result4 = Win32.SetupDiGetDeviceInterfaceDetail(hDevInfo, ifData, detailDataBuffer, nBytes, out needed, null);
if (result4 == false)
{
int error = Marshal.GetLastWin32Error();
if (error != Win32.ERROR_NO_MORE_ITEMS)
throw new Win32Exception(error);
}
IntPtr pDevicePathName = new IntPtr(detailDataBuffer.ToInt32() + 4);
String devicePathName = Marshal.PtrToStringAuto(pDevicePathName);
Additional Note: If running on a 64-bit machine, or forced 64-bit mode, the above line for the pointer to pDevicePathName would reference a 64-bit pointer, not 32
IntPtr pDevicePathName = new IntPtr(detailDataBuffer.ToInt64() + 8);
The struct is a variable sized structure which cannot be marshalled automatically. You'll need to do so yourself.
You'll need to remove the SP_DEVICE_INTERFACE_DETAIL_DATA type. It's no use to you. Change the declaration of SetupDiGetDeviceInterfaceDetail to:
[DllImport(#"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean SetupDiGetDeviceInterfaceDetail(
IntPtr hDevInfo,
SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
IntPtr deviceInterfaceDetailData,
uint deviceInterfaceDetailDataSize,
out uint requiredSize,
SP_DEVINFO_DATA deviceInfoData
);
Pass IntPtr.Zero in the first call to SetupDiGetDeviceInterfaceDetail. Then allocate a buffer of the required size by calling Marshal.AllocHGlobal. Then write the size into the first 4 bytes of that buffer. Then call SetupDiGetDeviceInterfaceDetail again.
Something along these lines:
bool result3 = Win32.SetupDiGetDeviceInterfaceDetail(hDevInfo, ifData, IntPtr.Zero, 0,
out needed, null);
if(!result3)
{
int error = Marshal.GetLastWin32Error();
}
// expect that result3 is false and that error is ERROR_INSUFFICIENT_BUFFER = 122,
// and needed is the required size
IntPtr DeviceInterfaceDetailData = Marshal.AllocHGlobal((int)needed);
try
{
uint size = needed;
Marshal.WriteInt32(DeviceInterfaceDetailData, (int)size);
bool result4 = Win32.SetupDiGetDeviceInterfaceDetail(hDevInfo, ifData,
DeviceInterfaceDetailData, size, out needed, null);
if(!result4)
{
int error = Marshal.GetLastWin32Error();
}
// do whatever you need with DeviceInterfaceDetailData
}
finally
{
Marshal.FreeHGlobal(DeviceInterfaceDetailData);
}
For me, the answer of David Hoffmann doesn't work. But he inspired me to this solution:
IntPtr buffer = Marshal.AllocHGlobal((int)requiredSize);
int cbSize = sizeof(DWORD) + 2 * sizeof(CHAR); // cbSize + empty DevicePath
Marshal.WriteInt32(buffer, cbSize);
deviceInterfaceInfoData.cbSize = (DWORD)Marshal.SizeOf(deviceInterfaceInfoData);
if (!SetupDiGetDeviceInterfaceDetail(
deviceInfoSet,
ref deviceInterfaceData,
buffer,
requiredSize,
out requiredSize,
ref deviceInterfaceInfoData))
throw new Win32Exception(error);
int devicePathSize = (int)requiredSize - sizeof(DWORD); // cbSize
char[] devicePathChars = new char[devicePathSize / sizeof(char)];
int offset = sizeof(DWORD); // cbSize
Marshal.Copy(IntPtr.Add(buffer, offset), devicePathChars, 0, devicePathChars.Length);
string devicePath = new(devicePathChars, 0, devicePathChars.Length - 1); // Remove NULL terminator
Marshal.FreeHGlobal(buffer);
Related
I have an application that monitors file and folder changes. I get an error when a new file is created, deleted or modified. I have the following code and have no idea why the Marshal.PtrToStringAuto throw "Attempted to read or write protected memory. This is often an indication that other memory is corrupt.".
Why am I getting this error when I try to get the filename?
Can someone please help me on what to look for to resolve this error...
error image
[DllImport("kernel32.dll", EntryPoint = "FindFirstChangeNotification")]
static extern System.IntPtr FindFirstChangeNotification(string lpPathName, bool bWatchSubtree, uint dwNotifyFilter);
[DllImport("kernel32.dll", EntryPoint = "FindNextChangeNotification")]
static extern bool FindNextChangeNotification(IntPtr hChangedHandle);
[DllImport("kernel32.dll", EntryPoint = "FindCloseChangeNotification")]
static extern bool FindCloseChangeNotification(IntPtr hChangedHandle);
[DllImport("kernel32.dll", EntryPoint = "WaitForSingleObject")]
static extern uint WaitForSingleObject(IntPtr handle, uint dwMilliseconds);
[DllImport("kernel32.dll", EntryPoint = "ReadDirectoryChangesW")]
static extern bool ReadDirectoryChangesW(IntPtr hDirectory, IntPtr lpBuffer, uint nBufferLength, bool bWatchSubtree, uint dwNotifyFilter, out uint lpBytesReturned, uint lpOverlapped, uint lpCompletionRoutine);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern int ReadDirectoryChangesW(uint hDirectory, out FILE_NOTIFY_INFORMATION finfo, uint nBufferLength,uint bWatchSubtree, uint dwNotifyFilter, out uint lpbytesReturned,uint PassZero1, uint PassZero2);
[DllImport("kernel32.dll", EntryPoint = "CreateFile")]
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
private const int MaxChanges = 4096;
public async Task ReadChangesAsyncNew()
{
var token = _cancellationTokenSource.Token;
await Task.Run(() =>
{
unsafe
{
var directoryHandle = CreateFile(_path, 0x80000000, 0x00000007, IntPtr.Zero, 3, 0x02000000, IntPtr.Zero);
var fileCreatedDeletedOrUpdated = FileSystemNotifications.FileNameChanged | FileSystemNotifications.FileModified;
var waitable = FindFirstChangeNotification(_path, true, (uint)fileCreatedDeletedOrUpdated);
var notifySize = Marshal.SizeOf(typeof(FileNotifyInformation));
do
{
var changes = new FileNotifyInformation[MaxChanges];
var pinnedArray = GCHandle.Alloc(changes, GCHandleType.Pinned);
var buffer = pinnedArray.AddrOfPinnedObject();
uint bytesReturned = 0;
if (!ReadDirectoryChangesW(directoryHandle, buffer, (uint)(notifySize * MaxChanges), true, (uint)fileCreatedDeletedOrUpdated, out bytesReturned, 0, 0))
throw Win32Error.GetLastError().GetException();
var result = new List<FileEvent>();
for (var i = 0; i < bytesReturned / notifySize; i += 1)
{
var change = Marshal.PtrToStructure<FileNotifyInformation>(new IntPtr(buffer.ToInt64() + i * notifySize));
if (((int)change.Action) == (int)FileActions.FileAdded)
{
result.Add(new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileAdded));
}
else if (((int)change.Action) == (int)FileActions.FileRemoved)
{
result.Add(new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileDeleted));
}
else if (((int)change.Action) == (int)FileActions.FileModified)
{
result.Add(new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileChanged));
}
else if (((int)change.Action) == (int)FileActions.FileRenamedNew)
{
result.Add(new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileRenamed));
}
}
pinnedArray.Free();
} while (FindNextChangeNotification(waitable));
FindCloseChangeNotification(waitable);
}
}, token);
}
I am using IApplicationActivationManager Interface method to start my WinStore package. But desired application is opening in separate window. I need this app to open in current window. Could you please help me for the same. My code is following:
[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
class ApplicationActivationManager { }
static class MetroLauncher
{
public static uint LaunchApp(string packageFullName, string arguments = null)
{
IntPtr pir = IntPtr.Zero;
try
{
int error = OpenPackageInfoByFullName(packageFullName, 0, out pir);
Debug.Assert(error == 0);
if (error != 0)
throw new Win32Exception(error);
int length = 0, count;
GetPackageApplicationIds(pir, ref length, null, out count);
var buffer = new byte[length];
error = GetPackageApplicationIds(pir, ref length, buffer, out count);
Debug.Assert(error == 0);
if (error != 0)
throw new Win32Exception(error);
var appUserModelId = Encoding.Unicode.GetString(buffer, IntPtr.Size * count, length - IntPtr.Size * count);
var activation = (IApplicationActivationManager)new ApplicationActivationManager();
uint pid;
int hr = activation.ActivateApplication(appUserModelId, arguments ?? string.Empty, ActivateOptions.NoErrorUI, out pid);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
return pid;
}
finally
{
if (pir != IntPtr.Zero)
ClosePackageInfo(pir);
}
}
[DllImport("kernel32")]
static extern int OpenPackageInfoByFullName([MarshalAs(UnmanagedType.LPWStr)] string fullName, uint reserved, out IntPtr packageInfo);
[DllImport("kernel32")]
static extern int GetPackageApplicationIds(IntPtr pir, ref int bufferLength, byte[] buffer, out int count);
[DllImport("kernel32")]
static extern int ClosePackageInfo(IntPtr pir);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("2e941141-7f97-4756-ba1d-9decde894a3d")]
interface IApplicationActivationManager {
int ActivateApplication([MarshalAs(UnmanagedType.LPWStr)] string appUserModelId, [MarshalAs(UnmanagedType.LPWStr)] string arguments,
ActivateOptions options, out uint processId);
int ActivateForFile([MarshalAs(UnmanagedType.LPWStr)] string appUserModelId, IntPtr pShelItemArray,
[MarshalAs(UnmanagedType.LPWStr)] string verb, out uint processId);
int ActivateForProtocol([MarshalAs(UnmanagedType.LPWStr)] string appUserModelId, IntPtr pShelItemArray,
[MarshalAs(UnmanagedType.LPWStr)] string verb, out uint processId);
}
I have an application that writes large files in multiple segments. I use FileStream.Seek to position each wirte. It appears that when I call FileStream.Write at a deep position in a sparse file the write triggers a "backfill" operation (writeing 0s) on all preceding bytes which is slow.
Is there a more efficient way of handling this situation?
The below code demonstrates the problem. The initial write takes about 370 MS on my machine.
public void WriteToStream()
{
DateTime dt;
using (FileStream fs = File.Create("C:\\testfile.file"))
{
fs.SetLength(1024 * 1024 * 100);
fs.Seek(-1, SeekOrigin.End);
dt = DateTime.Now;
fs.WriteByte(255);
}
Console.WriteLine(#"WRITE MS: " + DateTime.Now.Subtract(dt).TotalMilliseconds.ToString());
}
NTFS does support Sparse Files, however there is no way to do it in .net without p/invoking some native methods.
It is not very hard to mark a file as sparse, just know once a file is marked as a sparse file it can never be converted back in to a non sparse file except by coping the entire file in to a new non sparse file.
Example useage
class Program
{
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool DeviceIoControl(
SafeFileHandle hDevice,
int dwIoControlCode,
IntPtr InBuffer,
int nInBufferSize,
IntPtr OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
[In] ref NativeOverlapped lpOverlapped
);
static void MarkAsSparseFile(SafeFileHandle fileHandle)
{
int bytesReturned = 0;
NativeOverlapped lpOverlapped = new NativeOverlapped();
bool result =
DeviceIoControl(
fileHandle,
590020, //FSCTL_SET_SPARSE,
IntPtr.Zero,
0,
IntPtr.Zero,
0,
ref bytesReturned,
ref lpOverlapped);
if(result == false)
throw new Win32Exception();
}
static void Main()
{
//Use stopwatch when benchmarking, not DateTime
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
using (FileStream fs = File.Create(#"e:\Test\test.dat"))
{
MarkAsSparseFile(fs.SafeFileHandle);
fs.SetLength(1024 * 1024 * 100);
fs.Seek(-1, SeekOrigin.End);
fs.WriteByte(255);
}
stopwatch.Stop();
//Returns 2 for sparse files and 1127 for non sparse
Console.WriteLine(#"WRITE MS: " + stopwatch.ElapsedMilliseconds);
}
}
Once a file has been marked as sparse it now behaves like you excepted it to behave in the comments too. You don't need to write a byte to mark a file to a set size.
static void Main()
{
string filename = #"e:\Test\test.dat";
using (FileStream fs = new FileStream(filename, FileMode.Create))
{
MarkAsSparseFile(fs.SafeFileHandle);
fs.SetLength(1024 * 1024 * 25);
}
}
Here is some code to use sparse files:
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.Win32.SafeHandles;
public static class SparseFiles
{
private const int FILE_SUPPORTS_SPARSE_FILES = 64;
private const int FSCTL_SET_SPARSE = 0x000900c4;
private const int FSCTL_SET_ZERO_DATA = 0x000980c8;
public static void MakeSparse(this FileStream fileStream)
{
var bytesReturned = 0;
var lpOverlapped = new NativeOverlapped();
var result = DeviceIoControl(
fileStream.SafeFileHandle,
FSCTL_SET_SPARSE,
IntPtr.Zero,
0,
IntPtr.Zero,
0,
ref bytesReturned,
ref lpOverlapped);
if (!result)
{
throw new Win32Exception();
}
}
public static void SetSparseRange(this FileStream fileStream, long fileOffset, long length)
{
var fzd = new FILE_ZERO_DATA_INFORMATION();
fzd.FileOffset = fileOffset;
fzd.BeyondFinalZero = fileOffset + length;
var lpOverlapped = new NativeOverlapped();
var dwTemp = 0;
var result = DeviceIoControl(
fileStream.SafeFileHandle,
FSCTL_SET_ZERO_DATA,
ref fzd,
Marshal.SizeOf(typeof(FILE_ZERO_DATA_INFORMATION)),
IntPtr.Zero,
0,
ref dwTemp,
ref lpOverlapped);
if (!result)
{
throw new Win32Exception();
}
}
public static bool SupportedOnVolume(string filename)
{
var targetVolume = Path.GetPathRoot(filename);
var fileSystemName = new StringBuilder(300);
var volumeName = new StringBuilder(300);
uint lpFileSystemFlags;
uint lpVolumeSerialNumber;
uint lpMaxComponentLength;
var result = GetVolumeInformationW(
targetVolume,
volumeName,
(uint)volumeName.Capacity,
out lpVolumeSerialNumber,
out lpMaxComponentLength,
out lpFileSystemFlags,
fileSystemName,
(uint)fileSystemName.Capacity);
if (!result)
{
throw new Win32Exception();
}
return (lpFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES) == FILE_SUPPORTS_SPARSE_FILES;
}
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeviceIoControl(
SafeFileHandle hDevice,
int dwIoControlCode,
IntPtr InBuffer,
int nInBufferSize,
IntPtr OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
[In] ref NativeOverlapped lpOverlapped);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeviceIoControl(
SafeFileHandle hDevice,
int dwIoControlCode,
ref FILE_ZERO_DATA_INFORMATION InBuffer,
int nInBufferSize,
IntPtr OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
[In] ref NativeOverlapped lpOverlapped);
[DllImport("kernel32.dll", EntryPoint = "GetVolumeInformationW")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetVolumeInformationW(
[In] [MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
[Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpVolumeNameBuffer,
uint nVolumeNameSize,
out uint lpVolumeSerialNumber,
out uint lpMaximumComponentLength,
out uint lpFileSystemFlags,
[Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileSystemNameBuffer,
uint nFileSystemNameSize);
[StructLayout(LayoutKind.Sequential)]
private struct FILE_ZERO_DATA_INFORMATION
{
public long FileOffset;
public long BeyondFinalZero;
}
}
And sample code to test the above class.
class Program
{
static void Main(string[] args)
{
using (var fileStream = new FileStream("test", FileMode.Create, FileAccess.ReadWrite, FileShare.None))
{
fileStream.SetLength(1024 * 1024 * 128);
fileStream.MakeSparse();
fileStream.SetSparseRange(0, fileStream.Length);
}
}
}
Hope this helps
I have an unmanaged C++ Windows console app that works fine. I want it in C#. I have done DllImport statements for the necessary Kernel32.dll symbols:
[StructLayout(LayoutKind.Sequential)]
internal struct DiskGeometry
{
public long Cylinders;
public int MediaType;
public int TracksPerCylinder;
public int SectorsPerTrack;
public int BytesPerSector;
}
internal static class NativeMethods
{
internal const uint FileAccessGenericRead = 0x80000000;
internal const uint FileShareWrite = 0x2;
internal const uint FileShareRead = 0x1;
internal const uint CreationDispositionOpenExisting = 0x3;
internal const uint IoCtlDiskGetDriveGeometry = 0x70000;
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile(
string fileName,
uint fileAccess,
uint fileShare,
IntPtr securityAttributes,
uint creationDisposition,
uint flags,
IntPtr template);
[DllImport("Kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
public static extern int DeviceIoControl(
SafeFileHandle device,
uint controlCode,
IntPtr inBuffer,
uint inBufferSize,
IntPtr outBuffer,
uint outBufferSize,
ref uint bytesReturned,
IntPtr overlapped);
}
I then have the following application code:
public static void Main()
{
SafeFileHandle diskHandle = NativeMethods.CreateFile(
"\\\\.\\PhysicalDrive0",
NativeMethods.FileAccessGenericRead,
NativeMethods.FileShareWrite | NativeMethods.FileShareRead,
IntPtr.Zero,
NativeMethods.CreationDispositionOpenExisting,
0,
IntPtr.Zero);
if (diskHandle.IsInvalid)
{
Console.WriteLine("CreateFile failed with error: {0}", Marshal.GetLastWin32Error());
return;
}
int geometrySize = Marshal.SizeOf(typeof(DiskGeometry));
Console.WriteLine("geometry size = {0}", geometrySize);
IntPtr geometryBlob = Marshal.AllocHGlobal(geometrySize);
uint numBytesRead = 0;
if (0 == NativeMethods.DeviceIoControl(
diskHandle,
NativeMethods.IoCtlDiskGetDriveGeometry,
IntPtr.Zero,
0,
geometryBlob,
(uint)geometrySize,
ref numBytesRead,
IntPtr.Zero))
{
Console.WriteLine("DeviceIoControl failed with error: {0}", Marshal.GetLastWin32Error());
return;
}
Console.WriteLine("Bytes read = {0}", numBytesRead);
DiskGeometry geometry = (DiskGeometry)Marshal.PtrToStructure(geometryBlob, typeof(DiskGeometry));
Marshal.FreeHGlobal(geometryBlob);
long bytesPerCylinder = (long)geometry.TracksPerCylinder * (long)geometry.SectorsPerTrack * (long)geometry.BytesPerSector;
long totalSize = geometry.Cylinders * bytesPerCylinder;
Console.WriteLine("Media Type: {0}", geometry.MediaType);
Console.WriteLine("Cylinders: {0}", geometry.Cylinders);
Console.WriteLine("Tracks per Cylinder: {0}", geometry.TracksPerCylinder);
Console.WriteLine("Sectors per Track: {0}", geometry.SectorsPerTrack);
Console.WriteLine("Bytes per Sector: {0}", geometry.BytesPerSector);
Console.WriteLine("Bytes per Cylinder: {0}", bytesPerCylinder);
Console.WriteLine("Total disk space: {0}", totalSize);
}
My C# app prints "Bytes read = 0" and the geometry member values are garbage. I am certainly no expert on DllImport and marshaling. Please help me understand what I am doing wrong. If I change the fifth parameter to DeviceIoControl to be "ref DiskGeometry" and just pass in one created right before the call (instead of IntPtr and alloc), all printed geometry member values are 0.
You have mistype, try this:
internal const uint IoCtlDiskGetDriveGeometry = 0x70000;
Try using one of those signatures for the DllImport: http://pinvoke.net/default.aspx/kernel32.DeviceIoControl
It's a pointer to an array of LSA_UNICODE_STRING structures. I found some code that does the inverse, i.e., create a LSA_UNICODE_STRING from a C# string. You can see that in the helper code section below.
What I have up to and including the call to LsaEnumerateAccountRights() seems to work just fine. Sensible values are returned for the array pointer and for the count.
I am at a loss as to how to get at those blasted strings. Help please? Pretty please?
UPDATE: nobugz's helper function in his answer below is ALMOST right, you only have to divide the length by UnicodeEncoding.CharSize. Thanks to him, I can now see the FIRST string in the array. See the updates at the end of both code sections below.
Now, how the netherworld do I do pointer arithmetic?
UPDATE 2.5: See answer for the functioning code. I lost the old, "wrong" code.
Found it! In this blog post. Now the amended code below works fully. It's even 64-bit safe!
The main code:
IntPtr sid = IntPtr.Zero;
int sidSize = 0;
StringBuilder domainName = new StringBuilder();
int nameSize = 0;
int accountType = 0;
LookupAccountName("\\\\" + tbHost.Text, tbUsername.Text, sid, ref sidSize,
domainName, ref nameSize, ref accountType);
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);
bool result = LookupAccountName("\\\\" + tbHost.Text, tbUsername.Text, sid, ref sidSize,
domainName, ref nameSize, ref accountType);
myResults.Text += String.Format("LookupAccountName(): Result {0}, SID {1}\n", result, sid);
LSA_UNICODE_STRING systemName = string2LSAUS("\\\\" + tbHost.Text);
IntPtr policyHandle = IntPtr.Zero;
LSA_OBJECT_ATTRIBUTES objAttrs = new LSA_OBJECT_ATTRIBUTES();
uint retVal = LsaOpenPolicy(ref systemName, ref objAttrs,
POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION, out policyHandle);
myResults.Text += String.Format("LsaOpenPolicy(): Result {0}, Policy Handle {1}\n", retVal, policyHandle);
IntPtr rightsArray = IntPtr.Zero;
ulong rightsCount = 0;
long lretVal = LsaEnumerateAccountRights(policyHandle, sid, out rightsArray, out rightsCount);
retVal = LsaNtStatusToWinError(lretVal);
if (retVal != 0)
throw new System.ComponentModel.Win32Exception((int)retVal);
myResults.Text += String.Format("LsaEnumerateAccountRights(): Result {0}, RightsArray {1}, Count {2}\n",
retVal, rightsArray, rightsCount);
LSA_UNICODE_STRING myLsaus = new LSA_UNICODE_STRING();
for (ulong i = 0; i < rightsCount; i++)
{
IntPtr itemAddr = new IntPtr(rightsArray.ToInt64() + (long)(i * (ulong) Marshal.SizeOf(myLsaus)));
myLsaus = (WinNetUtils.LSA_UNICODE_STRING)Marshal.PtrToStructure(itemAddr, myLsaus.GetType());
string thisRight = WinNetUtils.LSAUS2string(myLsaus);
NonBlockingPrint(wmiResults, "Right #{0}: {1}\n", i+1, thisRight);
}
LsaClose(policyHandle);
The helper functions, imports etc:
public const int POLICY_VIEW_LOCAL_INFORMATION = 0x1;
public const int POLICY_LOOKUP_NAMES = 0x00000800;
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, PreserveSig = true)]
public static extern UInt32 LsaNtStatusToWinError(
long Status);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = true)]
public static extern bool ConvertStringSidToSid(
string StringSid, out IntPtr pSid);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, PreserveSig = true)]
public static extern bool LookupAccountName(
string lpSystemName, string lpAccountName,
IntPtr psid, ref int cbsid,
StringBuilder domainName, ref int cbdomainLength,
ref int use );
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, PreserveSig = true)]
public static extern UInt32 LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
Int32 DesiredAccess,
out IntPtr PolicyHandle );
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
public static extern long LsaEnumerateAccountRights(
IntPtr PolicyHandle, IntPtr AccountSid,
out /* LSA_UNICODE_STRING[] */ IntPtr UserRights,
out ulong CountOfRights);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
public static extern long LsaClose(
IntPtr PolicyHandle);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LSA_OBJECT_ATTRIBUTES
{
public IntPtr RootDirectory;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
public LSA_UNICODE_STRING ObjectName;
public UInt32 Attributes;
public UInt32 Length;
}
public static LSA_UNICODE_STRING string2LSAUS(string myString)
{
LSA_UNICODE_STRING retStr = new LSA_UNICODE_STRING();
retStr.Buffer = Marshal.StringToHGlobalUni(myString);
retStr.Length = (UInt16)(myString.Length * UnicodeEncoding.CharSize);
retStr.MaximumLength = (UInt16)((myString.Length + 1) * UnicodeEncoding.CharSize);
return retStr;
}
public static string LSAUS2string(LSA_UNICODE_STRING lsaus)
{
char[] cvt = new char[lsaus.Length / UnicodeEncoding.CharSize];
Marshal.Copy(lsaus.Buffer, cvt, 0, lsaus.Length / UnicodeEncoding.CharSize);
return new string(cvt);
}
This ought to work for you:
private static string LSAUS2String(LSA_UNICODE_STRING lsa) {
char[] cvt = new char[lsa.Length];
Marshal.Copy(lsa.Buffer, cvt, 0, lsa.Length);
return new string(cvt);
}