2

現在、VBラッパークラスを介してアクセスされ(1つの関数のみ)、C#クラスから呼び出されている既存のCOMDLLがあります。

C#コードにコールバックを追加しようとしています(4つの個別のコールバック)。私が選んだアプローチは私が見つけた唯一のアプローチですが、問題があります。

「DLL'xxxx'で'InitDotNet'という名前のエントリポイントが見つかりません。

私のDLLヘッダーファイル:

extern "C"
{
#define DLL __declspec(dllexport)
typedef void (__stdcall * CB_func1)(int);
typedef void (__stdcall * CB_func2)(char *);

DLL void InitDotNet(CB_func1 func1, CB_func2 func2);
}

...

class CComInterface : public CCmdTarget
...
   afx_msg void mainCall(short parm1, LPCTSTR parm2);
...

私のDLLC++ファイル:

...
CB_func1  func1Function;
CB_func2  func2Function;
...
IMPLEMENT_DYNCREATE(CComInterface, CCmdTarget)
...
BEGIN_DISPATCH_MAP(CComInterface, CCmdTarget)
   DISP_FUNCTION(CComInterface, "mainCall", mainCall, VT_EMPTY, VTS_I2 VTS_BSTR)
END_DISPATCH_MAP()
...
IMPLEMENT_OLECREATE(CComInterface, "MyDll.Interface", ...)

...
void CComInterface::mainCall(short parm1, LPCTSTR parm2)
{
   ...

   // at various times call func1Functoin and func2Function

   ...
}

DLL void InitDotNet(CB_func1 func1, CB_func2 func2)
{
   func1Function = func1;
   func2Function = func2;
}

私のVBラッパーは次のようになります。

Public Class MyWrapperClass
   Private Shared Protocol As Object = CreateObject("MyDll.Interface")

   Public Shared Sub mainCall(ByVal parm1 As Short, ByVal parm2 As String)
      Protocol.mainCall(parm1, parm2)
   End Sub
End Class

私のC#コードは次のようになります。

...
using System.Runtime.InteropServices
namespace MyNamespace
{
   public partial class MyForm : AnotherForm
   {
      ...
      [UnmanagedFunctionPointer(CallingConvention.StdCall)]
      public delegate void func1Callback(int value);

      [UnmanagedFunctionPointer(CallingConvention.StdCall)]
      public delegate void func2Callback(string value);

      [DllImport("mycppdll.dll")]
      public static extern void InitDotNet([MarshalAs(UnmanagedType.FunctionPtr)] func1Callback f1c,
         [MarshalAs(UnmanagedType.FunctionPtr)] func2Callback f2c);
      ...
      private void MyFunc()
      {
         func1Callback f1c = 
            (value) =>
            {
               // work here
            };
         func2Callback f2c =
            (value) =>
            {
               // work here
            };

         InitDotNet(f1c, f2c);

         MyWrapperDll.MyWrapperClass.mainCall(1, "One");

}

誰かが私が間違っていることについて何か考えがありますか?

4

2 に答える 2

3

私が見る問題:

  1. InitDotNetCB_func1との代わりに時間がかかりますCB_func2。これは、プログラムの64ビットバージョンでは2つの問題です。これにより、stdcall関数のエクスポートされた名前の不一致が発生し、さらに悪いことに、InitDotNetなんらかの方法で呼び出された場合にポインターが切り捨てられる可能性があります。

  2. InitDotNetはマークされていません__stdcall。デフォルトの呼び出し規約はcdeclです。cdeclの命名規則は「アンダースコア付きのプレフィックス」であるため、エクスポートされる名前は「_InitDotNet」です。ただし、stdcallの命名規則は「接頭辞にアンダースコア、接尾辞に@を付け、その後に引数のサイズ(バイト単位)を続ける」ため、エクスポートされる名前は「_InitDotNet @ 8」になります(現在の署名は2つのlongを取ります)。DLLによってエクスポートされた関数の名前を表示するには、dumpbinやdepends.exeなどのプログラムを使用する必要があります。この不一致はInitDotNet、32ビットWindowsを想定している場合、ランタイムが検出できない理由である可能性があります。EntryPointDllImport

  3. コメントでcdhowieが指摘しているように、ネイティブコードに渡す2つのデリゲートを「存続」させる必要があります。.NETガベージコレクターは、関数ポインターがネイティブコードによって格納されていることを知ることができません。ガベージコレクターがそれらを収集しないようにするには、周囲のデリゲートへの参照を保持するか(ネイティブコードの使用よりも長持ちすることが保証されているオブジェクトのフィールドなど)、を使用しGCHandleます。を使用する場合は注意してくださいGCHandle:ピン留めされたハンドルを使用する必要はありません。コードに実際に渡される関数ポインターはスタブであり、デリゲートがガベージコレクターによって移動されても、スタブは同じ場所に残ります。ただし、デリゲートが収集されるとスタブが削除されるため、ネイティブコードがコールバックを必要としなくなるまで、デリゲートが収集されないようにすることが重要です。

于 2013-01-17T19:42:42.523 に答える
1

コールバックを渡すCOMの方法はインターフェースです。経験したように、アンマネージ関数ポインターを.NETデリゲートと一致させようとすると、複雑でエラーが発生しやすくなります。インターフェイスはデリゲートほど実用的ではありませんが、関数ポインタよりも優れています。

したがって、私があなたなら、COMDLLによってエクスポートされたCOMインターフェイスにコールバックを配置します。

(以下はIDLコードであり、C ++プロジェクトに関連付けられた.idlファイルに含める必要があります。)

interface ISomeObject : IUnknown
{
    HRESULT DoTask1([in] int i);
    HRESULT DoTask2([in] BSTR s);
}

次に、C ++プロジェクトをビルドし、C#プロジェクトからの参照としてタイプライブラリを追加します。タイプライブラリが登録されている場合は、Visual StudioのソリューションエクスプローラーペインでC#プロジェクト名を右クリックし、[参照の追加]を選択し、[ COM ]タブに移動して、タイプライブラリの名前を探し、次のように追加します。参照。

タイプライブラリへの参照を追加すると、COMインターフェイスをC#インターフェイスであるかのように使用できます。

class MyForm : AnotherForm, ISomeObject
{
      // ISomeObject methods:
    public void DoTask1(int i) { ... }
    public void DoTask2(string s) { ... }

    ...
}

次に、InitDotNetはISomeObjectポインターを受け取り、C#コードはこれを渡すことで単純に呼び出します

C ++:

ISomeObject* g_pSomeObject;

extern "C" __declspec(dllexport) void __stdcall InitDotNet(ISomeObject* o)
{
    g_pSomeObject = o;
}

C#:

[DllImport("mycppdll.dll")]
private static extern void InitDotNet(ISomeObject o);

private void DoInitDotNet()
{
    // The following works because MyForm implements ISomeObject
    InitDotNet(this);
}

ただし、InitDotNetをグローバル関数ではなくCOMインターフェイスのメソッドにすることもできます。

最後になりましたが、VBクラスの目的は何ですか?COMクラスをラップすることだけが目的である場合は、それは必要ありません。COMクラス/インターフェイスはC#から直接消費できます。

于 2013-01-18T08:50:58.027 に答える