I am writing a C# application that needs to be able to tell how much time it takes for a certain application to open. I am using the Stopwatch class as my timer. Start time is easy since I set it exactly with the call to run the .exe. The problem is finding out how to time when the program is done opening. The only thing I could think of to test this is using a PerformanceCounter object and checking when CPU% is less than a certain number using a while loop.
At the moment I am using PerformanceCounter, but I am not having luck with it displaying the CPU% (it's always displaying 0). My guess for this is either that the application opens up faster than the PerformanceCounter can check for the CPU% or that the PerformanceCounter is not seeing the process name because I'm calling it too quickly (I highly doubt the latter due to the fact that I think I would get errors if this happened).
Are there any other ways to have this problem solved? Is there something I might be doing wrong that is giving me a 0 CPU% all the time? I am not looking for external tools outside of my application. Here is a sample of my code:
otherApp = new otherApplication.Application();
PerformanceCounter cpuCounter = new PerformanceCounter("Process",
"% Processor Time", "otherApplication");
//This next line is to check if PerformanceCounter object is working
//MessageBox.Show(cpuCounter.NextValue().ToString());
stopwatch.Start();
while (cpuCounter.NextValue() > 10)
{
}
stopwatch.Stop();
Edited: Changed code to say otherApp and otherApplication instead of myApp and myApplication, so that it may be easier to understand.
If your application is a window application, i.e. if it is a process with a message loop then the standard way would be to use the Process.WaitForInputIdle
method.
This method will block until the respective process has reached the idle state for the first time. This is the state when the main window of the application is created and it is okay to send messages to the application1.
The name of the method is a little bit confusing, it should really be called WaitForProcessStartupComplete.
using System;
using System.Diagnostics;
class StartupWatch
{
static void Main()
{
string application = "calc.exe";
Stopwatch sw = Stopwatch.StartNew();
Process process = Process.Start(application);
process.WaitForInputIdle();
Console.WriteLine("Time to start {0}: {1}", application, sw.Elapsed);
}
}
1Note that there might be further initialization going on in a background thread until the application is completely ready. However, being able to handle window messages is probably the clearest definition of an application being completely started.
Update:
If you need to measure the start-up time of a COM server you can still use Process.Start
and then use AccessibleWindowFromObject
to access the actual COM object for automation. The procedure is a bit complicated and you will need to know the window class name of the accessible object.
Below is a sample how you can measure the start-up time of Word and get a Word.Application
object at the same time, see the comments how you would have to adjust it to suit your COM server.
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Word = Microsoft.Office.Interop.Word;
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
public interface IDispatch
{
}
class StartupWatch
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint dwObjectID, byte[] riid, out IDispatch ptr);
public delegate bool EnumChildCallback(IntPtr hwnd, ref IntPtr lParam);
[DllImport("User32.dll")]
public static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildCallback lpEnumFunc, ref IntPtr lParam);
[DllImport("User32.dll")]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
public static bool EnumChildProc(IntPtr hwndChild, ref IntPtr lParam)
{
StringBuilder buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
if (buf.ToString() == "_WwG")
{
lParam = hwndChild;
return false;
}
return true;
}
static Word.Application GetWordApplicationObject(Process process)
{
Word.Application wordApp = null;
if (process.MainWindowHandle != IntPtr.Zero)
{
IntPtr hwndChild = IntPtr.Zero;
// Search the accessible child window (it has class name "_WwG")
// as described in http://msdn.microsoft.com/en-us/library/dd317978%28VS.85%29.aspx
//
// adjust this class name inside EnumChildProc accordingly if you are
// creating another COM server than Word
//
EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows(process.MainWindowHandle, cb, ref hwndChild);
if (hwndChild != IntPtr.Zero)
{
// We call AccessibleObjectFromWindow, passing the constant OBJID_NATIVEOM (defined in winuser.h)
// and IID_IDispatch - we want an IDispatch pointer into the native object model.
//
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
IDispatch ptr;
int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);
if (hr >= 0)
{
// possibly adjust the name of the property containing the COM
// object accordingly
//
wordApp = (Word.Application)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
}
}
}
return wordApp;
}
static void Main(string[] args)
{
Stopwatch sw = Stopwatch.StartNew();
Process process = Process.Start(@"C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE");
process.WaitForInputIdle();
Console.WriteLine("Time to start {0}: {1}", "Word", sw.Elapsed);
Word.Application wordApp = GetWordApplicationObject(process);
Console.WriteLine(string.Format("Word version is: {0}", wordApp.Version));
}
}
You may be better off using a known location in your code (a particular method that's called when all initialization work is complete) rather than relying on CPU utilization as a heuristic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With