Как использовать позднюю привязку, чтобы получить экземпляр Excel?
Я использую
[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(
int hwnd,
uint dwObjectID,
byte[] riid,
ref Excel.Window ptr);
чтобы получить экземпляр Excel, используя его дескриптор, который я получаю из идентификатора процесса экземпляра Excel.
Вот как это выглядит, когда я использую эти функции
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
Excel.Window ptr = null;
int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM,
IID_IDispatch.ToByteArray(), ref ptr);
Object objApp = ptr.Application;
Этот кодекс прекрасно работает, но единственная проблема заключается в том, что мне пришлось добавить ссылку на основные сборки взаимодействия Office 2003.
Как вы можете видеть, последний параметр в функции - это причина, по которой мне нужно было добавить ссылку на Pias, поэтому мой вопрос заключается в том, есть ли способ избежать использования Interop Сборок, я пробовал с поздним связыванием, но, возможно, Я делал это неправильно, потому что я не смог заставить это работать.
4 ответа
Во-первых: поздняя привязка в C# - довольно большая боль. Лучше всего этого избегать. Второе: позднее связывание в C# - это боль. Используйте ПИА!
Хорошо, как говорится, вот что вам нужно сделать, чтобы использовать позднюю привязку: удалите ссылку на PIA Office 2003 и вместо этого добавьте COM-импорт интерфейса, требуемого для AccessibleObjectFromWindow
то есть Excel.Window
интерфейс:
[Guid("00020893-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ExcelWindow
{
}
Вы можете получить этот интерфейс, используя такой инструмент, как Reflector (или просто нажав F12 на типе Excel.Window
пока ссылка на Excel PIA все еще находится в вашем проекте)
После этого вам придется изменить подпись AccessibleObjectFromWindow
соответствовать импортированному ExcelWindow
интерфейс:
[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, out ExcelWindow ptr);
Наконец, вы должны использовать отражение, чтобы получить Excel.Application
объект из ExcelWindow
объект:
object xlApp = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
Если ваш код будет выполнять много вызовов в OM Excel, может быть проще использовать VB с Option Strict
выключен (или подождите C#4.0;-). Или, если вы не хотите переходить с C#, было бы неплохо создать класс-оболочку для вызовов поздней привязки.
Полный образец
Вот полностью функциональный образец (на основе статьи Эндрю Уайтчепела):
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));
}
}
}
}
}
}
}
И это было бы тем же решением без PIA в VB (обратите внимание, что вызов OM гораздо более читабелен; однако код для получения доступа к OM будет таким же):
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
Вместо этого используйте это определение AccessibleObjectFromWindow:
[DllImport("Oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
int hwnd, uint dwObjectID,
byte[] riid,
[MarshalAs(UnmanagedType.IUnknown)]ref object ptr);
Код в первом ответе работал как шарм. Вот то же самое для Word, плюс немного динамического действия.NET 4.0 внизу.
// http://stackru.com/questions/779363/how-to-use-use-late-binding-to-get-excel-instance
// ReSharper disable InconsistentNaming
using System;
using System.Runtime.InteropServices;
using System.Globalization;
using System.Reflection;
using System.Text;
namespace LateBindingWord {
/// <summary> Interface definition for Word.Window interface </summary>
[Guid("00020962-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWordWindow {
}
/// <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 readonly 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 IWordWindow 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) {
var buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
Console.WriteLine(buf.ToString());
if (buf.ToString() == "_WwG") {
lParam = hwndChild;
return false;
}
return true;
}
static void 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:
// int hwnd = (int)Process.GetProcessById(excelPid).MainWindowHandle;
// var p=Process.GetProcesses().FirstOrDefault(x => x.ProcessName=="WINWORD");
var hwnd = (int) FindWindow("OpusApp", null);
if (hwnd == 0)
throw new Exception("Can't find Word");
// Search the accessible child window (it has class name "_WwG") // http://msdn.microsoft.com/en-us/library/windows/desktop/dd317978%28v=vs.85%29.aspx
var hwndChild = 0;
var cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows(hwnd, cb, ref hwndChild);
if (hwndChild == 0)
throw new Exception("Can't find Automation Child Window");
// 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;
var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
IWordWindow ptr;
var hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);
if (hr < 0)
throw new Exception("Can't get Accessible Object");
// 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 (new UiLanguageHelper()) {
var wordApp = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
var version = wordApp.GetType().InvokeMember("Version", BindingFlags.GetField | BindingFlags.InvokeMethod | BindingFlags.GetProperty, null, wordApp, null);
Console.WriteLine("Word version is: {0}", version);
dynamic wordAppd = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
Console.WriteLine("Version: " + wordAppd.Version);
}
}
}
}
Не.
Я знаю, это звучит банально, но VB во много, много раз проще в использовании, чем C# при работе с Excel. Даже если вы используете PIA вместо полного позднего связывания, вам все равно лучше использовать VB.
(Примечание: все эти комментарии сразу же станут неверными, когда выйдет C# 4.)