2009-04-22 71 views
17

我使用如何使用後期綁定來獲取excel實例?

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

使用他的把柄,這是我從Excel實例的進程ID去得到一個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主互操作程序集。

正如你所看到的,函數中的最後一個參數是我需要添加引用到Pias的原因,所以我的問題是如果有避免使用Interop Assemblies的方法,我嘗試過後期綁定,但也許我一直在做錯了,因爲我沒有能夠使它的工作。

回答

25

第一:C#中的後期綁定是一個相當痛苦的事情。最好避免它。其次:C#中的後期綁定是一種痛苦。使用PIA!

好吧,這就是說,爲了使用後期綁定,您需要執行以下操作:刪除對Office 2003 PIA的引用,並添加對AccessibleObjectFromWindow所需接口(即Excel.Window接口)所需的COM導入:

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

您可以檢索使用像Reflector工具這個接口(或通過簡單的類型Excel.Window按下F12鍵,而參考的Excel PIA仍然在你的項目)

那正在做,你將有修改的簽名匹配進口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可能更容易使用VB關閉Option Strict(或等待C#4.0 ;-)。或者,如果您不想從C#中更改,爲後期綁定調用創建包裝類可能是一個好主意。


全樣本

這裏是一個全功能樣品(由安德魯白教堂基於一個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)); 
         } 
        } 
       } 
      } 
     } 
    } 
} 

而且沒有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

這讓我的腦袋受到傷害只是看着它。 – 2009-04-22 23:51:13

+7

+1令人印象深刻的作品,divo。並顯示在VB以及C#?對不起,我們僅限於投票一次。 – 2009-04-23 13:09:21

+2

非常感謝,正如Mike所說這是一項令人印象深刻的工作,這正是我所需要的。 – Vic 2009-04-23 14:19:18

0

不要。

我知道這聽起來很陳腐,但使用Excel時VB比C#更容易使用。即使你使用PIA而不是全部使用後期綁定,你仍然最好使用VB。

(注:C#4釋放時,所有這些評論將成爲瞬間錯誤)

5

使用這個定義AccessibleObjectFromWindow代替:

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

在第一個答案的代碼工作就像一個魅力。對Word來說,這是一樣的東西,再加上一些底層的.NET 4.0 Dynamic動作。

// 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); 
      } 
     } 
    } 
} 
相關問題