VBAで関数の参照を得るには?

解決


灰色猫  2005-11-24 07:32:02  No: 128151

VBAで、VBA内で利用できるプロシージャの参照を得る方法はないものでしょうか?
AddressOfで取得したポインタはVBA内では利用できず、困っています。
VBSにはGetRefなる関数がありますので、それが利用できるのですが。

用途としては、プロシージャAに引数としてプロシージャB/Cの参照を渡して、
プロシージャA内で渡されたプロシージャを実行したいのです。

現在は、実行用プロシージャのみを持つクラスモジュールを複数作成し、
それぞれにプロシージャBまたはCを実装しています。
このクラスモジュールのオブジェクトをプロシージャAに渡すことで
上記機能を実現しています。
この方法は汎用性は高いのですが、クラス定義やオブジェクトの管理が面倒なのです。
クラスにデフォルトのメソッドが設定できればまだ助かるのですが・・・

皆様のお知恵をお貸し下さい。


ガッ  2005-11-24 08:04:25  No: 128152

CallByName()とかかな。
でも個人的には「"関数インタフェイス"のようなインタフェイスを実装させようかなぁ…」と考えますが。

※トリッキーなことをすればもっとできると思いますが。


灰色猫  2005-11-24 08:59:06  No: 128153

今回もお返事ありがとうございます。

実は私も先ほどCallByNameにたどり着きました。
(ずいぶん遠回りした上、偶然見つけたようなものですが・・・)
実行速度が気になるところですが、
スクリプトっぽいこの手段もお手軽で良いかなと思っています。

あと、お恥ずかしながら"関数インタフェイス"という単語がピンと来ないのですが、
関数を表すキーワードと引数のカタマリを組にして渡すと、希望の関数を実行してくれる
というCallByNameの汎用(非オブジェクト)プロシージャ対応版、みたいなものでしょうか?
それともC++(STL)の関数オブジェクトとアルゴリズムの組み合わせみたいなものを意味されていますでしょうか?
JDKのFunctor Interfacesが一番近いような気もしますが・・・
なにぶんJavaは疎いものではっきりとは分かりません。

おすすめの実装もぜひ教えてください。


ガッ  2005-11-24 09:25:32  No: 128154

> 実は私も先ほどCallByNameにたどり着きました。
> (ずいぶん遠回りした上、偶然見つけたようなものですが・・・)
> 実行速度が気になるところですが、
> スクリプトっぽいこの手段もお手軽で良いかなと思っています。
そうですね。手軽さで言ったらコレが一番ですね。

> あと、お恥ずかしながら"関数インタフェイス"という単語がピンと来ないのですが、
これは私が考えたものなので、ピンとこなければわざわざ説明するまでもない拙いものです。
オブジェクトの多様性を利用してメソッドを呼び出す(灰色猫さん案)か、
それとも"あるインタフェイス"を実装しているオブジェクトのメソッドを呼び出す(私案)かの違いです。

でもやっぱり、実行時に事前に決定できないメソッドを呼び出す事態が発生する場合は、
CallByNameによる呼び出しが簡単です。

で、もっとゴリゴリしたい場合はコチラが参考になります"かも":
http://www.momo-lab.net/kibidango/cbbs.cgi?mode=one&namber=96&type=76&space=60&no=0
(単語としては、InvokeHookとか)

…でもTLBINF32.DLLの無い環境もありますしねぇ…
では。


ガッ  2005-11-24 09:43:45  No: 128155

面白そうなので試してみました。

'VB6
Option Explicit
'1 [プロジェクト]-[参照設定]からTypeLib Informationを選択
'2 実行
Private Sub Form_Load()
    Dim i       As Long
    Dim u       As New TLIApplication
    Dim fID     As Long
    Dim tim     As Single
    
    'クリア
    Call Me.someMethod("")
    
    '普通に呼び出す
    tim = Timer
    For i = 1 To 10000
        Call Me.someMethod(CStr(i))
    Next
    Debug.Print Timer - tim
    
    'クリア
    Call Me.someMethod("")
    
    'InvokeHookで呼び出す
    fID = u.InvokeID(Me, "someMethod")
    tim = Timer
    For i = 1 To 10000
        Call u.InvokeHook(Me, fID, INVOKE_FUNC, CStr(i))
    Next
    Debug.Print Timer - tim
    
End Sub

Public Function someMethod(ByVal s As String) As String
    '簡単なパブリックメソッド
    Static pSt  As String
    If Len(s) = 0 Then
        someMethod = pSt
        pSt = ""
    Else
        pSt = pSt & s & vbNewLine
    End If
End Function


灰色猫  2005-11-24 10:14:35  No: 128156

お返事感謝です。

なんだかVBらしくない話に発展してしまっている感がありますね。
ありがたいことです。

> それとも"あるインタフェイス"を実装しているオブジェクトのメソッドを呼び出す(私案)かの違いです。
そちらの方が実行時バインディングが少なく高速に動きそうな気配がしますね。

実はCollectionのソートの大小判定用の述語としても利用していたのですが、
汎用性と引き替えのあまりの遅さにショックを受け、別の手段を模索していたのです。
STL方式のごり押しでは、やはり限界がありました。

TLBINF32.DLLを利用した方法は非常に興味深いです。
早速いろいろ調べてみます。

しかし、(言語的に)純粋なロジックを実装しようとすると、
逆にどんどん深みにはまっていく気がします。
これがVBなのかもしれませんね・・・。

ともあれ、ご回答ありがとうございました。
独学もそろそろ厳しくなってきましたので、また質問させていただくかもしれませんが、
そのときもよろしくお願いいたします。


灰色猫  2005-11-24 10:36:39  No: 128157

のんびり書いていたのでソースを書いてくださったことに気づきませんでした。
失礼いたしました。

VBAだとMeのところで引っかかりますね。
クラス・フォームでないので使用できないのは道理ですが・・・

この場合、渡すべきなのはExcel.Applicationオブジェクトの参照でしょうか?
・・・というか、そもそもVBAではこの書式では利用できなかったりします?


灰色猫  2005-11-24 11:06:01  No: 128158

たびたび失礼します。

VBAでも、クラスオブジェクトに配置すればこの書式で呼ぶことができますね。
失礼いたしました。

参照設定をする必要がありますが、実に(メンバ)関数ポインタに近い感覚で関数が呼び出せます。
標準モジュールの関数を呼び出す方法も探ってみようと思います。

早速活用してみます。
感謝、感謝です。


ガッ  2005-11-24 17:36:00  No: 128159

えーと、寝てました(ぉ

> 標準モジュールの関数を呼び出す方法も探ってみようと思います。
こちらの方はCか何かで作った方が簡単かもしれませんね。


魔界の仮面弁士  2005-11-24 19:44:37  No: 128160

既に解決済みなので、蛇足情報という事で。

Access VBAなら、Application.Evalメソッド、
Excel VBAなら、Application.Evaluateメソッドなんてのもありますね。

とはいえ、これらは標準モジュールのプロシージャに対しては使えても、
クラスのメソッドを直接認識する事はできないので、今回の目的には
利用できないでしょうけれども。

もう一つの方法は、Microsoft Script Controlを用いる方法。
    Set X = CreateObject("ScriptControl")
    X.Language = "VBScript"
のようにして生成してから、AddObjectでクラスのインスタンスを渡し、
それを Evalメソッドや ExecuteStatementメソッドで呼び出す、と。

……まぁ、Office 2000以上であれば、既に解決済みの案となった
「CallByName」が使えるので、それ以前のバージョンのVBAで無い限り、
わざわざ ScriptControl を生成する意味もないでしょうけれど、それでも
> VBSにはGetRefなる関数がありますので、それが利用できるのですが。
で書かれている VBS の GetRefを VBAから利用するような目的には
使えるかもしれません。
(VBSのGetRefは、デフォルトメソッドを持ったオブジェクトを返します)

> で、もっとゴリゴリしたい場合はコチラが参考になります"かも":
おぉ、懐かしい投稿。(^^;

>     Dim u       As New TLIApplication
宣言時のNewは、VB6ではあまりお奨めできないので、
  Dim u As TLIApplication
  Set u = New TLIApplication
の方が良いかも知れません。参照設定を使わない場合は、
  Set u = CreateObject("TLI.TLIApplication")
ですね。

> …でもTLBINF32.DLLの無い環境もありますしねぇ…
Office 2000あたりには付属していますね。
http://support.microsoft.com/dllhelp/default.aspx?l=55&fid=79655

ちなみに、VB6付属のTLBINF32.DLLは再頒布可能。
ヘルプは下記。
http://support.microsoft.com/kb/224331/ja

> 標準モジュールの関数を呼び出す方法も探ってみようと思います。
できない事も無いですが、素直にVC等を用いた方が無難ですよ。
http://nienie.com/~masapico/other.html


灰色猫  2005-11-25 11:16:49  No: 128161

やっと帰ってこれました。

ガッさん
> こちらの方はCか何かで作った方が簡単かもしれませんね。
全くもってその通りです。
C++はそこそこ使えるものの、Officeとの連携となると門外漢なのです。
何か良い資料はないものでしょうか・・・

魔界の仮面弁士さん
ディープなお返事感謝です。
どれもこれも危険な香りが漂ってきますね。
特にバイナリコードで使う関数ポインタは、一度は試してみたいです。
・・・怖くて実務では絶対に使えないですが。

> 宣言時のNewは、VB6ではあまりお奨めできないので、
これは、VB6から.NETへのMSの移行資料かなにかで読んだような。
煩雑なオブジェクト定義周りがすっきりすると思っていたのですが、
書きやすい書式なのに無駄が多いと知り、酷くがっかりした覚えが。
せめて引数無しでも良いからコンストラクタがあれば、
すっきりしたソースが書けるのですけれども・・・無い物ねだりですね。

InvokeHookが、速度と汎用性を兼ねているようなので、実際に利用してみるつもりです。
でも、If(Select)での分岐の方が遙かに速い、なんてオチだったら哀しいですね・・・。

可搬性を気にして今まで参照設定を避けて通っていましたが、
これも悪くないですね。
こちらも色々調べてみることにします。


ガッ  2005-11-25 17:54:38  No: 128162

> せめて引数無しでも良いからコンストラクタがあれば、
> すっきりしたソースが書けるのですけれども・・・無い物ねだりですね。
↓の様に強引に作れば、とりあえずコンストラクタの様に使えますかも。

※VB6
-IConstructor.cls-
Option Explicit
'interface IConstructor
Public Function IConstructor(ByVal arg As Variant) As IConstructor
End Function

-Class1.cls-
Option Explicit
'class Class1
Implements IConstructor
Private v1      As Long
Private v2      As Long

Private Function IConstructor_IConstructor(ByVal arg As Variant) As IConstructor
    Set IConstructor_IConstructor = Me
    v1 = arg(LBound(arg))
    v2 = arg(LBound(arg) + 1)
End Function

Public Sub Class1Method()
    MsgBox v1 & v2
End Sub

-Class2.cls-
Option Explicit
'class Class2
Implements IConstructor
Private v1      As String
Private v2      As String

Private Function IConstructor_IConstructor(ByVal arg As Variant) As IConstructor
    Set IConstructor_IConstructor = Me
    v1 = arg(LBound(arg))
    v2 = arg(LBound(arg) + 1)
End Function

Public Function Class2Method()
    MsgBox v1 & v2
End Function

-Module1.bas-
Option Explicit
'module Module1
Public Function PublicConstructor(ByVal icons As IConstructor, ParamArray args() As Variant) As IConstructor
    Set PublicConstructor = icons.IConstructor(args)
End Function

-Form1.frm-
Option Explicit

Private Sub Form_Load()
    Dim c1      As Class1
    Dim c2      As Class2
    Set c1 = PublicConstructor(New Class1, 10, 20)
    Set c2 = PublicConstructor(New Class2, "ab", "12")
    Call c1.Class1Method
    Call c2.Class2Method
End Sub

※VB6ってインタフェイスは継承できるのですが、
  処理を継承できないので難物ですよね…


灰色猫  2005-11-26 10:28:03  No: 128163

Creator関数を標準モジュールに列挙していた自分が情けないです・・・
やっぱりコンストラクタはクラスモジュール内にあると安心できますね。

ちなみに、PublicConstructorの第一引数は、
implementsしたインターフェースへの暗黙の型変換なのでしょうか?

> ※VB6ってインタフェイスは継承できるのですが、
  処理を継承できないので難物ですよね…

Publicな継承はインタフェイスで、
Privateな継承は明示的なクラスの埋込で、
それぞれ実現するってことなのでしょうね。
これもまた一つの継承の形なのかなーと妙に納得しています。
委譲が言語で提供されていれば言うこと無しだったのですが。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加