如何使用后期绑定获取Excel实例?

19

我正在使用

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

我需要使用进程ID获取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主要互操作程序集的引用。

正如你所看到的,在函数的最后一个参数是我需要添加对PIA引用的原因,所以我的问题是是否有避免使用Interop Assemblies的方法,我已经尝试了晚期绑定,但可能我做错了什么,因为我无法使其工作。

4个回答

27

首先:在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
{
    /// <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

5
看到那个东西就让我感到头疼。 - Jonathan Allen
7
+1 真的很惊人的工作,divo。而且还展示了VB和C#的实现?很抱歉我们只能投一次赞。 - Mike Rosenblum
2
非常感谢,正如Mike所说,这是一项令人印象深刻的工作,正是我所需要的。 - Vic
我的点赞只把这个答案提升到了+15,但它应该是+1500。绝对是我见过的最好的关于这个主题的解释。 - AlfredBr
有人有 Andrew Whitechapel 文章的链接吗?对我来说已经失效了。谢谢。 - Jim Johnson
1
@Jim:更新的链接在答案中(文章可以在这里找到:http://blogs.msdn.com/b/andreww/archive/2008/11/30/starting-or-connecting-to-office-apps.aspx)。 - Dirk Vollmar

5
请使用以下AccessibleObjectFromWindow的定义:

使用此定义:

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

2
+1пјҢйқһеёёйҖӮеҗҲиҺ·еҸ–Access.Applicationе®һдҫӢиҖҢдёҚдҪҝз”ЁPIAжҲ–е®ҡд№үиҷҡжӢҹжҺҘеҸЈгҖӮ - Heinzi

3

第一个答案中的代码非常好用。以下是适用于Word的相同代码,以及底部的一些.NET 4.0动态操作。

// https://dev59.com/_HRA5IYBdhLWcg3w8SaJ
// 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,你知道为什么会这样吗? - Aniket Bhansali

0

不要。

我知道这听起来陈词滥调,但是在处理Excel时,与C#相比,VB使用起来要容易得多。即使您使用PIA而不是全面的后期绑定,仍然最好使用VB。

(注意:当C# 4发布时,所有这些评论都将立即变得错误。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接