Get Instance of Excel Application with C# by Handle

Get instance of Excel application with C# by Handle

Use the following code to get the first running instance of Excel:

oExcelApp =  (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");

Example

public Excel.Application StartExcel()
{
Excel.Application instance = null;
try
{
instance = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
}
catch (System.Runtime.InteropServices.COMException ex)
{
instance = new Excel.ApplicationClass();
}

return instance;
}

Retrieve Excel application from process id

You can use the following code to access all running Excel instances and display the Window Handle they use:

[DllImport("ole32.dll")]
private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);

private void button1_Click(object sender, EventArgs e)
{
IRunningObjectTable lRunningObjectTable = null;
IEnumMoniker lMonikerList = null;

try
{
// Query Running Object Table
if (GetRunningObjectTable(0, out lRunningObjectTable) != 0 || lRunningObjectTable == null)
{
return;
}

// List Monikers
lRunningObjectTable.EnumRunning(out lMonikerList);

// Start Enumeration
lMonikerList.Reset();

// Array used for enumerating Monikers
IMoniker[] lMonikerContainer = new IMoniker[1];

IntPtr lPointerFetchedMonikers = IntPtr.Zero;

// foreach Moniker
while (lMonikerList.Next(1, lMonikerContainer, lPointerFetchedMonikers) == 0)
{
object lComObject;
lRunningObjectTable.GetObject(lMonikerContainer[0], out lComObject);

// Check the object is an Excel workbook
if (lComObject is Microsoft.Office.Interop.Excel.Workbook)
{
Microsoft.Office.Interop.Excel.Workbook lExcelWorkbook = (Microsoft.Office.Interop.Excel.Workbook)lComObject;
// Show the Window Handle
MessageBox.Show("Found Excel Application with Window Handle " + lExcelWorkbook.Application.Hwnd);
}
}
}
finally
{
// Release ressources
if (lRunningObjectTable != null) Marshal.ReleaseComObject(lRunningObjectTable);
if (lMonikerList != null) Marshal.ReleaseComObject(lMonikerList);
}
}

Get Excel.Application object from Process or hwnd in .NET

Answered on another SO post:

How to get Excel instance or Excel instance CLSID using the Process ID?

How to Get Active Excel Instance?

For all possibilities to get hold of or to start Excel see this blog post by Andrew Whitechapel:

Launching Office Apps Programmatically

If you can get a window handle of the "active" Excel instance you might want to try AccessibleObjectFromWindow which will give you access to the OM.

How to get window or process handle of a certain Excel process?

Although all the answers I got did not help, thanks for the answers anyway. I now found a workaround by myself:

I now created a worker process which periodically checks for excel processes which are older than one minute. Since my excel processing should never last longer than one minute, I can be sure that excel processes older than one minute are done, so I can kill this process.

An other way which comes now to my mind is to get a list of all excel processes before you instantiate your excel process. Then after instantiating you check again. The pid which is new, is the new excel process. Remember to put a lock statement around this to void multithreading issues.

Accessing an open Excel Workbook in C#

Instead of instantiating a new instance, check for an existing one:

try
{
Microsoft.Office.Interop.Excel.Application app =
System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
}
catch
{
// Excel is not running.
}

How can I get the ProcessID (PID) for a hidden Excel Application instance

Application.Hwnd has a valid window handle that you can use to get the process for the associated Excel application even when the application has no visible workbook window.

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

Process GetExcelProcess(Microsoft.Office.Interop.Excel.Application excelApp)
{
int id;
GetWindowThreadProcessId(excelApp.Hwnd, out id);
return Process.GetProcessById(id);
}

void TerminateExcelProcess(Microsoft.Office.Interop.Excel.Application excelApp)
{
var process = GetExcelProcess(excelApp);
if (process != null)
{
process.Kill();
}
}

private void button1_Click(object sender, EventArgs e)
{
// Create Instance of Excel
Microsoft.Office.Interop.Excel.Application oXL = new Microsoft.Office.Interop.Excel.Application();
try
{
// Work with oXL
}
finally
{
TerminateExcelProcess(oXL);
}
}

Note: This question has some answers that explain why the Excel process will not terminate when you are finished working with it from C# via automation (unless you are very pedantic about making sure you release every reference to any object you use explicitly).

How to use use late binding to get excel instance?

First: Late binding in C# is quite a pain. It's best to avoid it.
Second: Late binding in C# is a pain. Use the PIA!

Ok, that being said, here is what you need to do in order to use late binding: Remove the reference to the Office 2003 PIAs and instead add a COM import of the interface required by AccessibleObjectFromWindow, i.e. the Excel.Window interface:

[Guid("00020893-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ExcelWindow
{
}

You can retrieve this interface using a tool like Reflector (or by simply pressing F12 on the type Excel.Window while the reference to the Excel PIA is still in your project)

That being done you will have to modify the signature of AccessibleObjectFromWindow to match the imported ExcelWindow interface:

[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, out ExcelWindow ptr);

Finally, you must use reflection to get the Excel.Application object from the ExcelWindow object:

object xlApp = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);

If your code is going to make a lot of calls into Excel's OM it might be easier to use VB with Option Strict turned off (or wait for C#4.0 ;-). Or, if you don't want to change from C#, it might be a good idea to create a wrapper class for the late binding calls.


Full Sample

Here is a fully functional sample (based on an article by Andrew Whitechapel):

using System;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace ExcelLateBindingSample
{
/// <summary>
/// Interface definition for Excel.Window interface
/// </summary>
[Guid("00020893-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ExcelWindow
{
}

/// <summary>
/// This class is needed as a workaround to http://support.microsoft.com/default.aspx?scid=kb;en-us;320369
/// Excel automation will fail with the follwoing error on systems with non-English regional settings:
/// "Old format or invalid type library. (Exception from HRESULT: 0x80028018 (TYPE_E_INVDATAREAD))"
/// </summary>
class UILanguageHelper : IDisposable
{
private CultureInfo _currentCulture;

public UILanguageHelper()
{
// save current culture and set culture to en-US
_currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
}

public void Dispose()
{
// reset to original culture
System.Threading.Thread.CurrentThread.CurrentCulture = _currentCulture;
}
}

class Program
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, out ExcelWindow ptr);

public delegate bool EnumChildCallback(int hwnd, ref int lParam);

[DllImport("User32.dll")]
public static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam);

[DllImport("User32.dll")]
public static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);

public static bool EnumChildProc(int hwndChild, ref int lParam)
{
StringBuilder buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
if (buf.ToString() == "EXCEL7")
{
lParam = hwndChild;
return false;
}
return true;
}

static void Main(string[] args)
{
// Use the window class name ("XLMAIN") to retrieve a handle to Excel's main window.
// Alternatively you can get the window handle via the process id:
// int hwnd = (int)Process.GetProcessById(excelPid).MainWindowHandle;
//
int hwnd = (int)FindWindow("XLMAIN", null);

if (hwnd != 0)
{
int hwndChild = 0;

// Search the accessible child window (it has class name "EXCEL7")
EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows(hwnd, cb, ref hwndChild);

if (hwndChild != 0)
{
// 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}");
ExcelWindow ptr;

int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);

if (hr >= 0)
{
// We successfully got a native OM IDispatch pointer, we can QI this for
// an Excel Application using reflection (and using UILanguageHelper to
// fix http://support.microsoft.com/default.aspx?scid=kb;en-us;320369)
//
using (UILanguageHelper fix = new UILanguageHelper())
{
object xlApp = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);

object version = xlApp.GetType().InvokeMember("Version", BindingFlags.GetField | BindingFlags.InvokeMethod | BindingFlags.GetProperty, null, xlApp, null);
Console.WriteLine(string.Format("Excel version is: {0}", version));
}
}
}
}
}
}
}

And this would be the same solution without PIAs in VB (Note that OM call are much more readable; however, the code to get access to the OM would be the same):

Option Strict Off

Imports System.Globalization
Imports System.Runtime.InteropServices
Imports System.Text

Module ExcelLateBindingSample

''' <summary>
''' Interface definition for Excel.Window interface
''' </summary>
<Guid("00020893-0000-0000-C000-000000000046"), _
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface ExcelWindow
End Interface

''' <summary>
''' This class is needed as a workaround to http://support.microsoft.com/default.aspx?scid=kb;en-us;320369
''' Excel automation will fail with the follwoing error on systems with non-English regional settings:
''' "Old format or invalid type library. (Exception from HRESULT: 0x80028018 (TYPE_E_INVDATAREAD))"
''' </summary>
Class UILanguageHelper
Implements IDisposable

Private _currentCulture As CultureInfo

Public Sub New()
' save current culture and set culture to en-US
_currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture
System.Threading.Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
End Sub

Public Sub Dispose() Implements System.IDisposable.Dispose
'reset to original culture
System.Threading.Thread.CurrentThread.CurrentCulture = _currentCulture
End Sub

End Class

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
End Function

<DllImport("Oleacc.dll")> _
Private Function AccessibleObjectFromWindow(ByVal hwnd As Integer, ByVal dwObjectID As UInt32, ByVal riid() As Byte, ByRef ptr As ExcelWindow) As Integer
End Function

Public Delegate Function EnumChildCallback(ByVal hwnd As Integer, ByRef lParam As Integer) As Boolean

<DllImport("User32.dll")> _
Public Function EnumChildWindows(ByVal hWndParent As Integer, ByVal lpEnumFunc As EnumChildCallback, ByRef lParam As Integer) As Boolean
End Function

<DllImport("User32.dll")> _
Public Function GetClassName(ByVal hWnd As Integer, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
End Function

Public Function EnumChildProc(ByVal hwndChild As Integer, ByRef lParam As Integer) As Boolean
Dim buf As New StringBuilder(128)
GetClassName(hwndChild, buf, 128)
If buf.ToString() = "EXCEL7" Then
lParam = hwndChild
Return False
End If
Return True
End Function

Sub Main()
' Use the window class name ("XLMAIN") to retrieve a handle to Excel's main window.
' Alternatively you can get the window handle via the process id:
' Dim hwnd As Integer = CInt(Process.GetProcessById(excelPid).MainWindowHandle);
'
Dim hwnd As Integer = CInt(FindWindow("XLMAIN", Nothing))

If hwnd <> 0 Then
Dim hwndChild As Integer = 0

' Search the accessible child window (it has class name "EXCEL7")
Dim cb As New EnumChildCallback(AddressOf EnumChildProc)
EnumChildWindows(hwnd, cb, hwndChild)

If hwndChild <> 0 Then
' 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 OBJID_NATIVEOM As UInteger = &HFFFFFFF0&
Dim IID_IDispatch As New Guid("{00020400-0000-0000-C000-000000000046}")
Dim ptr As ExcelWindow

Dim hr As Integer = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ptr)

If hr >= 0 Then
' We successfully got a native OM IDispatch pointer, we can QI this for
' an Excel Application using reflection (and using UILanguageHelper to
' fix http://support.microsoft.com/default.aspx?scid=kb;en-us;320369)
'
Using fixCrash As New UILanguageHelper
Console.WriteLine(String.Format("Excel version is: {0}", ptr.Application.Version))
End Using
End If
End If
End If

End Sub

End Module

How to connect an open excel application in net.core 3.1?

We can have a look to source code of Marshal class in .NET Framework 4.X and copy GetActiveObject method implementation to projects of .NET Core and .NET 5 and .NET 6. It works.

public static class ExMarshal
{
internal const String OLEAUT32 = "oleaut32.dll";
internal const String OLE32 = "ole32.dll";

[System.Security.SecurityCritical] // auto-generated_required
public static Object GetActiveObject(String progID)
{
Object obj = null;
Guid clsid;

// Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if
// CLSIDFromProgIDEx doesn't exist.
try
{
CLSIDFromProgIDEx(progID, out clsid);
}
// catch
catch (Exception)
{
CLSIDFromProgID(progID, out clsid);
}

GetActiveObject(ref clsid, IntPtr.Zero, out obj);
return obj;
}

//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
[DllImport(OLE32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
[DllImport(OLE32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

//[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)]
[DllImport(OLEAUT32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void GetActiveObject(ref Guid rclsid, IntPtr reserved, [MarshalAs(UnmanagedType.Interface)] out Object ppunk);
}


Related Topics



Leave a reply



Submit