読者です 読者をやめる 読者になる 読者になる

Accessが複数開かれている時にGetObject()する方法

その他

はじめに

  • ExcelAccessをはじめとしたOffice系のソフトは,オートメーションなる機構が準備されているので,各々連携することが可能である.
  • 具体的には,CreateObject()やGetObject()といった関数で,各ソフトのApplicationオブジェクトに相当する参照を取得でき,例えば,ExcelからAccessにクエリを投げて,得られた結果をワークシートに転送する,といったマクロを作成可能である.
  • いや,MSDN*2は最初の引数に使いたいファイルパスを指定すれば選択できそうな雰囲気ではあるが,試してみたところ,新しいインスタンスが開かれ,取得された参照が破棄されると同時にインスタンスも閉じられた.うまくやる方法があるのかもしれないが,ここでは操作するインスタンスを指定できないとしよう.
  • そこで,ウィンドウタイトルから開いているファイルを推測し,そのウィンドウのハンドルを基にしてAccessibleObjectFromWindow()関数を呼び出し,目的のインスタンスの参照をつかむ方法が,首尾良く行きそうなので報告する.

AccessibleObjectFromWindow()関数

MSDN*3を参照.

こいつでウィンドウハンドルから,インスタンスの参照を得られる.VBAでは引数の扱いに注意が必要な点がある.

  • 第2引数は取得したいオブジェクトのIDを渡す.Office系の参照が欲しい場合はOBJID_NATIVEOMを指定する.
  • 第3引数の REFIID riid はGUID渡すべきであるが,VBAのGUIDを指定すると怒られる.Declareな関数では使えないらしい?ので,同等の構造体をユーザー定義する.Office系の参照を得るためには,IDispatchに相当するGUIDを渡す.

EnumWindows()関数

MSDN*4を参照.

ウィンドウを列挙してくれる.第1引数にコールバック関数を渡す必要があるが,なんと,VBAはAddressOf演算子を使えば渡せるらしい.

GetWindowText()関数

MSDN*5を参照.

ウィンドウタイトルを得る.VBAでは固定長文字列を宣言する方法があって,宣言の後に「* 256」を付け足せば良いらしい.

Access複数開かれている場合に参照を取得する例

  • Access複数開かれている場合を例にした実装を張る.
  • Main()からEnumWindows()を呼び出し,目的のインスタンス("tesuto.accdb"なるファイルを開いたAccessインスタンス)が存在したら,フォーム"hoge"を開く.
  • EnumWindows()のコールバック関数では,ウィンドウタイトルを調べ,目的のファイルを開いていそうだったら,Accessインスタンスの参照の取得を試みる.
Option Explicit

Public AccessWndHandle As Long
Public AccessApplication As Object

Private Const OBJID_NATIVEOM = &HFFFFFFF0

Private Declare Function GetWindowText Lib "User32" _
Alias "GetWindowTextA" _
(ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal Hwnd As Long, ByVal dwId As Long, _
ByRef riid As UUID, ByRef ppvObject As Object) As Long
     
Private Declare Function EnumWindows Lib "User32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Private Type UUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

Public Function EnumWindowsCallBackFunc(ByVal Hwnd As Long, ByVal lParam As Long) As Boolean
    Dim title As String * 256

    If False = GetWindowText(Hwnd, title, Len(title)) Then
        GoTo Continue
    End If
    
    If InStr(title, "tesuto") > 0 And InStr(title, "Access") > 0 Then
        Dim IID_IDispatch As UUID
        With IID_IDispatch
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        
        If 0 <> AccessibleObjectFromWindow(Hwnd, OBJID_NATIVEOM, IID_IDispatch, AccessApplication) Then
            GoTo Continue
        End If
        
        AccessWndHandle = Hwnd
        EnumWindowsCallBackFunc = False
        Exit Function
    End If
    
Continue:
    EnumWindowsCallBackFunc = True
    Exit Function
End Function

Public Sub Main()
    AccessWndHandle = 0
    Call EnumWindows(AddressOf EnumWindowsCallBackFunc, 0)
    
    If AccessWndHandle = 0 Then
        Exit Sub
    End If
    
    AccessApplication.DoCmd.OpenForm "hoge"
End Sub

*1:インスタンスとは,現在コンピュータで動いているプログラム,と取って欲しい.例えば,タスクマネージャで「EXCEL.EXE」が複数存在するとき,複数インスタンスが存在すると表現する.

*2:GetObject 関数 (VBScript)

*3:AccessibleObjectFromWindow

*4:EnumWindows 関数

*5:GetWindowText 関数