【问题标题】:How to use use late binding to get excel instance?如何使用后期绑定获取excel实例?
【发布时间】:2009-04-22 21:42:03
【问题描述】:

我正在使用

[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 的引用的原因,所以我的问题是是否有办法避免使用互操作程序集,我已经尝试过了绑定,但也许我做错了,因为我无法使它工作。

【问题讨论】:

    标签: c# excel


    【解决方案1】:

    首先:C# 中的后期绑定非常痛苦。最好避免它。 第二:C# 中的后期绑定很痛苦。使用 PIA!

    好的,话虽如此,要使用后期绑定,您需要执行以下操作:删除对 Office 2003 PIA 的引用,而是添加AccessibleObjectFromWindow 所需接口的 COM 导入,即@987654325 @接口:

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

    您可以使用Reflector 之类的工具检索此接口(或者在您的项目中仍然引用 Excel PIA 时,只需在类型 Excel.Window 上按 F12)

    完成后,您必须修改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 的情况下使用 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 真正令人印象深刻的工作,迪沃。并在 VB 和 C# 中显示?很抱歉,我们只能投票一次。
    • 非常感谢,正如迈克所说,这是一部令人印象深刻的作品,正是我所需要的。
    • 我的赞成票只是将此答案移至 +15,但应该是 +1500。绝对是我见过的对这个主题的最佳处理。
    • @Jim:更新的链接在答案中(文章可以在这里找到:blogs.msdn.com/b/andreww/archive/2008/11/30/…
    【解决方案2】:

    改用这个 AccessibleObjectFromWindow 的定义:

        [DllImport("Oleacc.dll")]
        private static extern int AccessibleObjectFromWindow(
            int hwnd, uint dwObjectID,
            byte[] riid,
            [MarshalAs(UnmanagedType.IUnknown)]ref object ptr);
    

    【讨论】:

    • +1,非常适合在不使用 PIA 或定义虚拟接口的情况下获取 Access.Application 实例。
    【解决方案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);
                }
            }
        }
    }
    

    【讨论】:

    • 我知道它很安静,但我越来越 hr = -2147467259,你知道为什么会这样吗?
    【解决方案4】:

    不要。

    我知道这听起来很陈词滥调,但在使用 Excel 时,VB 比 C# 更容易使用很多很多倍。即使您使用 PIA 而不是全部后期绑定,您仍然最好使用 VB。

    (注意:当 C# 4 发布时,所有这些 cmets 都会立即出错。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-20
      • 1970-01-01
      • 1970-01-01
      • 2019-03-09
      相关资源
      最近更新 更多