2009-04-22 5 views
17

J'utiliseComment utiliser utiliser la liaison tardive pour obtenir une instance Excel?

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

pour obtenir une instance Excel à l'aide de sa poignée, que je reçois de l'ID de l'instance Excel processus.

Voici comment il ressemble quand j'utilise ces fonctions

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;

Cette paix code fonctionne très bien, mais le seul problème est que je devais ajouter une référence à l'Office 2003 assemblys Interop.

Comme vous pouvez le voir, le dernier paramètre de la fonction est la raison pour laquelle je devais ajouter la référence au Pias, donc ma question est de savoir s'il existe un moyen d'éviter l'utilisation des Assemblages Interop, j'ai essayé avec liaison tardive mais peut-être que je l'ai fait mal parce que je n'ai pas été en mesure de le faire fonctionner.

Répondre

25

Première: La liaison tardive en C# est assez douloureuse. Il vaut mieux l'éviter. Deuxièmement: La liaison tardive en C# est une douleur. Utilisez le PIA! Ok, cela étant dit, voici ce que vous devez faire pour utiliser la liaison tardive: Supprimer la référence aux PIA Office 2003 et ajouter à la place une importation COM de l'interface requise par AccessibleObjectFromWindow, soit l'interface Excel.Window:

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

vous pouvez récupérer cette interface à l'aide d'un outil comme Reflector (ou en appuyant simplement sur F12 du type Excel.Window tandis que la référence à l'Excel PIA est encore dans votre projet)

cela fait, vous aurez modifier la signature de AccessibleObjectFromWindow pour correspondre à l'importation ExcelWindow Interface:

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

Enfin, vous devez utiliser la réflexion pour obtenir le de l'objet ExcelWindow objet Excel.Application:

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

Si votre code va faire beaucoup d'appels en L'OM d'Excel, il pourrait être plus facile d'utiliser VB avec Option Strict désactivé (ou attendez C# 4.0 ;-). Ou, si vous ne souhaitez pas modifier C#, il peut être judicieux de créer une classe wrapper pour les appels de liaison tardive.


Échantillon complet

Voici un échantillon entièrement fonctionnel (basé sur un article par 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)); 
         } 
        } 
       } 
      } 
     } 
    } 
} 

Et ce serait la même solution sans PIA en VB (Notez que les appels OM sont beaucoup plus lisibles, cependant, le code pour avoir accès à l'OM serait le même):

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

Cela me fait mal à la tête en la regardant. –

+7

+1 Travail vraiment impressionnant, divo. Et montré dans VB ainsi que C#? Je suis désolé que nous sommes limités à voter une seule fois. –

+2

Merci beaucoup, Mike dit que c'est un travail impressionnant, c'est exactement ce dont j'avais besoin. – Vic

0

Ne pas.

Je sais que cela semble banal, mais VB est beaucoup, beaucoup plus facile à utiliser que C# lorsque vous travaillez avec Excel. Même si vous utilisez PIA au lieu d'une liaison tardive, il vaut mieux utiliser VB.

(Note:. Tous ces commentaires deviendront instantanément tort quand C# 4 est libéré)

5

Utilisez cette définition AccessibleObjectFromWindow à la place:

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

+1, fonctionne très bien pour obtenir une instance de 'Access.Application' sans utiliser de PIAs ou de définir une interface fictive. – Heinzi

3

Le code dans la première réponse a fonctionné comme un charme. Voici la même chose pour Word, plus un peu d'action dynamique .NET 4.0 en bas.

// 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

Je connais son vieux calme, mais je reçois ** hr = -2147467259 **, Savez-vous pourquoi il pourrait être ainsi? –

Questions connexes