Long Running Timer increasing Private Bytes in C# - c#

I created an application which runs in background and make use of 4 Timers for different purposes. On elapsed event of timers task is accomplished but Memory Private Bytes are growing.
I read about Timer classes and it is said that Timers must be disposed after once task is completed but the issue is timer must run in background to accomplish task.
class myservice
{
public void Start()
{
Timer tActiveWin = new Timer();
tActiveWin.Interval = TimeSpan.FromSeconds(2).TotalMilliseconds;
tActiveWin.Elapsed += TActiveWin_Elapsed;
tActiveWin.AutoReset = true;
tActiveWin.Enabled = true;
}
private void TActiveWin_Elapsed(object sender, ElapsedEventArgs e)
{
var win = new WindowEvents().GetActiveWindow();
Console.WriteLine(win.activewindowtitle);
}
class WindowEvents
{
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public JsonAppEvents.Activewindow GetActiveWindow()
{
JsonAppEvents.Activewindow activeWin = new JsonAppEvents.Activewindow();
IntPtr hWnd = GetForegroundWindow();
int processID = 0;
int threadID = GetWindowThreadProcessId(hWnd, out processID);
using (Process p = Process.GetProcessById(processID))
{
StringBuilder text = new StringBuilder(256);
if (GetWindowText(hWnd, text, 256) > 0)
{
text.ToString();
}
activeWin.activewindowfullpath = p.MainModule.FileName;
activeWin.activewindowtitle = p.MainWindowTitle;
activeWin.time = p.StartTime.ToString("ddd, dd MMM yyyy HH:mm:ss");
activeWin.activewindowdescription = p.MainModule.ModuleName;
p.Dispose();
hWnd = IntPtr.Zero;
processID = 0;
threadID = 0;
text.Clear();
text = null;
}
return activeWindow.Result;
}
class JsonAppEvents
{
public class Activewindow
{
public string activewindowfullpath { get; set; }
public string activewindowdescription { get; set; }
public string time { get; set; }
public string activewindowtitle { get; set; }
}
}
I also did searches on Garbage Collection working and since in C# we can not control when memory will be claimed, I am stuck with more findings on it. Any clue on it will help.

Unlike the SafeFileHandle, unsafe handles aren't disposed automatically. So
use the CloseHandle() Win32 API to discard unsafe handles. Size of a handle depends on the target architecture - it is either 32 or 64 bits. So declare the function and use it as follows:
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern bool CloseHandle([In] IntPtr Handle);
.
.
.
IntPtr hWnd = GetForegroundWindow();
.
.
.
CloseHandle(hWnd);

Related

timeBeginPeriod not working on Intel Comet Lake CPU (i5 10400H)

I have some operations in my application which rely on short timers. Using the example code below I have timers firing every ~5ms as required.
On an Intel i5 10400H CPU the timings are observed to be off, and the callback occurs after ~15ms (or a multiple of 15). Using the ClockRes sysinternals tool shows that the machine has a system timer resolution of 15ms even when run after the call to timeBeginPeriod(1) made in the code below.
Using https://cms.lucashale.com/timer-resolution/ to set the resolution to the maximum supported value (0.5ms) does not change the behaviour of the example code.
From what I can see the machine is using the Invariant TSC acpi timer, and forcing it to use HPET (with bcdedit /set useplatformclock true and rebooting) did not change the behaviour.
I can't see anything in the CPU documentation or errata that would explain this.
I don't know where the problem lies and if it is something that is fixable on my end, any ideas?
Edit: Having this program (DPC Latency Checker) open results in the timer queue firing when expected, so it's solveable.
Example code:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
using (new TimePeriod(1))
RunTimer();
}
public static void RunTimer()
{
var completionEvent = new ManualResetEvent(false);
var stopwatch = Stopwatch.StartNew();
var i = 0;
var previous = 0L;
using var x = TimerQueue.Default.CreateTimer((s) =>
{
if (i > 100)
completionEvent.Set();
i++;
var now = stopwatch.ElapsedMilliseconds;
var gap = now - previous;
previous = now;
Console.WriteLine($"Gap: {gap}ms");
}, "", 10, 5);
completionEvent.WaitOne();
}
}
public class TimerQueueTimer : IDisposable
{
private TimerQueue MyQueue;
private TimerCallback Callback;
private object UserState;
private IntPtr Handle;
internal TimerQueueTimer(
TimerQueue queue,
TimerCallback cb,
object state,
uint dueTime,
uint period,
TimerQueueTimerFlags flags)
{
MyQueue = queue;
Callback = cb;
UserState = state;
bool rslt = TQTimerWin32.CreateTimerQueueTimer(
out Handle,
MyQueue.Handle,
TimerCallback,
IntPtr.Zero,
dueTime,
period,
flags);
if (!rslt)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating timer.");
}
}
~TimerQueueTimer()
{
Dispose(false);
}
public void Change(uint dueTime, uint period)
{
bool rslt = TQTimerWin32.ChangeTimerQueueTimer(MyQueue.Handle, ref Handle, dueTime, period);
if (!rslt)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error changing timer.");
}
}
private void TimerCallback(IntPtr state, bool bExpired)
{
Callback.Invoke(UserState);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private IntPtr completionEventHandle = new IntPtr(-1);
public void Dispose(WaitHandle completionEvent)
{
completionEventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
this.Dispose();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
bool rslt = TQTimerWin32.DeleteTimerQueueTimer(MyQueue.Handle,
Handle, completionEventHandle);
if (!rslt)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error deleting timer.");
}
disposed = true;
}
}
}
public class TimerQueue : IDisposable
{
public IntPtr Handle { get; private set; }
public static TimerQueue Default { get; private set; }
static TimerQueue()
{
Default = new TimerQueue(IntPtr.Zero);
}
private TimerQueue(IntPtr handle)
{
Handle = handle;
}
public TimerQueue()
{
Handle = TQTimerWin32.CreateTimerQueue();
if (Handle == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating timer queue.");
}
}
~TimerQueue()
{
Dispose(false);
}
public TimerQueueTimer CreateTimer(
TimerCallback callback,
object state,
uint dueTime,
uint period)
{
return CreateTimer(callback, state, dueTime, period, TimerQueueTimerFlags.ExecuteInPersistentThread);
}
public TimerQueueTimer CreateTimer(
TimerCallback callback,
object state,
uint dueTime,
uint period,
TimerQueueTimerFlags flags)
{
return new TimerQueueTimer(this, callback, state, dueTime, period, flags);
}
private IntPtr CompletionEventHandle = new IntPtr(-1);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(WaitHandle completionEvent)
{
CompletionEventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
Dispose();
}
private bool Disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!Disposed)
{
if (Handle != IntPtr.Zero)
{
bool rslt = TQTimerWin32.DeleteTimerQueueEx(Handle, CompletionEventHandle);
if (!rslt)
{
int err = Marshal.GetLastWin32Error();
throw new Win32Exception(err, "Error disposing timer queue");
}
}
Disposed = true;
}
}
}
public enum TimerQueueTimerFlags : uint
{
ExecuteDefault = 0x0000,
ExecuteInTimerThread = 0x0020,
ExecuteInIoThread = 0x0001,
ExecuteInPersistentThread = 0x0080,
ExecuteLongFunction = 0x0010,
ExecuteOnlyOnce = 0x0008,
TransferImpersonation = 0x0100,
}
public delegate void Win32WaitOrTimerCallback(
IntPtr lpParam,
[MarshalAs(UnmanagedType.U1)] bool bTimedOut);
static public class TQTimerWin32
{
[DllImport("kernel32.dll", SetLastError = true)]
public extern static IntPtr CreateTimerQueue();
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool DeleteTimerQueue(IntPtr timerQueue);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool DeleteTimerQueueEx(IntPtr timerQueue, IntPtr completionEvent);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool CreateTimerQueueTimer(
out IntPtr newTimer,
IntPtr timerQueue,
Win32WaitOrTimerCallback callback,
IntPtr userState,
uint dueTime,
uint period,
TimerQueueTimerFlags flags);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool ChangeTimerQueueTimer(
IntPtr timerQueue,
ref IntPtr timer,
uint dueTime,
uint period);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool DeleteTimerQueueTimer(
IntPtr timerQueue,
IntPtr timer,
IntPtr completionEvent);
}
public sealed class TimePeriod : IDisposable
{
private const string WINMM = "winmm.dll";
private static TIMECAPS timeCapabilities;
private static int inTimePeriod;
private readonly int period;
private int disposed;
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeGetDevCaps(ref TIMECAPS ptc, int cbtc);
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeBeginPeriod(int uPeriod);
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeEndPeriod(int uPeriod);
static TimePeriod()
{
int result = timeGetDevCaps(ref timeCapabilities, Marshal.SizeOf(typeof(TIMECAPS)));
if (result != 0)
{
throw new InvalidOperationException("The request to get time capabilities was not completed because an unexpected error with code " + result + " occured.");
}
}
internal TimePeriod(int period)
{
if (Interlocked.Increment(ref inTimePeriod) != 1)
{
Interlocked.Decrement(ref inTimePeriod);
throw new NotSupportedException("The process is already within a time period. Nested time periods are not supported.");
}
if (period < timeCapabilities.wPeriodMin || period > timeCapabilities.wPeriodMax)
{
throw new ArgumentOutOfRangeException("period", "The request to begin a time period was not completed because the resolution specified is out of range.");
}
int result = timeBeginPeriod(period);
if (result != 0)
{
throw new InvalidOperationException("The request to begin a time period was not completed because an unexpected error with code " + result + " occured.");
}
this.period = period;
}
internal static int MinimumPeriod
{
get
{
return timeCapabilities.wPeriodMin;
}
}
internal static int MaximumPeriod
{
get
{
return timeCapabilities.wPeriodMax;
}
}
internal int Period
{
get
{
if (this.disposed > 0)
{
throw new ObjectDisposedException("The time period instance has been disposed.");
}
return this.period;
}
}
public void Dispose()
{
if (Interlocked.Increment(ref this.disposed) == 1)
{
timeEndPeriod(this.period);
Interlocked.Decrement(ref inTimePeriod);
}
else
{
Interlocked.Decrement(ref this.disposed);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct TIMECAPS
{
internal int wPeriodMin;
internal int wPeriodMax;
}
}
}
This seem to be an issue with windows 10 2004. I would guess that it has nothing to do with the processor/motherboard.
A possible workaround might be to use a stopwatch and spinwait on a thread. This would be inadvisable for regular consumer applications since it would consume a full thread, but might be feasible if you have full control of the system.
I encountered the exact same problem under Windows 10 2004. Previous versions did not seem to exhibit the same behavior. CreateTimerQueueTimer does not seem to honor timeBeginPeriod anymore and its minimum period seems to be 15ms (good old 15 ms...).
There are a few people complaining about this problem around, but not a lot. (see this forum entry for example.
I do not know if this is a bug introduced in v2004, or a power-saving "feature" that got sneaked past us.
That being said, official documentation never linked TimerQueueTimers and timeBeginPeriod, so if might have been a bug to begin with that they honored the timeBeginPeriod setting.
In any case, I ended up re-implementing a TimerQueue on top of timeBeginPeriod/timeSetEvent to achieve the required timer frequency.
Running into the same problem, I'm using CreateTimerQueueTimer. What still works is timeSetEvent. You'll loose some precision as it's in whole milliseconds, but it's better than nothing.

Cannot Populate TreeView Control with Win32 Messages in CSharp

I'm working with TreeView Control in Windows Forms in C#.
Since actual population part of TreeView Control takes a lot of time it freezes the UI. So I'm attempting to do the population using PostMessage Win32 API from a background thread but I found that the Treeview isn't getting inserted with Items.
So I moved the code from background thread to main thread. But then also the Insert Item Code is not working. I got similar code working with TreeView in C++ and trying to do the same thing with C# using interop routines.
I'm not going the usual C# way of treeView1.Nodes.Add("...") because it freezes the UI even if I follow the Delegate method and BackgroundWorker method for populating UI controls from another thread. I'm giving the code I use below. Can some one please help to find the issue with the code.
Also Please note for the TreeView Control I'm using my own simple class derived from TreeView class, where I have overriden the WndProc method to verify the flow of Windows Messages and I can see the messages(TVM_INSERTITEM) are actually getting through but still the item is not getting populated
Also I have got similar interop code working fine from Background Thread for ListView Control but my attempts with TreeView haven't succeeded so far.
Form Class Code
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace UpdateTreeViewFromAnotherThread
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public uint lParam;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVINSERTSTRUCT
{
public IntPtr hParent;
public IntPtr hInsertAfter;
public TVITEM item;
}
public enum TreeViewInsert
{
TVI_ROOT = -0x10000,
}
[Flags]
public enum TreeViewItemMask
{
TVIF_TEXT = 0x0001,
}
public partial class Form1 : Form
{
const int TV_FIRST = 0x1100;
IntPtr tvInsItemPtr;
TVINSERTSTRUCT tvins;
IntPtr handleTreeView;
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
public enum TreeViewMessage
{
TVM_INSERTITEM = TV_FIRST + 50,
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
handleTreeView = treeView1.Handle;
//treeView1.Nodes.Add("hello");
PopulateTree(handleTreeView);
}
public void PopulateTree(IntPtr handle)
{
tvins = new TVINSERTSTRUCT();
tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;
// Set the text of the item.
string productName = "Product";
string value = productName;
byte[] buffer = new byte[100];
buffer = Encoding.Unicode.GetBytes(value + "\0");
tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);
tvins.hParent = IntPtr.Zero;
tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
PostMessage(treeView1.Handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
//SendMessage(treeView1.Handle, (int)TreeViewMessage.TVM_INSERTITEM, 0, tvInsItemPtr);
}
}
}
MyTreeView Class Code
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace UpdateTreeViewFromAnotherThread
{
class MyTreeView:TreeView
{
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x1132)
{
TVINSERTSTRUCT anotherTVInsertStruct;
anotherTVInsertStruct = (TVINSERTSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(TVINSERTSTRUCT));
string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVInsertStruct.item.pszText);
}
if(m.Msg == 0x113F)
{
TVITEM anotherTVItem;
anotherTVItem = (TVITEM)Marshal.PtrToStructure(m.LParam, typeof(TVITEM));
string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVItem.pszText);
}
base.WndProc(ref m);
//Trace.WriteLine(m.Msg.ToString() + ", " + m.ToString());
}
}
}
Update_1
Prevented NM_CUSTOMDRAW for Treeview using the below code. Thanks to the code atlink.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_REFLECT + WM_NOTIFY:
{
NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
switch ((int)nmhdr.code)
{
case NM_CUSTOMDRAW:
NMTVCUSTOMDRAW nmTvDraw = (NMTVCUSTOMDRAW)m.GetLParam(typeof(NMTVCUSTOMDRAW));
switch (nmTvDraw.nmcd.dwDrawStage)
{
case CDDS_ITEMPREPAINT:
m.Result = (IntPtr)CDRF_DODEFAULT;
break;
}
Marshal.StructureToPtr(nmTvDraw, m.LParam, false);
return;
}
break;
}
}
base.WndProc(ref m);
}
So Now If I change my earlier PopulateTree function (note Thread.Sleep()) and its invocation to a background thread as below it will not freeze the UI during the population process
private void button1_Click(object sender, EventArgs e)
{
handleTreeView = treeView1.Handle;
Thread backgroundThread = new Thread(() => PopulateTree(handleTreeView));
backgroundThread.Start();
}
public void PopulateTree(IntPtr handle)
{
for(int i =0; i< 1000; i++)
{
tvins = new TVINSERTSTRUCT();
tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;
// Set the text of the item.
string productName = "Product_" + i.ToString();
string value = productName;
byte[] buffer = new byte[100];
buffer = Encoding.Unicode.GetBytes(value + "\0");
tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);
tvins.hParent = IntPtr.Zero;
tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
PostMessage(handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
Thread.Sleep(1000);
}
}
Thanks Jimi and MikiD I was able to produce same non-freezing UI behaviour using the BeginUpdate and BeginInvoke approach. I changed my code as below
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => PopulateTree());
}
private async void PopulateTree()
{
for(int i = 0;i< 1000;i++)
{
treeView1.BeginInvoke( (MethodInvoker)delegate ()
{
treeView1.BeginUpdate();
treeView1.Nodes.Add("Product_" + i.ToString());
treeView1.EndUpdate();
}
);
System.Threading.Thread.Sleep(1000);
}
}

Get a windows title by it's process name without main window

I have a problem I'm trying to get a window's title by it's process name and I can't do it here s what I tried :
Process[] p = Process.GetProcessesByName("Acrobat");
Console.WriteLine(p[0].MainWindowTitle);
Console.ReadLine();
But the problem is that I can only get it only if the associated process does have a main window. How can I make it working ?
The main goal is that I've a method named BringToFront()
But this method ask for a caption name which is "thenameofthePDF.pdf - Adobe Acrobat Pro (Yes, acrobat is running with an opened pdf)
I would like to bring to front my Acrobat window..
but for this I need the name of the windows as my method is asking for the caption.
Here is the entire code at the moment:
class Program
{
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(int hWnd);
[DllImport("user32.dll")]
public static extern int FindWindow(string lpClassName, string lpWindowName);
private static void BringToFront(string className, string CaptionName)
{
SetForegroundWindow(FindWindow(className, CaptionName));
}
static void Main(string[] args)
{
// BringToFront("Acrobat", "mypdf.pdf - Adobe Acrobate Pro");
Process[] p = Process.GetProcesses();
foreach (var process in p)
{
Console.WriteLine(process.MainWindowTitle);
}
Console.ReadLine();
}
}
Did you read the manual, does the following apply?
A process has a main window associated with it only if the process has a graphical interface. If the associated process does not have a main window (so that MainWindowHandle is zero), MainWindowTitle is an empty string (""). If you have just started a process and want to use its main window title, consider using the WaitForInputIdle method to allow the process to finish starting, ensuring that the main window handle has been created. Otherwise, the system throws an exception.
One possible solution is to enumerate all top-level windows and pick the one you are interested in. In your case this would be all windows with a class of AcrobatSDIWindow and a window title starting with your document name.
class Program
{
public class SearchData
{
public string ClassName { get; set; }
public string Title { get; set; }
private readonly List<IntPtr> _result = new List<IntPtr>();
public List<IntPtr> Result
{
get { return _result; }
}
}
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, ref SearchData data);
private delegate bool EnumWindowsProc(IntPtr hWnd, ref SearchData data);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
public static bool EnumProc(IntPtr hWnd, ref SearchData searchData)
{
var sbClassName = new StringBuilder(1024);
GetClassName(hWnd, sbClassName, sbClassName.Capacity);
if (searchData.ClassName == null || Regex.IsMatch(sbClassName.ToString(), searchData.ClassName))
{
var sbWindowText = new StringBuilder(1024);
GetWindowText(hWnd, sbWindowText, sbWindowText.Capacity);
if (searchData.Title == null || Regex.IsMatch(sbWindowText.ToString(), searchData.Title))
{
searchData.Result.Add(hWnd);
}
}
return true;
}
static void Main(string[] args)
{
var searchData = new SearchData
{
ClassName = "AcrobatSDIWindow",
Title = "^My Document\\.pdf.*"
};
EnumWindows(EnumProc, ref searchData);
var firstResult = searchData.Result.FirstOrDefault();
if (firstResult != IntPtr.Zero)
{
SetForegroundWindow(firstResult);
}
}
}
If you dump them all out using GetProcesses() it appears that the window title will have 'Adobe Reader', prefixed by the name of the PDF if one is open. So you might need to do that and walk the array instead.
For example if I have UserManual.pdf open and I run the code below it displays UserManual.pdf - Adobe Reader on the console:
Process[] p = Process.GetProcesses();
String Title = String.Empty;
for (var i = 0; i < p.Length; i++)
{
Title = p[i].MainWindowTitle;
if (Title.Contains(#"Adobe"))
Console.WriteLine(Title);
}

How to log actions in compact framework?

I'm working on a windows mobile project using compact framework.
One thing I have to do is log when users perform actions, this can mean any action from pressing a button to using the barcode scanner. The time it happened also needs to be logged.
My plan is to override all controls to include logging functionality built into them but this might not be the right way to go about it, seems like a very tedious thing to do..
Is there a better way?
I would go with IL Weaving. Here is a library that I would recommend: http://www.sharpcrafters.com/aop.net/msil-injection What it does is that you mark your class with an attribute and you can intercept all function calls. In this interception you would put in your logging logic.
I'd say it depends greatly on the definition of "action". I'd be highly inclined to see if the (undocumented) QASetWindowsJournalHook API would work. It's probably going to grab most of what you want, with not a lot of code required. A native example of usage can be found on Codeproject here.
SetWindowsHook with WH_JOURNALRECORD might also be worth a look. Yeah, I know it's "unsupported" but it works just fine, and it's unlikely to be removed from a device you've got fielded (plus it's been in the OS for at least 10 years).
Some P/Invoke declarations, all derived from pwinuser.h, for them both are as follows:
[StructLayout(LayoutKind.Sequential)]
public struct JournalHookStruct
{
public int message { get; set; }
public int paramL { get; set; }
public int paramH { get; set; }
public int time { get; set; }
public IntPtr hwnd { get; set; }
}
internal enum HookType
{
JournalRecord = 0,
JournalPlayback = 1,
KeyboardLowLevel = 20
}
internal enum HookCode
{
Action = 0,
GetNext = 1,
Skip = 2,
NoRemove = 3,
SystemModalOn = 4,
SystemModalOff = 5
}
public const int HC_ACTION = 0;
public const int LLKHF_EXTENDED = 0x1;
public const int LLKHF_INJECTED = 0x10;
public const int LLKHF_ALTDOWN = 0x20;
public const int LLKHF_UP = 0x80;
public const int VK_TAB = 0x9;
public const int VK_CONTROL = 0x11;
public const int VK_ESCAPE = 0x1B;
public const int VK_DELETE = 0x2E;
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(HookType idHook, HookProc lpfn, IntPtr hMod, int
[DllImport("coredll.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("coredll.dll", SetLastError = true)]
public static extern int CallNextHookEx(IntPtr hhk, HookCode nCode, IntPtr wParam, IntPtr
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr QASetWindowsJournalHook(HookType nFilterType, HookProc pfnFilterProc, ref JournalHookStruct pfnEventMsg);
Would writing these messages to a log file not solve your problem?
#if PocketPC
private static string _appPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
#else
private static string _appPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Application.CompanyName);
#endif
public const int KILOBYTE = 1024;
public static string ErrorFile { get { return _appPath + #"\error.log"; } }
public static void Log(string message)
{
if (String.IsNullOrEmpty(message)) return;
using (FileStream stream = File.Open(ErrorFile, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(stream, Encoding.UTF8, KILOBYTE))
{
sw.WriteLine(string.Format("{0:MM/dd/yyyy HH:mm:ss} - {1}", DateTime.Now, message));
}
}
}
You could have issues though if you have threading going on and multiple routines try to write at the same time. In that case, you could add additional logic to lock the routine while it is in use.
That's how I do it, anyway.
By the #if regions, you can see this is also used by my Windows PC applications.

How can I tell which application of openoffice is running?

I want to look in the list of processes to determine if OpenOffice Calc is running or if OpenOffice Writer is running. With QuickStart off, I get a scalc.exe and an swriter.exe so its simple.
However when the quick start is on I just get soffice.bin and soffice.exe
Is there a way of asking those processes which application is running?
I think there is no way around checking the window title. You would have to enumerate all windows and check whether the title ends with "- OpenOffice.org Writer" or "- OpenOffice.org Calc" etc.
The following short sample would check whether Writer is running:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Sample
{
public class Window
{
public string Title { get; set; }
public int Handle { get; set; }
public string ProcessName { get; set; }
}
public class WindowHelper
{
/// <summary>
/// Win32 API Imports
/// </summary>
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder title, int size);
[DllImport("user32.dll")]
private static extern int EnumWindows(EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
private List<Window> _openOfficeWindows;
public List<Window> GetOpenOfficeWindows()
{
_openOfficeWindows = new List<Window>();
EnumWindowsProc ewp = new EnumWindowsProc(EvalWindow);
EnumWindows(ewp, 0);
return _openOfficeWindows;
}
private bool EvalWindow(IntPtr hWnd, int lParam)
{
if (!IsWindowVisible(hWnd))
return (true);
uint lpdwProcessId;
StringBuilder title = new StringBuilder(256);
GetWindowThreadProcessId(hWnd, out lpdwProcessId);
GetWindowText(hWnd, title, 256);
Process p = Process.GetProcessById((int)lpdwProcessId);
if (p != null && p.ProcessName.ToLower().Contains("soffice"))
{
_openOfficeWindows.Add(new Window() { Title = title.ToString(), Handle = hWnd.ToInt32(), ProcessName = p.ProcessName });
}
return (true);
}
}
class Program
{
static void Main(string[] args)
{
WindowHelper helper = new WindowHelper();
List<Window> openOfficeWindows = helper.GetOpenOfficeWindows();
foreach (var item in openOfficeWindows)
{
if (item.Title.EndsWith("- OpenOffice.org Writer"))
{
Console.WriteLine("Writer is running");
}
}
}
}
}

Categories

Resources