sourcetip

엑셀 인스턴스를 얻기 위해 late binding을 사용하는 방법은?

fileupload 2023. 6. 12. 21:50
반응형

엑셀 인스턴스를 얻기 위해 late binding을 사용하는 방법은?

사용 중

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

그의 핸들을 사용하여 엑셀 인스턴스를 얻는 것, 나는 엑셀 인스턴스의 프로세스 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 Primary Interop Assembly에 대한 참조를 추가해야 한다는 것입니다.

보다시피, 함수의 마지막 파라미터는 제가 Pias에 참조를 추가해야 하는 이유입니다. 그래서 제 질문은 Interop Assembly의 사용을 피할 수 있는 방법이 있다면, 저는 늦은 바인딩을 시도해 보았지만, 아마도 제가 그것을 작동시키지 못했기 때문에 잘못하고 있었을 것입니다.

첫 번째: C#에서 늦게 바인딩하는 것은 꽤 고통스럽습니다.피하는 것이 가장 좋습니다.두 번째: C#에서 늦게 결합하는 것은 고통입니다.PIA를 사용하세요!

네, 그렇긴 하지만, 늦은 바인딩을 사용하려면 다음과 같이 해야 합니다: Office 2003 PIA에 대한 참조를 제거하고 대신 필요한 인터페이스의 COM 가져오기를 추가합니다.AccessibleObjectFromWindow즉, 그Excel.Window인터페이스:

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

Reflector와 같은 도구를 사용하거나 유형에서 F12 키를 눌러 이 인터페이스를 검색할 수 있습니다.Excel.WindowExcel PIA에 대한 참조가 프로젝트에 남아 있는 동안)

그렇게 되면 당신은 서명을 수정해야 할 것입니다.AccessibleObjectFromWindow수입품과 일치하는ExcelWindow인터페이스:

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

마지막으로, 당신은 반사를 사용하여 다음을 얻어야 합니다.Excel.Application의 반대.ExcelWindow객체:

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

코드가 Excel의 OM으로 많은 호출을 할 경우 VB를 사용하는 것이 더 쉬울 수 있습니다.Option Strict꺼짐(또는 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

대신 다음과 같은 AccessibleObjectFromWindow 정의를 사용합니다.

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

첫 번째 답변의 코드는 매력적으로 작동했습니다.여기 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);
            }
        }
    }
}

하지 마.

진부하게 들리겠지만, VB는 Excel로 작업할 때 C#보다 훨씬 사용하기 쉽습니다.전체적인 지연 바인딩 대신 PIA를 사용하더라도 VB를 사용하는 것이 좋습니다.

(참고: C#4가 출시되면 이 모든 설명이 즉시 틀리게 됩니다.)

언급URL : https://stackoverflow.com/questions/779363/how-to-use-use-late-binding-to-get-excel-instance

반응형