C
C#2y ago
Matt

✅ Exception Handling for NRE without modifying every call.

I have a ReadData function which reads the memory of a process. If the process exits or crashes during the read, an exception is thrown and control is handed to the catch block. However, the calling function (the function that calls ReadData) can sometimes throw an NRE if the ReadData was aborted and returned null instead of a byte[]. How can I avoid the NREs without needing to change every single call of ReadData? Ideally I would abort the calling function as if it had never been called but I know that would require a large scale rewrite. I have toyed with the idea of modifying the ReadData signature to return a tuple of type (byte[], bool) and changing the behaviour of the calling function depending on the success of the ReadData (bool) but this would also require custom behaviour for every call. There are about 20 calls and I figure adding custom behaviour or exception handling for all of them is inefficient and bad practice. Any solutions?
17 Replies
ero
ero2y ago
ReadData should really just be
unsafe bool TryRead<T>(nint handle, nint address, out T result)
where T : unmanaged
{
fixed (T* pResult = &result)
{
nuint nSize = (nuint)bufferSize, nRead;
return ReadProcessMemory(handle, (void*)address, pResult, nSize, &nRead)
&& nRead == nSize;
}
}
unsafe bool TryRead<T>(nint handle, nint address, out T result)
where T : unmanaged
{
fixed (T* pResult = &result)
{
nuint nSize = (nuint)bufferSize, nRead;
return ReadProcessMemory(handle, (void*)address, pResult, nSize, &nRead)
&& nRead == nSize;
}
}
no throwing involved and you know immediately whether or not the call failed or succeeded from the return value you could even make it an extension method on Process;
static unsafe bool TryRead<T>(this Process proc, nint address, out T result)
where T : unmanaged
{
if (proc is null || proc.Handle == 0)
{
result = default;
return false;
}

fixed (T* pResult = &result)
{
nuint nSize = (nuint)bufferSize, nRead;
return ReadProcessMemory(proc.Handle, (void*)address, pResult, nSize, &nRead)
&& nRead == nSize;
}
}
static unsafe bool TryRead<T>(this Process proc, nint address, out T result)
where T : unmanaged
{
if (proc is null || proc.Handle == 0)
{
result = default;
return false;
}

fixed (T* pResult = &result)
{
nuint nSize = (nuint)bufferSize, nRead;
return ReadProcessMemory(proc.Handle, (void*)address, pResult, nSize, &nRead)
&& nRead == nSize;
}
}
and then you can simply do
var process = /* */;
Console.WriteLine(process.TryRead<int>(0xDEADBEEF, out int result));
Console.WriteLine(result);
var process = /* */;
Console.WriteLine(process.TryRead<int>(0xDEADBEEF, out int result));
Console.WriteLine(result);
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Matt
MattOP2y ago
I like this solution and I'm going to try it but I'm confused by the line
nuint nSize = (nuint)bufferSize, nRead;
nuint nSize = (nuint)bufferSize, nRead;
bufferSize is never declared?
ero
ero2y ago
ah, my bad i just copy pasted the code from another method
nuint nSize = (nuint)sizeof(T), nRead;
nuint nSize = (nuint)sizeof(T), nRead;
Matt
MattOP2y ago
Thank you, there's also a clash between my current declaration of the ReadProcessMemory method and the call to it. I'm assuming I need to change my declaration's signature?
public static extern bool ReadProcessMemory(IntPtr hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
public static extern bool ReadProcessMemory(IntPtr hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
ero
ero2y ago
yes https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory
BOOL ReadProcessMemory(
[in] HANDLE hProcess,
[in] LPCVOID lpBaseAddress,
[out] LPVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesRead
);
BOOL ReadProcessMemory(
[in] HANDLE hProcess,
[in] LPCVOID lpBaseAddress,
[out] LPVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesRead
);
this is the real signature HANDLE can be a nint (or IntPtr, they're identical, nint just doesn't require using System;) LPVOID and LPCVOID are void* SIZE_T is nuint (or UIntPtr) oh yeah and BOOL is int so the c# signature is
[DllImport("kernel32")]
internal static static extern int ReadProcessMemory(
nint hProcess,
void* lpBaseAddress,
void* lpBuffer,
nuint nSize,
nuint* lpNumberOfBytesRead);
[DllImport("kernel32")]
internal static static extern int ReadProcessMemory(
nint hProcess,
void* lpBaseAddress,
void* lpBuffer,
nuint nSize,
nuint* lpNumberOfBytesRead);
static unsafe bool TryRead<T>(this Process proc, nint address, out T result)
where T : unmanaged
{
if (proc is null || proc.Handle == 0)
{
result = default;
return false;
}

fixed (T* pResult = &result)
{
nuint nSize = (nuint)sizeof(T), nRead;
return ReadProcessMemory(proc.Handle, (void*)address, pResult, nSize, &nRead) != 0
&& nRead == nSize;
}
}
static unsafe bool TryRead<T>(this Process proc, nint address, out T result)
where T : unmanaged
{
if (proc is null || proc.Handle == 0)
{
result = default;
return false;
}

fixed (T* pResult = &result)
{
nuint nSize = (nuint)sizeof(T), nRead;
return ReadProcessMemory(proc.Handle, (void*)address, pResult, nSize, &nRead) != 0
&& nRead == nSize;
}
}
Matt
MattOP2y ago
Thank you. I guess I need to declare the method as unsafe too
ero
ero2y ago
ah, yeah or the class
Matt
MattOP2y ago
better option
ero
ero2y ago
unsafe class Win32
{

}
unsafe class Win32
{

}
or WinApi or Win32Api or use CsWin32 (NuGet)
Matt
MattOP2y ago
this seems to work. Sorry to be a pain but I assume there is also a solution for the write function which is similar?
ero
ero2y ago
almost the exact same
Matt
MattOP2y ago
Thank you, I'll figure that one out
ero
ero2y ago
[DllImport("kernel32")]
internal static extern int WriteProcessMemory(
nint hProcess,
void* lpBaseAddress,
void* lpBuffer,
nuint nSize,
nuint* lpNumberOfBytesWritten);
[DllImport("kernel32")]
internal static extern int WriteProcessMemory(
nint hProcess,
void* lpBaseAddress,
void* lpBuffer,
nuint nSize,
nuint* lpNumberOfBytesWritten);
static unsafe bool TryWrite<T>(this Process proc, nint address, T data)
where T : unmanaged
{
if (proc is null || proc.Handle == 0)
{
result = default;
return false;
}

fixed (T* pData = &data)
{
nuint nSize = (nuint)sizeof(T), nWritten;
return WriteProcessMemory(proc.Handle, (void*)address, pData, nSize, &nWritten) != 0
&& nWritten == nSize;
}
}
static unsafe bool TryWrite<T>(this Process proc, nint address, T data)
where T : unmanaged
{
if (proc is null || proc.Handle == 0)
{
result = default;
return false;
}

fixed (T* pData = &data)
{
nuint nSize = (nuint)sizeof(T), nWritten;
return WriteProcessMemory(proc.Handle, (void*)address, pData, nSize, &nWritten) != 0
&& nWritten == nSize;
}
}
Matt
MattOP2y ago
oh damn. Thank you 🙂
ero
ero2y ago
i'm not sure whether the handle check might be pointless you can possibly just remove that one
Matt
MattOP2y ago
^ it's not for my use case. I'll get on implementing this and hope it's less overhead and things speed up. Otherwise I have the nasty task of figuring out what's slowing my app down
Want results from more Discord servers?
Add your server