15

2 つの関数間に循環依存関係があります。これらの各関数を独自の dll に常駐させたいと考えています。これをビジュアルスタジオでビルドすることは可能ですか?

foo(int i)
{
   if (i > 0)
      bar(i -i);
}

-> foo.dll にコンパイルする必要があります

bar(int i)
{
   if (i > 0)
      foo(i - i);
}

-> bar.dll にコンパイルする必要があります

Visual Studio で 2 つのプロジェクトを作成しました。1 つは foo 用、もう 1 つは bar 用です。「参照」をいじって数回コンパイルすることで、必要な dll を取得することができました。ただし、ビジュアルスタジオがこれをクリーンな方法で行う方法を提供しているかどうかを知りたいです。

foo が変更された場合、bar の実装ではなく、bar の署名のみに依存するため、bar を再コンパイルする必要はありません。両方の dll に lib が存在する場合、新しい機能を 2 つのうちのいずれかに再コンパイルしても、システム全体は引き続き機能します。

私がこれを試している理由は、現在静的にリンクされている循環依存関係を持つレガシー システムがあるためです。さまざまな理由から、dll に移行したいと考えています。すべての循環依存関係をクリーンアップするまで待ちたくありません。私は解決策について考えていて、Linuxでgccを使っていくつか試してみましたが、私が提案したことを実行することが可能です。したがって、互いに依存し、互いに独立して構築できる 2 つの共有ライブラリを使用できます。

循環依存が良いことではないことはわかっていますが、それは私がしたい議論ではありません。

4

10 に答える 10

14

Unix ライクなシステムで機能する理由は、ロード時に実際のリンク解決を実行するためです。共有ライブラリは、プロセスにロードされるまで、その関数定義がどこから来るのかわかりません。これの欠点は、あなたも知らないということです。ライブラリは、他のライブラリ (または最初にプロセスを起動したメイン バイナリでさえも) の関数を見つけて呼び出すことができます。また、デフォルトでは、共有ライブラリ内のすべてがエクスポートされます。

Windowsはそのようにはまったく機能しません。明示的にエクスポートされたものだけがエクスポートされ、すべてのインポートはライブラリのリンク時に解決される必要があります。その時点までに、インポートされた各関数を提供する DLL の ID が決定されます。これには、リンクするインポート ライブラリが必要です。

ただし、(追加の作業を行うことで)これを回避できます。を使用LoadLibraryして任意の DLL を開き、 を使用GetProcAddressして呼び出したい関数を見つけます。このように、制限はありません。しかし、通常の方法の制限には理由があります。

静的ライブラリから DLL に移行したいので、各静的ライブラリを DLL にする必要があると想定しているように聞こえます。それが唯一の選択肢ではありません。循環性のない階層化された設計に適合する自己完結型モジュールとしてコードを識別した場合にのみ、コードを DLL に移動し始めませんか? そうすれば、プロセスを今すぐ開始できますが、一度に少しずつ攻撃できます。

于 2008-12-22T21:16:54.750 に答える
5

.EXP ファイルで LIB ユーティリティを使用して、このような循環参照を持つ一連の DLL を "ブートストラップ" (以前の .LIB ファイルなしでビルド) することができます。詳細については、 MSDN の記事を参照してください。

設計を修正することでこのような状況を回避する必要があるという上記の他の人々に同意します。

于 2011-05-28T20:39:19.520 に答える
1

これはどう:

プロジェクトA

パブリック クラス A は C.IA を実装します

Public Function foo(ByVal value As C.IB) As Integer Implements C.IA.foo
    Return value.bar(Me)
End Function

クラス終了

プロジェクトB

パブリック クラス B は C.IB を実装します

Public Function bar(ByVal value As C.IA) As Integer Implements C.IB.bar
    Return value.foo(Me)
End Function

クラス終了

プロジェクトC

Public Interface IA
    Function foo(ByVal value As IB) As Integer
End Interface

Public Interface IB
    Function bar(ByVal value As IA) As Integer
End Interface

プロジェクトD

Sub Main()

    Dim a As New A.A
    Dim b As New B.B

    a.foo(b)

End Sub
于 2008-12-12T15:23:55.553 に答える
0

きれいにすることはできません。どちらも相互に依存しているため、A が変更された場合、B を再コンパイルする必要があります。B が再コンパイルされたため、変更されており、A を再コンパイルする必要があります。

これが循環依存が悪い理由の一部であり、望むと望まざるとにかかわらず、それを議論から除外することはできません。

于 2008-12-12T14:31:35.317 に答える
0

2 つの DLL を分離し、インターフェイスと実装を 2 つの異なる DLL に配置し、レイト バインディングを使用してクラスをインスタンス化する必要があります。


// IFoo.cs: (build IFoo.dll)
    interface IFoo {
      void foo(int i);
    }

    public class FooFactory {
      public static IFoo CreateInstance()
      {
        return (IFoo)Activator.CreateInstance("Foo", "foo").Unwrap();
      }
    }

// IBar.cs: (build IBar.dll)
    interface IBar {
      void bar(int i);
    }

    public class BarFactory {
      public static IBar CreateInstance()
      {
        return (IBar)Activator.CreateInstance("Bar", "bar").Unwrap();
      }
    }

// foo.cs: (build Foo.dll, references IFoo.dll and IBar.dll)
    public class Foo : IFoo {
      void foo(int i) {
        IBar objBar = BarFactory.CreateInstance();
        if (i > 0) objBar.bar(i -i);
      }
    }

// bar.cs: (build Bar.dll, references IBar.dll and IFoo.dll)
    public class Bar : IBar {
      void bar(int i) {
        IFoo objFoo = FooFactory.CreateInstance();
        if (i > 0) objFoo.foo(i -i);
      }
    }

「ファクトリ」クラスは技術的には必要ありませんが、次のように言う方がはるかに優れています。

IFoo objFoo = FooFactory.CreateInstance();

よりもアプリケーションコードで:

IFoo objFoo = (IFoo)Activator.CreateInstance("Foo", "foo").Unwrap();

以下の理由によります。

  1. アプリケーション コードでの「キャスト」を回避できます。これは良いことです
  2. クラスをホストする DLL が変更された場合、すべてのクライアントを変更する必要はなく、ファクトリだけを変更する必要があります。
  3. コード補完は引き続き機能します。
  4. 必要に応じて、カルチャ対応またはキー署名付き DLL を呼び出す必要があります。この場合、ファクトリの CreateInstance 呼び出しに 1 か所でさらにパラメーターを追加できます。

-- ケネス・カサジアン

于 2009-09-04T00:49:47.437 に答える
0

これを「きれいに」(私はこの用語を大まかに使用しています) 回避する唯一の方法は、静的/リンク時の依存関係の 1 つを排除し、それを実行時の依存関係に変更することです。

多分このようなもの:

// foo.h
#if defined(COMPILING_BAR_DLL)
inline void foo(int x) 
{
  HMODULE hm = LoadLibrary(_T("foo.dll");
  typedef void (*PFOO)(int);
  PFOO pfoo = (PFOO)GetProcAddress(hm, "foo");
  pfoo(x); // call the function!
  FreeLibrary(hm);
}
#else
extern "C" {
__declspec(dllexport) void foo(int);
}
#endif

Foo.dll は関数をエクスポートします。Bar.dll は関数をインポートしようとしなくなりました。代わりに、実行時に関数アドレスを解決します。

独自のエラー処理とパフォーマンスの改善を行います。

于 2009-09-04T01:04:28.307 に答える
0

関数のアドレスは新しくコンパイルされた DLL 内で変更される可能性があるため、一般的に言えば、Visual Studio は依存関係を適用します。署名は同じかもしれませんが、公開されたアドレスは変わるかもしれません。

ただし、通常、Visual Studio がビルド間で同じ関数アドレスを維持することに気付いた場合は、「プロジェクトのみ」のビルド設定の 1 つを使用できます (依存関係を無視します)。それを行って、依存関係の DLL を読み込めないというエラーが発生した場合は、両方を再構築してください。

于 2008-12-22T20:55:24.327 に答える