C# 如何使用后期绑定获取excel实例?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/779363/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 00:50:39  来源:igfitidea点击:

How to use use late binding to get excel instance?

c#excel

提问by Vic

I'm using

我正在使用

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

to get an Excel Instance using his handle, which I get from the process ID of the excel instance.

使用他的句柄获取 Excel 实例,这是我从 excel 实例的进程 ID 中获取的。

This is how it looks like when I use these function

这是我使用这些功能时的样子

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;

This peace of code works great but the only problem is that I had to add a reference to the Office 2003 Primary Interop Assemblies.

这种和平的代码效果很好,但唯一的问题是我必须添加对 Office 2003 主要互操作程序集的引用。

As you can see, the last param in the function is the reason why I needed to add the reference to the Pias, so my question is if there is a way of avoiding the use of Interop Assemblies, I have tried with late binding but perhaps I've been doing it wrong because I haven't been able to make it work.

如您所见,函数中的最后一个参数是我需要添加对 Pias 的引用的原因,所以我的问题是是否有办法避免使用互操作程序集,我尝试过后期绑定,但也许我一直做错了,因为我无法让它发挥作用。

采纳答案by Dirk Vollmar

First: Late binding in C# is quite a pain. It's best to avoid it. Second: Late binding in C# is a pain. Use the PIA!

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

Ok, that being said, here is what you need to do in order to use late binding: Remove the reference to the Office 2003 PIAs and instead add a COM import of the interface required by AccessibleObjectFromWindow, i.e. the Excel.Windowinterface:

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

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

You can retrieve this interface using a tool like Reflector(or by simply pressing F12 on the type Excel.Windowwhile the reference to the Excel PIA is still in your project)

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

That being done you will have to modify the signature of AccessibleObjectFromWindowto match the imported ExcelWindowinterface:

完成后,您必须修改 的签名AccessibleObjectFromWindow以匹配导入的ExcelWindow接口:

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

Finally, you must use reflection to get the Excel.Applicationobject from the ExcelWindowobject:

最后,必须使用反射Excel.ApplicationExcelWindow对象中获取对象:

object xlApp = ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);

If your code is going to make a lot of calls into Excel's OM it might be easier to use VB with Option Strictturned off (or wait for C#4.0 ;-). Or, if you don't want to change from C#, it might be a good idea to create a wrapper class for the late binding calls.

如果您的代码要对 Excel 的 OM 进行大量调用,则Option Strict在关闭的情况下使用 VB 可能会更容易(或等待 C#4.0 ;-)。或者,如果您不想从 C# 进行更改,那么为后期绑定调用创建包装类可能是个好主意。



Full Sample

完整样本

Here is a fully functional sample (based on an articleby Andrew Whitechapel):

这是一个功能齐全的示例(基于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));
                        }
                    }
                }
            }
        }
    }
}

And this would be the same solution without PIAs in VB (Note that OM call are much more readable; however, the code to get access to the OM would be the same):

这将是 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

回答by Jonathan Allen

Don't.

别。

I know it sounds trite, but VB is many, many times easier to use than C# when working with Excel. Even if you use PIA instead of all-out late binding, you are still better off using VB.

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

(Note: all these comments will become instantly wrong when C# 4 is released.)

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

回答by Samuel Hyman

Use this definition of AccessibleObjectFromWindow instead:

请改用 AccessibleObjectFromWindow 的这个定义:

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

回答by Wade Hatler

The code in the first answer worked like a charm. Here's the same thing for Word, plus a bit of .NET 4.0 Dynamic action at the bottom.

第一个答案中的代码就像一个魅力。这是 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);
            }
        }
    }
}