2009-04-22 25 views
17

Tôi đang sử dụngLàm thế nào để sử dụng sử dụng cuối ràng buộc để có được ví dụ excel?

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

để có được một Excel Instance sử dụng tay cầm của mình, mà tôi nhận được từ quá trình ID của dụ excel.

Đây là cách nó trông giống như khi tôi sử dụng các chức năng

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;

hòa bình mã này hoạt động tuyệt vời nhưng vấn đề duy nhất là tôi đã phải thêm một tham chiếu đến Office 2003 chính Interop Assemblies.

Như bạn có thể thấy, thông số cuối cùng trong hàm là lý do tại sao tôi cần thêm tham chiếu đến Pias, vì vậy câu hỏi của tôi là nếu có cách tránh sử dụng Interop Assemblies, tôi đã thử ràng buộc muộn nhưng có lẽ tôi đã làm điều đó sai bởi vì tôi đã không thể làm cho nó hoạt động được.

Trả lời

25

Đầu tiên: Kết buộc trễ trong C# khá là đau. Tốt nhất là tránh nó. Thứ hai: Ràng buộc trễ trong C# là một cơn đau. Sử dụng PIA! Ok, có nghĩa là, đây là những gì bạn cần làm để sử dụng cuối ràng buộc: Loại bỏ các tham chiếu đến Office 2003 PIAs và thay vào đó thêm một COM nhập khẩu của giao diện theo yêu cầu của AccessibleObjectFromWindow, tức là giao diện Excel.Window:

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

bạn có thể lấy giao diện này sử dụng một công cụ như Reflector (hoặc bằng cách đơn giản nhấn F12 vào loại Excel.Window khi tham chiếu đến Excel PIA vẫn còn trong dự án của bạn)

Điều đó đang được thực hiện, bạn sẽ có sửa đổi chữ ký của AccessibleObjectFromWindow để phù hợp với ExcelWindow giao diện nhập khẩu:

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

Cuối cùng, bạn phải sử dụng phản ánh để có được những đối tượng Excel.Application từ ExcelWindow đối tượng:

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

Nếu mã của bạn sẽ kiếm được nhiều cuộc gọi vào Excel của OM nó có thể dễ dàng hơn để sử dụng VB với Option Strict tắt (hoặc chờ đợi cho C# 4.0 ;-). Hoặc, nếu bạn không muốn thay đổi từ C#, nó có thể là một ý tưởng tốt để tạo một lớp bao bọc cho các cuộc gọi kết buộc muộn.


Full Mẫu

Đây là một mẫu đầy đủ chức năng (dựa trên một article bởi 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)); 
         } 
        } 
       } 
      } 
     } 
    } 
} 

Và đây sẽ là giải pháp tương tự mà không PIAs trong VB (Lưu ý rằng cuộc gọi OM dễ đọc hơn nhiều, tuy nhiên, mã để truy cập vào OM sẽ giống nhau):

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

Điều đó khiến tôi đau đầu khi nhìn vào nó. –

+7

+1 Công việc thực sự ấn tượng, divo. Và được hiển thị trong VB cũng như C#? Tôi xin lỗi vì chúng tôi bị giới hạn chỉ bỏ phiếu một lần. –

+2

Cảm ơn bạn rất nhiều, như Mike nói đó là một tác phẩm ấn tượng, đó chính xác là những gì tôi cần. – Vic

0

Không.

Tôi biết nó nghe có vẻ khá, nhưng VB rất nhiều, nhiều lần dễ sử dụng hơn C# khi làm việc với Excel. Ngay cả khi bạn sử dụng PIA thay vì tất cả ra ràng buộc muộn, bạn vẫn còn tốt hơn bằng cách sử dụng VB.

(Lưu ý:. Tất cả những ý kiến ​​sẽ trở thành ngay lập tức sai khi C# 4 được phát hành)

5

Sử dụng định nghĩa này của AccessibleObjectFromWindow thay vì:

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

+1, hoạt động tuyệt vời để nhận được cá thể 'Access.Application' mà không sử dụng PIA hoặc xác định giao diện giả. – Heinzi

3

Mã trong câu trả lời đầu tiên làm việc như một nét duyên dáng. Đây là điều tương tự cho Word, cộng thêm một chút của .NET 4.0 Dynamic action ở phía dưới.

// 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); 
      } 
     } 
    } 
} 
+0

Tôi biết tuổi yên tĩnh của nó, nhưng tôi nhận được ** hr = -2147467259 **, Bạn có biết tại sao nó có thể là như vậy? –

Các vấn đề liên quan