Kernel32 VirtualAllocEx returning IntPtr.Zero Intermittently - c#

We are trying to read the ToolTips from system tray icons and the code is working but is returning zero intermittently for the the method below calling Kernel32.VirtualAllocEx
IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
hProcess,
IntPtr.Zero,
new UIntPtr(BUFFER_SIZE),
MemAllocationType.COMMIT,
MemoryProtection.PAGE_READWRITE);
if (ipRemoteBuffer == IntPtr.Zero)
return String.Empty;
It seems to work absolutely fine then suddenly stops working and returns IntPtr.Zero consistently. When checking Marshal.GetLastWin32Error() it returns 8 (not enough memory). Below is the full code:
public static string GetTooltip(string search)
{
IntPtr _ToolbarWindowHandle = GetSystemTrayHandle();
UInt32 count = User32.SendMessage(_ToolbarWindowHandle, TB.BUTTONCOUNT, 0, 0);
List<string> tooltips = new List<string>();
for (int i = 0; i < count; i++)
{
TBBUTTON tbButton = new TBBUTTON();
string text = String.Empty;
IntPtr ipWindowHandle = IntPtr.Zero;
text = GetTBButtonText(_ToolbarWindowHandle, i, ref tbButton, ref text, ref ipWindowHandle);
if (!String.IsNullOrWhiteSpace(text) && text.ToLowerInvariant().Contains(search.ToLowerInvariant()))
return text;
}
return String.Empty;
}
static unsafe string GetTBButtonText(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle)
{
const int BUFFER_SIZE = 0x1000;
byte[] localBuffer = new byte[BUFFER_SIZE];
UInt32 processId = 0;
UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);
IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
if (hProcess == IntPtr.Zero)
return String.Empty;
IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
hProcess,
IntPtr.Zero,
new UIntPtr(BUFFER_SIZE),
MemAllocationType.COMMIT,
MemoryProtection.PAGE_READWRITE);
if (ipRemoteBuffer == IntPtr.Zero)
{
var error = Marshal.GetLastWin32Error();
return String.Empty;
}
// TBButton
fixed (TBBUTTON* pTBButton = &tbButton)
{
IntPtr ipTBButton = new IntPtr(pTBButton);
int b = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
if (b == 0)
return String.Empty;
// this is fixed
Int32 dwBytesRead = 0;
IntPtr ipBytesRead = new IntPtr(&dwBytesRead);
bool b2 = Kernel32.ReadProcessMemory(
hProcess,
ipRemoteBuffer,
ipTBButton,
new UIntPtr((uint)sizeof(TBBUTTON)),
ipBytesRead);
if (!b2)
return String.Empty;
}
// button text
fixed (byte* pLocalBuffer = localBuffer)
{
IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer);
int chars = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tbButton.idCommand, ipRemoteBuffer);
if (chars == -1) { Debug.Assert(false); return ""; }
// this is fixed
Int32 dwBytesRead = 0;
IntPtr ipBytesRead = new IntPtr(&dwBytesRead);
bool b4 = Kernel32.ReadProcessMemory(
hProcess,
ipRemoteBuffer,
ipLocalBuffer,
new UIntPtr(BUFFER_SIZE),
ipBytesRead);
if (!b4)
return String.Empty;
text = Marshal.PtrToStringUni(ipLocalBuffer, chars);
return text;
}
}

OK if I make a call to release the memory like so the problem is solved.
const uint MEM_RELEASE = 0x8000;
UIntPtr uintPtr = UIntPtr.Zero;
var successfullyReleased = Kernel32.VirtualFreeEx(hProcess, ipRemoteBuffer, uintPtr, MEM_RELEASE);
if (!successfullyReleased)
{
}

Related

Memory error at Marshal.PtrToStringUni (Runtime)

I am always getting error at this line (in Runtime, not in Editor):
return Marshal.PtrToStringUni(ptr, object_TYPE_INFORMATION.Name.Length >> 1);
Here is my full code:
public static string getObjectTypeName(Win32API.SYSTEM_HANDLE_INFORMATION shHandle, Process process)
{
IntPtr hSourceProcessHandle = Win32API.OpenProcess(Win32API.ProcessAccessFlags.All, false, process.Id);
IntPtr zero = IntPtr.Zero;
Win32API.OBJECT_BASIC_INFORMATION object_BASIC_INFORMATION = default(Win32API.OBJECT_BASIC_INFORMATION);
IntPtr intPtr = IntPtr.Zero;
Win32API.OBJECT_TYPE_INFORMATION object_TYPE_INFORMATION = default(Win32API.OBJECT_TYPE_INFORMATION);
IntPtr intPtr2 = IntPtr.Zero;
IntPtr zero2 = IntPtr.Zero;
int num = 0;
IntPtr ptr = IntPtr.Zero;
bool flag = !Win32API.DuplicateHandle(hSourceProcessHandle, shHandle.Handle, Win32API.GetCurrentProcess(), out zero, 0u, false, 2u);
string result;
if (flag)
{
result = null;
}
else
{
intPtr = Marshal.AllocHGlobal(Marshal.SizeOf<Win32API.OBJECT_BASIC_INFORMATION>(object_BASIC_INFORMATION));
Win32API.NtQueryObject(zero, 0, intPtr, Marshal.SizeOf<Win32API.OBJECT_BASIC_INFORMATION>(object_BASIC_INFORMATION), ref num);
object_BASIC_INFORMATION = (Win32API.OBJECT_BASIC_INFORMATION)Marshal.PtrToStructure(intPtr, object_BASIC_INFORMATION.GetType());
Marshal.FreeHGlobal(intPtr);
intPtr2 = Marshal.AllocHGlobal(object_BASIC_INFORMATION.TypeInformationLength);
num = object_BASIC_INFORMATION.TypeInformationLength;
while (Win32API.NtQueryObject(zero, 2, intPtr2, num, ref num) == -1073741820)
{
Marshal.FreeHGlobal(intPtr2);
intPtr2 = Marshal.AllocHGlobal(num);
}
object_TYPE_INFORMATION = (Win32API.OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(intPtr2, object_TYPE_INFORMATION.GetType());
bool flag2 = Win32Processes.Is64Bits();
if (flag2)
{
ptr = new IntPtr(Convert.ToInt64(object_TYPE_INFORMATION.Name.Buffer.ToString(), 10) >> 32);
}
else
{
ptr = object_TYPE_INFORMATION.Name.Buffer;
}
Marshal.FreeHGlobal(intPtr2);
}
return Marshal.PtrToStringUni(ptr, object_TYPE_INFORMATION.Name.Length >> 1);
}
Error:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
.NET Framework Version: 4.6.1
What am I doing wrong?

SCardEstablishContext memory leak

We suddenly have problems with the smart card api on some windows installations.
There seem to be a memory leak while calling the SCardEstablishContext function.
The problem can be reproduced in a console application with the code sample available at
http://www.pinvoke.net/default.aspx/winscard.scardestablishcontext
class Program
{
#region Win32
// WinSCard APIs to be imported.
[DllImport("WinScard.dll")]
static extern int SCardEstablishContext(uint dwScope,
IntPtr notUsed1,
IntPtr notUsed2,
out IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardReleaseContext(IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardConnect(IntPtr hContext,
string cReaderName,
uint dwShareMode,
uint dwPrefProtocol,
ref IntPtr phCard,
ref IntPtr ActiveProtocol);
[DllImport("WinScard.dll")]
static extern int SCardDisconnect(IntPtr hCard, int Disposition);
[DllImport("WinScard.dll", EntryPoint = "SCardListReadersA", CharSet = CharSet.Ansi)]
static extern int SCardListReaders(
IntPtr hContext,
byte[] mszGroups,
byte[] mszReaders,
ref UInt32 pcchReaders);
#endregion
static void Main(string[] args)
{
while (true)
{
SmartCardInserted();
System.Threading.Thread.Sleep(10);
}
}
internal static bool SmartCardInserted()
{
bool cardInserted = false;
IntPtr hContext = IntPtr.Zero;
try
{
List<string> readersList = new List<string>();
int ret = 0;
uint pcchReaders = 0;
int nullindex = -1;
char nullchar = (char)0;
// Establish context.
ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, out hContext);
// First call with 3rd parameter set to null gets readers buffer length.
ret = SCardListReaders(hContext, null, null, ref pcchReaders);
byte[] mszReaders = new byte[pcchReaders];
// Fill readers buffer with second call.
ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
// Populate List with readers.
ASCIIEncoding ascii = new ASCIIEncoding();
string currbuff = ascii.GetString(mszReaders);
int len = (int)pcchReaders;
if (len > 0)
{
while (currbuff[0] != nullchar)
{
nullindex = currbuff.IndexOf(nullchar); // Get null end character.
string reader = currbuff.Substring(0, nullindex);
readersList.Add(reader);
len = len - (reader.Length + 1);
currbuff = currbuff.Substring(nullindex + 1, len);
}
}
// We have list of readers, check for cards.
IntPtr phCard = IntPtr.Zero;
IntPtr ActiveProtocol = IntPtr.Zero;
int result = 0;
foreach (string readerName in readersList)
{
try
{
result = SCardConnect(hContext, readerName, 2, 3, ref phCard, ref ActiveProtocol);
if (result == 0)
{
cardInserted = true;
break;
}
}
finally
{
SCardDisconnect(phCard, 0);
}
}
}
finally
{
SCardReleaseContext(hContext);
}
return cardInserted;
}
}
To test, we call the method SmartCardInserted() in an infinite loop with a small delay => the memory grows constantly and new hadles are allocated.
We see this problem on systems runing Windows 10 or Windows Server 2012, but not on Windows Server 2008.
Any ideas are greatly appreciated!
The problem seems to have been released with v1709 of Windows 10. The shortest amount of code to reproduce the bug is
while(true) {
ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, out hContext);
SCardReleaseContext(hContext);
}
It leaks ~264 bytes of memory each time a context is established and released.
If you maintain hContext outside of the loop and only create a context if it's IntPtr.Zero you should be able to avoid the leak. Then when you call SCardListReaders, check to see if you get SCARD_E_INVALID_HANDLE back and invalidate your hContext.
class Program
{
#region Win32
// WinSCard APIs to be imported.
[DllImport("WinScard.dll")]
static extern int SCardEstablishContext(uint dwScope,
IntPtr notUsed1,
IntPtr notUsed2,
out IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardReleaseContext(IntPtr phContext);
[DllImport("WinScard.dll")]
static extern int SCardConnect(IntPtr hContext,
string cReaderName,
uint dwShareMode,
uint dwPrefProtocol,
ref IntPtr phCard,
ref IntPtr ActiveProtocol);
[DllImport("WinScard.dll")]
static extern int SCardDisconnect(IntPtr hCard, int Disposition);
[DllImport("WinScard.dll", EntryPoint = "SCardListReadersA", CharSet = CharSet.Ansi)]
static extern int SCardListReaders(
IntPtr hContext,
byte[] mszGroups,
byte[] mszReaders,
ref UInt32 pcchReaders);
#endregion
static void Main(string[] args)
{
IntPtr hContext = IntPtr.Zero;
while (true)
{
SmartCardInserted(hContext);
System.Threading.Thread.Sleep(10);
}
SCardReleaseContext(hContext);
}
internal static bool SmartCardInserted(IntPtr hContext)
{
bool cardInserted = false;
try
{
List<string> readersList = new List<string>();
int ret = 0;
uint pcchReaders = 0;
int nullindex = -1;
char nullchar = (char)0;
// Establish context.
if(hContext == IntPtr.Zero)
ret = SCardEstablishContext(2, IntPtr.Zero, IntPtr.Zero, out hContext);
// First call with 3rd parameter set to null gets readers buffer length.
ret = SCardListReaders(hContext, null, null, ref pcchReaders);
if(ret == 0x80100003) // SCARD_E_INVALID_HANDLE = 0x80100003, // The supplied handle was invalid
{
try
{
SCardReleaseContext(hContext);
}
catch {}
finally
{
hContext = IntPtr.Zero;
}
return false;
}
byte[] mszReaders = new byte[pcchReaders];
// Fill readers buffer with second call.
ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
// Populate List with readers.
ASCIIEncoding ascii = new ASCIIEncoding();
string currbuff = ascii.GetString(mszReaders);
int len = (int)pcchReaders;
if (len > 0)
{
while (currbuff[0] != nullchar)
{
nullindex = currbuff.IndexOf(nullchar); // Get null end character.
string reader = currbuff.Substring(0, nullindex);
readersList.Add(reader);
len = len - (reader.Length + 1);
currbuff = currbuff.Substring(nullindex + 1, len);
}
}
// We have list of readers, check for cards.
IntPtr phCard = IntPtr.Zero;
IntPtr ActiveProtocol = IntPtr.Zero;
int result = 0;
foreach (string readerName in readersList)
{
try
{
result = SCardConnect(hContext, readerName, 2, 3, ref phCard, ref ActiveProtocol);
if (result == 0)
{
cardInserted = true;
break;
}
}
finally
{
SCardDisconnect(phCard, 0);
}
}
}
return cardInserted;
}
}
It's a workaround until the Winscard.dll API is fixed.

LVM_GETCOLUMN only returns information about the first column

some days ago I had a question here about the function "LVM_GETCOLUMN" to get the text of an column in a syslistview32 (Please find this question right here: LVM_GETCOLUMN returns no result).
Thanks to an user here, I made it run. Afterwards I recogniced, that this function provides me only the text of the first column.
Does anybody know, what parameter in the LV_COLUMN struct is responsible to define the targetcolumn? I tried iOrder and iSubItem but it doesn't matter which parameter I change (or if I change both), I always get the text of the first column header. Here is the code I'm using:
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct LV_COLUMN
{
public System.Int32 mask;
public System.Int32 fmt;
public System.Int32 cx;
public System.IntPtr pszText;
public System.Int32 cchTextMax;
public System.Int32 iSubItem;
public System.Int32 iImage;
public System.Int32 iOrder;
}
public static string GetListViewColumn(System.IntPtr hwnd, uint processId, int Column)
{
const int dwBufferSize = 2048;
const int LVM_FIRST = 0x1000;
const int LVM_GETCOLUMNA = LVM_FIRST + 25;
const int LVM_GETCOLUMNW = LVM_FIRST + 95;
const int LVCF_FMT = 0x00000001;
const int LVCF_TEXT = 0x00000004;
int bytesWrittenOrRead = 0;
LV_COLUMN lvCol;
string retval;
bool bSuccess;
System.IntPtr hProcess = System.IntPtr.Zero;
System.IntPtr lpRemoteBuffer = System.IntPtr.Zero;
System.IntPtr lpLocalBuffer = System.IntPtr.Zero;
try
{
lvCol = new LV_COLUMN();
lpLocalBuffer = System.Runtime.InteropServices.Marshal.AllocHGlobal(dwBufferSize);
hProcess = OpenProcess(Win32ProcessAccessType.AllAccess, false, processId);
if (hProcess == System.IntPtr.Zero)
throw new System.ApplicationException("Failed to access process!");
lpRemoteBuffer = VirtualAllocEx(hProcess, System.IntPtr.Zero, dwBufferSize, Win32AllocationTypes.MEM_COMMIT, Win32MemoryProtection.PAGE_READWRITE);
if (lpRemoteBuffer == System.IntPtr.Zero)
throw new System.SystemException("Failed to allocate memory in remote process");
lvCol.mask = LVCF_TEXT;
lvCol.pszText = (System.IntPtr)(lpRemoteBuffer.ToInt32() + System.Runtime.InteropServices.Marshal.SizeOf(typeof(LV_COLUMN)));
lvCol.cchTextMax = 500;
lvCol.iOrder = Column;
lvCol.iSubItem = Column;
bSuccess = WriteProcessMemoryGETCOLUMN(hProcess, lpRemoteBuffer, ref lvCol, (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(LV_COLUMN)), out bytesWrittenOrRead);
if (!bSuccess)
throw new System.SystemException("Failed to write to process memory");
SendMessage(hwnd, LVM_GETCOLUMNW, System.IntPtr.Zero, lpRemoteBuffer);
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize, out bytesWrittenOrRead);
if (!bSuccess)
throw new System.SystemException("Failed to read from process memory");
retval = System.Runtime.InteropServices.Marshal.PtrToStringUni((System.IntPtr)(lpLocalBuffer.ToInt32() + System.Runtime.InteropServices.Marshal.SizeOf(typeof(LV_COLUMN))));
}
finally
{
if (lpLocalBuffer != System.IntPtr.Zero)
System.Runtime.InteropServices.Marshal.FreeHGlobal(lpLocalBuffer);
if (lpRemoteBuffer != System.IntPtr.Zero)
VirtualFreeEx(hProcess, lpRemoteBuffer, 0, Win32AllocationTypes.MEM_RELEASE);
if (hProcess != System.IntPtr.Zero)
CloseHandle(hProcess);
}
return retval; /*Always returns the name of the first column*/
}
The problem is in the SendMessage function.
The "wparam" argument is the index of the column you want to retrieve
int index = 2;
SendMessage(hwnd, LVM_GETCOLUMNW, index, lpRemoteBuffer);
Thanks to Laurijssen, here is the working code:
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct LV_COLUMN
{
public System.Int32 mask;
public System.Int32 fmt;
public System.Int32 cx;
public System.IntPtr pszText;
public System.Int32 cchTextMax;
public System.Int32 iSubItem;
public System.Int32 iImage;
public System.Int32 iOrder;
}
public static string GetListViewColumn(System.IntPtr hwnd, uint processId, int Column)
{
const int dwBufferSize = 2048;
const int LVM_FIRST = 0x1000;
const int LVM_GETCOLUMNA = LVM_FIRST + 25;
const int LVM_GETCOLUMNW = LVM_FIRST + 95;
const int LVCF_FMT = 0x00000001;
const int LVCF_TEXT = 0x00000004;
int bytesWrittenOrRead = 0;
LV_COLUMN lvCol;
string retval;
bool bSuccess;
System.IntPtr hProcess = System.IntPtr.Zero;
System.IntPtr lpRemoteBuffer = System.IntPtr.Zero;
System.IntPtr lpLocalBuffer = System.IntPtr.Zero;
try
{
lvCol = new LV_COLUMN();
lpLocalBuffer = System.Runtime.InteropServices.Marshal.AllocHGlobal(dwBufferSize);
hProcess = OpenProcess(Win32ProcessAccessType.AllAccess, false, processId);
if (hProcess == System.IntPtr.Zero)
throw new System.ApplicationException("Failed to access process!");
lpRemoteBuffer = VirtualAllocEx(hProcess, System.IntPtr.Zero, dwBufferSize, Win32AllocationTypes.MEM_COMMIT, Win32MemoryProtection.PAGE_READWRITE);
if (lpRemoteBuffer == System.IntPtr.Zero)
throw new System.SystemException("Failed to allocate memory in remote process");
lvCol.mask = LVCF_TEXT;
lvCol.pszText = (System.IntPtr)(lpRemoteBuffer.ToInt32() + System.Runtime.InteropServices.Marshal.SizeOf(typeof(LV_COLUMN)));
lvCol.cchTextMax = 500;
lvCol.iOrder = Column;
lvCol.iSubItem = Column;
bSuccess = WriteProcessMemoryGETCOLUMN(hProcess, lpRemoteBuffer, ref lvCol, (uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(LV_COLUMN)), out bytesWrittenOrRead);
if (!bSuccess)
throw new System.SystemException("Failed to write to process memory");
SendMessage(hwnd, LVM_GETCOLUMNW, (System.IntPtr)Column, lpRemoteBuffer);
bSuccess = ReadProcessMemory(hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize, out bytesWrittenOrRead);
if (!bSuccess)
throw new System.SystemException("Failed to read from process memory");
retval = System.Runtime.InteropServices.Marshal.PtrToStringUni((System.IntPtr)(lpLocalBuffer.ToInt32() + System.Runtime.InteropServices.Marshal.SizeOf(typeof(LV_COLUMN))));
}
finally
{
if (lpLocalBuffer != System.IntPtr.Zero)
System.Runtime.InteropServices.Marshal.FreeHGlobal(lpLocalBuffer);
if (lpRemoteBuffer != System.IntPtr.Zero)
VirtualFreeEx(hProcess, lpRemoteBuffer, 0, Win32AllocationTypes.MEM_RELEASE);
if (hProcess != System.IntPtr.Zero)
CloseHandle(hProcess);
}
return retval;
}

GetQueuedCompletionStatus blocking file to open c#

I am trying to convert a driver client from c to c#. I am able to make it work completely in c but as I am converting it in c# it is blocking the file to open the code for your reference is as follows:
[DllImport("fltLib.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
public static extern unsafe int FilterGetMessage(
IntPtr hPort,
IntPtr lpMessageBuffer,
int dwMessageBufferSize,
void* Overlapped
);
IntPtr buf = Marshal.AllocHGlobal(
Marshal.SizeOf(msg.MessageHeader));
OVERLAPPED povlp;
Marshal.StructureToPtr(msg.MessageHeader, buf, false);
var sizeUserRec = Marshal.SizeOf(typeof(OVERLAPPED));
var userRec = Marshal.AllocHGlobal(sizeUserRec);
povlp.Internal = UIntPtr.Zero;
povlp.InternalHigh = UIntPtr.Zero;
povlp.Offset = 0;
povlp.OffsetHigh = 0;
povlp.Pointer = IntPtr.Zero;
povlp.hEvent = IntPtr.Zero;
headerSize = Marshal.SizeOf(msg.MessageHeader);
msgsize += headerSize;
unsafe
{
status = FilterGetMessage(
portPtr,
buf,
msgsize,
&povlp
);
}
[DllImport("kernel32.dll")]
static extern bool GetQueuedCompletionStatus(IntPtr CompletionPort, ref uint
lpNumberOfBytes, ref IntPtr lpCompletionKey, ref IntPtr lpOverlapped,
uint dwMilliseconds);
static _OVERLAPPED Ovlp;
Console.WriteLine("int sddc");
_SCANNER_NOTIFICATION notification;
_SCANNER_REPLY_MESSAGE replyMessage;
IntPtr pOvlp;
pOvlp = Marshal.AllocHGlobal(Marshal.SizeOf(Ovlp));
Marshal.StructureToPtr(Ovlp, pOvlp, false);
_SCANNER_MESSAGE message;
bool result;
uint outSize = 3435973836, milisec = 0xFFFFFFFF;
int hr;
IntPtr key = IntPtr.Zero;
result = GetQueuedCompletionStatus(Context.Completion, ref outSize, ref key, ref pOvlp,uint.MaxValue); //error line
if (!result)
{
Console.WriteLine("Not valid");
}

Get ToolTip Text from Icon in System Tray

I'm trying to read the ToolTip text from the system tray for an application that is not my own. Basically just what I figure will be the easiest way to pull some status information.
What would be the easiest way to pull the ToolTip text using C#?
Let's start with finding systray window handle:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
static IntPtr GetSystemTrayHandle()
{
IntPtr hWndTray = FindWindow("Shell_TrayWnd", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null);
return hWndTray;
}
}
}
return IntPtr.Zero;
}
Systray window is a toolbar class, you need to get information for a single icon:
private static unsafe bool GetTBButton(IntPtr hToolbar, int i, ref TBBUTTON tbButton, ref string text, ref IntPtr ipWindowHandle)
{
// One page
const int BUFFER_SIZE = 0x1000;
byte[] localBuffer = new byte[BUFFER_SIZE];
UInt32 processId = 0;
UInt32 threadId = User32.GetWindowThreadProcessId(hToolbar, out processId);
IntPtr hProcess = Kernel32.OpenProcess(ProcessRights.ALL_ACCESS, false, processId);
if (hProcess == IntPtr.Zero) { Debug.Assert(false); return false; }
IntPtr ipRemoteBuffer = Kernel32.VirtualAllocEx(
hProcess,
IntPtr.Zero,
new UIntPtr(BUFFER_SIZE),
MemAllocationType.COMMIT,
MemoryProtection.PAGE_READWRITE);
if (ipRemoteBuffer == IntPtr.Zero) { Debug.Assert(false); return false; }
// TBButton
fixed (TBBUTTON* pTBButton = &tbButton)
{
IntPtr ipTBButton = new IntPtr(pTBButton);
int b = (int)User32.SendMessage(hToolbar, TB.GETBUTTON, (IntPtr)i, ipRemoteBuffer);
if (b == 0) { Debug.Assert(false); return false; }
// this is fixed
Int32 dwBytesRead = 0;
IntPtr ipBytesRead = new IntPtr(&dwBytesRead);
bool b2 = Kernel32.ReadProcessMemory(
hProcess,
ipRemoteBuffer,
ipTBButton,
new UIntPtr((uint)sizeof(TBBUTTON)),
ipBytesRead);
if (!b2) { Debug.Assert(false); return false; }
}
// button text
fixed (byte* pLocalBuffer = localBuffer)
{
IntPtr ipLocalBuffer = new IntPtr(pLocalBuffer);
int chars = (int)User32.SendMessage(hToolbar, TB.GETBUTTONTEXTW, (IntPtr)tbButton.idCommand, ipRemoteBuffer);
if (chars == -1) { Debug.Assert(false); return false; }
// this is fixed
Int32 dwBytesRead = 0;
IntPtr ipBytesRead = new IntPtr(&dwBytesRead);
bool b4 = Kernel32.ReadProcessMemory(
hProcess,
ipRemoteBuffer,
ipLocalBuffer,
new UIntPtr(BUFFER_SIZE),
ipBytesRead);
if (!b4) { Debug.Assert(false); return false; }
text = Marshal.PtrToStringUni(ipLocalBuffer, chars);
if (text == " ") text = String.Empty;
}
Kernel32.VirtualFreeEx(
hProcess,
ipRemoteBuffer,
UIntPtr.Zero,
MemAllocationType.RELEASE);
Kernel32.CloseHandle(hProcess);
return true;
}
Now, all you have to do is iterate through buttons and get data:
IntPtr _ToolbarWindowHandle = GetSystemTrayHandle();
UInt32 count = User32.SendMessage(_ToolbarWindowHandle, TB.BUTTONCOUNT, 0, 0);
for (int i = 0; i < count; i++)
{
TBBUTTON tbButton = new TBBUTTON();
string text = String.Empty;
IntPtr ipWindowHandle = IntPtr.Zero;
bool b = GetTBButton(_ToolbarWindowHandle, i, ref tbButton, ref text, ref ipWindowHandle);
}
In case anyone comes across this thread and has the same need, I posted a thread asking how to properly implement the code example and received a lot of help, and a working solution here:
Trouble implementing code example using PInvoke Declarations

Categories