一尘不染

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

c#

我在用着

[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程序集的方法,我尝试了后期绑定,但也许我一直做错了,因为我无法使其工作。


阅读 181

收藏
2020-05-19

共1个答案

一尘不染

第一:C#的后期绑定相当痛苦。最好避免这种情况。第二:在C#中后期绑定很痛苦。使用PIA!

好的,这就是说,这是使用后期绑定所需要做的:删除对Office 2003
PIA的引用,而是添加COM导入所需的接口AccessibleObjectFromWindow,即Excel.Window接口:

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

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

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

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

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

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

如果您的代码将大量调用Excel的OM,则在Option Strict关闭状态下使用VB可能会更容易(或等待C#4.0
;-)。或者,如果您不想从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));
                        }
                    }
                }
            }
        }
    }
}

在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
2020-05-19