首先:在C#中进行后期绑定非常麻烦,最好避免使用。
其次:在C#中进行后期绑定很麻烦。使用PIA!
好的,话虽如此,这里是你需要做的,才能使用后期绑定:删除对Office 2003 PIAs的引用,而是添加所需接口的COM导入,例如AccessibleObjectFromWindow
,即Excel.Window
接口:
[Guid("00020893-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ExcelWindow
{
}
您可以使用像Reflector这样的工具来检索此接口(或者在仍然引用Excel PIA的情况下,直接按F12键并选择类型Excel.Window
)
完成这一步之后,您将需要修改AccessibleObjectFromWindow
的签名以匹配导入的ExcelWindow
接口:
[DllImport("Oleacc.dll")]
static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, out ExcelWindow ptr);
最后,您必须使用反射从 ExcelWindow
对象中获取 Excel.Application
对象:
object xlApp = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
如果您的代码将频繁调用Excel的OM,那么最好使用关闭Option Strict
(或等待C#4.0 ;-)的VB。或者,如果不想从C#更改,则可以为后期绑定调用创建包装类。
完整示例
以下是一个完全功能的示例(基于Andrew Whitechapel的一篇文章):
using System;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace ExcelLateBindingSample
{
[Guid("00020893-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ExcelWindow
{
}
class UILanguageHelper : IDisposable
{
private CultureInfo _currentCulture;
public UILanguageHelper()
{
_currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
}
public void Dispose()
{
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)
{
int hwnd = (int)FindWindow("XLMAIN", null);
if (hwnd != 0)
{
int hwndChild = 0;
EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows(hwnd, cb, ref hwndChild);
if (hwndChild != 0)
{
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)
{
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