我正在使用
[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(
int hwnd,
uint dwObjectID,
byte[] riid,
ref Excel.Window ptr);
使用他的句柄获取Excel实例,我从excel实例的进程ID中获取该句柄。
这就是我使用这些功能时的样子
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 Assemblies,我已经尝试过了绑定,但也许我一直在做错,因为我无法使它工作。
答案 0 :(得分:26)
首先:C#中的后期绑定非常痛苦。最好避免它。 第二:C#中的后期绑定是一种痛苦。使用PIA!
好的,这就是说,为了使用后期绑定,您需要执行以下操作:删除对Office 2003 PIA的引用,而是添加AccessibleObjectFromWindow
所需的接口的COM导入,即Excel.Window
界面:
[Guid("00020893-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ExcelWindow
{
}
您可以使用Reflector之类的工具检索此界面(或者只需在类型Excel.Window
上按F12,而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);
如果您的代码要对Excel的OM进行大量调用,那么在Option Strict
关闭时使用VB可能会更容易(或者等待C#4.0 ;-)。或者,如果您不想从C#更改,那么为后期绑定调用创建包装类可能是个好主意。
完整样本
这是一个功能齐全的样本(基于Andrew Whitechapel的article):
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));
}
}
}
}
}
}
}
这与VB中没有PIA的解决方案相同(请注意,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
答案 1 :(得分:5)
使用AccessibleObjectFromWindow的这个定义:
[DllImport("Oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
int hwnd, uint dwObjectID,
byte[] riid,
[MarshalAs(UnmanagedType.IUnknown)]ref object ptr);
答案 2 :(得分:3)
第一个答案中的代码就像一个魅力。以下是Word的相同内容,以及底部的一些.NET 4.0动态操作。
// http://stackoverflow.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);
}
}
}
}
答案 3 :(得分:0)
别。
我知道这听起来很陈词滥调,但在使用Excel时,VB比C#更容易使用。即使你使用PIA而不是全面的后期绑定,你仍然可以更好地使用VB。
(注意:当C#4发布时,所有这些注释都会立即出错。)