プラットフォーム呼び出しでのデリゲートのポインタ値が毎回異なるのはなぜ?


ポント  2006-03-02 12:36:59  No: 94447

開発環境:Visual Stuio .NET 2003
OS:Windows XP Professional SP2

VB.NETからDLLの呼び出し経由でVB.NET内にある関数のアドレス値を取得するプログラムを作り、アドレスを取得したところ、呼び出しごとに毎回異なったアドレス値が返ってきました。関数のアドレスはガベージコレクションが行われない限り変わらないと考えているのですが、なぜ変わってしまうのか考えても埒が明かずご質問させていただくにいたりました。

より詳しくは、まずVC++ .NETによって関数のポインタを受け取り、その値をそのまま戻り値として返すWin32 DLLを次のように作りました。

// FuncPtrMaker.cpp : DLL アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "FuncPtrMaker.h"
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
           )
{
  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
    break;
  }
    return TRUE;
}

FUNCPTRMAKER_API ULONG GetFunctionAddress(ULONG inCallBackFunc)
{
  lcCallBackFunc = inCallBackFunc;

  return inCallBackFunc;
}

VB.NETからプラットフォーム呼び出しと解説されているMicrosoftの次のページ(http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpconinteropmarshalingoverview.asp)を参考にして、GetFunctionAddress関数を引数にデリゲートを渡して呼び出し、戻り値を次のように表示させました
(コードはプロジェクトにウィンドウズアプリケーションを選択して生成されるForm1クラス内です。Form1フォームにはButton1、Label1コントロールが配置してあります。)

    <System.Runtime.InteropServices.DllImport("FuncPtrMaker.dll")> _
    Private Shared Function GetFunctionAddress(ByVal inDelegate As DCallBackFunction) As Integer
    End Function

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Label1.Text = GetFunctionAddress(AddressOf CallBackFunction)
    End Sub

このVB.NETアプリケーションを実行させてButton1をクリックすると、クリックするごとに異なる値が表示されました。Microsoftのマーシャリングのヘルプページ(http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpguide/html/cpconhandlerefsample.asp)を参考にしながら、関数のアドレスはガベージコレクションが行われない限り変わらないと考えているのですが、ここで実際取得してみるとなぜ変わってしまうのか、考えても埒が明かずご質問させていただくにいたりました。どなたかご教授いただけると幸いです


ポント  2006-03-02 12:41:07  No: 94448

VB.NET側のコードに次のコードを付け忘れてしまいました。

    Private Delegate Sub DCallBackFunction()

    Private Sub CallBackFunction()
        Static lcCounter As Integer = 1

        Label1.Text = String.Format("呼び出し 回数:{0}", lcCounter)

        lcCounter += 1
    End Sub

ご教授よろしくお願いいたします。


K.J.K.  2006-03-02 18:44:20  No: 94449

それは、"Delegate"がMarshalされるから、でしょう。

まぁ、言葉でいろいろ書いてもわかりにくいと思うので、それが
意識できる事象を考えてみるとか。

例えば、クラスの静的なメソッド:Aに対してAddressOf演算子を
用いて、それを外部関数に渡すケースを考えてみます。この静的
なメソッドには呼び出し元のインスタンスを識別する引数は
"無い"ものとし、一方で、この静的メソッドAを外部関数に渡す
メソッド:Bは"動的"つまりインスタンスに依存するものとします。

このクラスのインスタンスを複数作り、それぞれからメソッド:Bを
呼び出すと、メソッド:Aではそれぞれのインスタンスから呼び出さ
れたことを区別できており、干渉し合いません。これは何故なので
しょうね?

VB.NETのAddressOf演算子は、VB6のそれとは全く異なります。
VB6のAddressOf演算子は、P-Codeであったとしても、メモリー上に
展開されたコードでの関数を示すポインタをただ返すだけ、です。
それに対してVB.NETでは、MSILのコードブロックを直接返すわけ
ではありません。もし仮にMSILで書かれたコードへのポインタを
返しても、それを外部の関数で直接使えるような代物ではありま
せん。なにせ"MSIL"コードなのですから。
メソッドを示すMSILのコードに対して作成されるDelegateは、
コード実体への参照だけではなく各種シグネイチャを含む新た
なるオブジェクトです。これをMarshalするのですから、結果は
異なって当然でしょう。

Delegateは.NETに於ける最も特徴的なものであると私は考えています。
これはMSILと実命令コード(x86系など)が分離されていることと表裏一体
です。


ポント  2006-03-03 12:15:26  No: 94450

ご教授ありがとうございます。

分かりやすいご教授のおかげで理解できそうな感じがしています。確かにデリゲートを生成するときに

new DCallBackFunction(AddressOf CallBackFunction)

としますし、単なる関数へのポインタではなく新しいオブジェクトを生成しているようなイメージがつかめました。新しく生成したデリゲートオブジェクト内にインスタンスや”コード実体への参照”が含まれていて、VB.NETコード内からデリゲートに登録された関数を呼び出す際にはInvoke()メソッド内で内部にある”コード実体への参照”を呼び出せば確かに関数を呼び出しできるように思いました。
さらにこのデリゲートオブジェクトをマーシャリングする際に、ネイティブコードからコールバックとして呼び出しが可能な、臨時的なネイティブコードの関数をその都度新しくメモリに作成しておいて、この臨時的な関数内に今のデリゲートオブジェクトの持つInvoke()メソッドを含ませておくことで、仮にAPI DLLへデリゲートオブジェクトがマーシャリングによって渡された関数ポインタをDLLがコールバックとしてアンマネージ的に呼び出ししても、実際の呼び出しはマーシャリングで作成された臨時的な関数が呼び出されて、その関数の中にあるInvoke()メソッドを実行させることでマネージ的に実行させることができるかもしれないと思いました。実際のデリゲートのマーシャリング動作を知らないので勝手なイメージのままに可能性を考えてみて理解しました。
まったく的外れなことをイメージしてしまっているようでしたらご指摘お願いいたします。


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

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






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