How to create fast and efficient filestream writes on large sparse files - c#

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

Related

When trying to get notifications about file system changes on Windows, getting "Attempted to read or write protected memory" error

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);
}

Understanding SP_DEVICE_INTERFACE_DETAIL_DATA and processing on it [duplicate]

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);

How to know if two hard links point to the same inode? (C#)

Anyway of checking, in C#, that two files (hard links) point to the same inode? And also get the count of this inode, in case there are more than two... ?
You can get count of hard links pointing to the node using GetFileInformationByHandle function. For example:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetFileInformationByHandle(
SafeFileHandle hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation
);
[StructLayout(LayoutKind.Sequential)]
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;
}
// then in another place
using (var fs = File.OpenRead("path to your file")) {
BY_HANDLE_FILE_INFORMATION info;
GetFileInformationByHandle(fs.SafeFileHandle, out info);
var numberOfLinks = info.NumberOfLinks;
}
To get what files they are pointing to, you will need another win api functions: FindFirstFileNameW and FineNextFileNameW. Use them like this:
[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);
public static string[] GetFileHardLinks(string filePath) {
// first get drive letter
var drive = new DriveInfo(Path.GetPathRoot(filePath));
var result = new List<string>();
// buffer for return value
var sb = new StringBuilder(256);
// length of buffer
uint sbLength = 256;
// third argument contains reference to buffer length (buffer is StringBuilder).
// it's a reference because if it's too small, call returns an error and will put required length there instead
IntPtr findHandle = FindFirstFileNameW(filePath, 0, ref sbLength, sb);
// returns -1 on error
if (findHandle.ToInt64() != -1) {
do {
// combine the result with drive letter (it comes without it)
result.Add(Path.Combine(drive.RootDirectory.FullName, sb.ToString().TrimStart(new [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar})));
sb.Clear();
sbLength = 256;
// and repeat
} while (FindNextFileNameW(findHandle, ref sbLength, sb));
FindClose(findHandle);
return result.ToArray();
}
return null;
}
This code might be not production ready, so take care. But it should at least give you an idea. If you will use it - carefully read what those function return on errors and act accordingly (for example, handle the case when buffer length is not enough, or just use larger buffer than 256).

How to get a value from other application and write it to a file?

I'm trying to make a simple app with no UI, that will take data from another app and store it in output file using C#. That's all. How can I make:
1. Open the process
2. Find the value "X"
2.1 Find the value "Y"
2.2 Find the value "Z"
3. Write values to a file "C:\output.txt"
4. Update every 1000ms for example.
Here are two codes I hardly managed to run. Reading from app:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
public class MemoryRead
{
const int PROCESS_WM_READ = 0x0010;
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess,
int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
public static void Main()
{
Process process = Process.GetProcessesByName("notepad")[0];
IntPtr processHandle = OpenProcess(PROCESS_WM_READ, false, process.Id);
int bytesRead = 0;
byte[] buffer = new byte[24];
ReadProcessMemory((int)processHandle, 0x0001BA5B0, buffer, buffer.Length, ref bytesRead);
Console.WriteLine(Encoding.Unicode.GetString(buffer) +
" (" + bytesRead.ToString() + "bytes)");
Console.ReadLine();
}
}
Write to a file:
class WriteTextFile
{
static void Main()
{
string[] lines = { "X", "Y", "Z", };
System.IO.File.WriteAllLines(#"O:\CS\output.txt", lines);
}
}
2 question:
1. How do I "connect" these two codes
2. How do I make the program refresh output every 1sec/2sec/300msec?
Thanks for any help!
P.S. If you know any existing app that will do exact same thing - it will be just OK for me.
UPDATE:
Finally made two pieces working together!
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
class Program
{
const int PROCESS_WM_READ = 0x0010;
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess,
int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
static void Main()
{
using (StreamWriter writer = new StreamWriter("O:\\out.txt"))
{
Console.SetOut(writer);
Act();
}
}
static void Act()
{
Process process = Process.GetProcessesByName("notepad")[0];
IntPtr processHandle = OpenProcess(PROCESS_WM_READ, false, process.Id);
int bytesRead = 0;
byte[] buffer = new byte[24];
ReadProcessMemory((int)processHandle, 0x0021AAD0, buffer, buffer.Length, ref bytesRead);
Console.WriteLine(Encoding.Unicode.GetString(buffer));
}
}
The code does what I need but only once: it reads the value and stores it in .txt. If the notepad's value is updated, the .txt content will remain unchanged until the program is restarted. How can I make it update over and over with no restarting the program?
C'mon guys, I'm half way there!!
Create a scheduled task that runs the program on an interval.
Edit...
static void Main()
{
var totalRunTime = TimeSpan.FromDays(1);
var elapsed = 0;
while (elapsed < totalRunTime.TotalMilliseconds)
{
using (StreamWriter writer = new StreamWriter("O:\\out.txt"))
{
Console.SetOut(writer);
Act();
}
Threading.Thread.Sleep(350);
elapsed += 350;
}
}

Why does my C#/pinvoke DeviceIoControl call return 0 bytes read with garbage data?

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

Categories

Resources