16 Stimmen

zirkuläre abhängigkeiten zwischen dlls mit visual studio

Ich habe eine zirkuläre Abhängigkeit zwischen zwei Funktionen. Ich möchte, dass jede dieser Funktionen in einer eigenen DLL untergebracht ist. Ist es möglich, dies mit Visual Studio zu bauen?

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

-> sollte zu foo.dll kompiliert werden

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

-> sollte in bar.dll kompiliert werden

Ich habe zwei Projekte in Visual Studio erstellt, eines für foo und eines für bar. Indem ich mit den "Referenzen" spielte und ein paar Mal kompilierte, gelang es mir, die gewünschten DLLs zu erhalten. Ich würde jedoch gerne wissen, ob Visual Studio eine Möglichkeit bietet, dies auf saubere Weise zu tun.

Wenn sich foo ändert, muss bar nicht neu kompiliert werden, weil ich nur von der Signatur von bar abhänge, nicht von der Implementierung von bar. Wenn die Lib in beiden DLLs vorhanden ist, kann ich neue Funktionen in eine der beiden neu kompilieren, und das ganze System funktioniert weiterhin.

Der Grund, warum ich dies versuche, ist, dass ich ein Legacy-System mit zirkulären Abhängigkeiten habe, das derzeit statisch verknüpft ist. Wir wollen aus verschiedenen Gründen auf DLLs umsteigen. Wir wollen nicht warten, bis wir alle zirkulären Abhängigkeiten bereinigt haben. Ich habe über Lösungen nachgedacht und einige Dinge mit gcc unter Linux ausprobiert, und dort ist es möglich, das zu tun, was ich vorschlage. Man kann also zwei gemeinsam genutzte Bibliotheken haben, die voneinander abhängen und unabhängig voneinander gebaut werden können.

Ich weiß, dass zirkuläre Abhängigkeiten keine gute Sache sind, aber das ist nicht die Diskussion, die ich führen möchte.

0 Stimmen

Eine sehr interessante Frage. Ich weiß, etwas kann wie dies möglich ist, aber ich weiß nicht wirklich, wie - ich denke, es beinhaltet einige spezielle Befehlszeilen-Parameter an den Linker von EINER der dll's und die andere dll sollte eine einfache Abhängigkeit haben.

0 Stimmen

@PM: Wenn das der Fall ist, dann ist es die Art von Sache, die ein ständiges Problem sein wird, das vergessen wird, falsch konfiguriert wird, etc.

1voto

ijprest Punkte 4178

Die einzige Möglichkeit, dies "sauber" zu umgehen (und ich verwende den Begriff nicht ganz genau), besteht darin, eine der statischen/Link-Time-Abhängigkeiten zu eliminieren und sie in eine Runtime-Abhängigkeit umzuwandeln.

Vielleicht etwa so:

// 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 wird die Funktion exportieren. Bar.dll versucht nicht mehr, die Funktion zu importieren; stattdessen wird die Funktionsadresse zur Laufzeit aufgelöst.

Entwickeln Sie Ihre eigene Fehlerbehandlung und Leistungsverbesserungen.

0 Stimmen

Igitt. Jetzt machen Sie den Kreis für Analysetools und andere Programmierer unsichtbar und verstecken das Problem, anstatt es zu lösen.

1voto

Azrael3000 Punkte 1735

Da ich vor kurzem auf genau dieses Problem gestoßen bin, möchte ich eine Lösung mit CMake vorstellen. Die Idee ist, dass wir zwei DLLs haben a y b die eine zirkuläre Abhängigkeit haben, und ein Hauptobjekt, das die a.dll . Damit die Verknüpfung funktioniert, erstellen wir eine zusätzliche Bibliothek b_init die mit dem Programm /FORCE:UNRESOLVED Flagge, um das Bauen mit unvollständigen Symbolen zu ermöglichen, da es nicht gegen a . Zusätzlich, b_init Der Name der Ausgabe lautet b so wird es erstellt b.lib .

Im nächsten Schritt verknüpfen wir a auf die neu erstellte b.lib . Hinweis: Die PRIVATE Schlüsselwort, um das transitive Hinzufügen von b.lib zum b Bibliothek unten.

Hier ist die CMakeLists.txt :

project(circ_dll CXX)

cmake_minimum_required(VERSION 3.15)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

add_library(b_init SHARED b_dll.cpp)
set_target_properties(b_init PROPERTIES LINK_FLAGS "/FORCE:UNRESOLVED")
set_target_properties(b_init PROPERTIES OUTPUT_NAME "b")

add_library(a SHARED a_dll.cpp)
target_link_libraries(a PRIVATE b_init)

add_library(b SHARED b_dll.cpp)
target_link_libraries(b a)

add_executable(main main.cpp)
target_link_libraries(main a b)

0voto

Rob Prouse Punkte 21205

Es ist nicht möglich, das sauber zu machen. Da beide voneinander abhängen, muss B neu kompiliert werden, wenn sich A ändert. Da B neu kompiliert wurde, hat es sich geändert und A muss neu kompiliert werden und so weiter.

Das ist einer der Gründe, warum zirkuläre Abhängigkeiten schlecht sind, und ob Sie es wollen oder nicht, Sie können das nicht aus der Diskussion herauslassen.

1 Stimmen

Wenn sich A ändert, muss B nicht neu kompiliert werden, denn ich bin nur von der Signatur von A abhängig, nicht von der Implementierung von A. Wenn beide DLLs die Lib enthalten, kann ich neue Funktionen in eine der beiden neu kompilieren, und das ganze System funktioniert weiterhin.

0 Stimmen

Sie erwähnten, dass Sie mit Referenzen arbeiten, daher nahm ich an, dass Sie mit verwaltetem Code arbeiten. Verwalteter Code verwendet keine Kopfzeilen aus anderen Projekten und funktioniert auf diese Weise.

0voto

Brian Punkte 3317

Visual Studio erzwingt die Abhängigkeiten im Allgemeinen, da sich die Funktionsadressen innerhalb der neu kompilierten DLL ändern können. Auch wenn die Signatur die gleiche ist, kann sich die exponierte Adresse ändern.

Wenn Sie jedoch feststellen, dass Visual Studio in der Regel die gleichen Funktionsadressen zwischen Builds beibehält, können Sie eine der Build-Einstellungen "Nur Projekt" verwenden (ignoriert Abhängigkeiten). Wenn Sie das tun und eine Fehlermeldung erhalten, dass die Abhängigkeits-DLL nicht geladen werden kann, dann erstellen Sie einfach beide neu.

0voto

zumalifeguard Punkte 8227

Sie müssen die beiden DLLs entkoppeln, indem Sie die Schnittstellen und die Implementierung in zwei verschiedenen DLLs unterbringen und dann die Klasse mit Late Binding instanziieren.


// 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);
      }
    }

Die "Factory"-Klassen sind technisch nicht notwendig, aber es ist viel netter zu sagen:

IFoo objFoo = FooFactory.CreateInstance();

im Anwendungscode als:

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

aus den folgenden Gründen:

  1. Sie können einen "Cast" im Anwendungscode vermeiden, was eine gute Sache ist
  2. Wenn sich die DLL, die die Klasse hostet, ändert, müssen Sie nicht alle Clients ändern, sondern nur die Fabrik.
  3. Die Code-Vervollständigung funktioniert immer noch.
  4. Je nach Bedarf müssen Sie kultursensitive oder schlüsselsignierte DLLs aufrufen. In diesem Fall können Sie dem CreateInstance-Aufruf in der Factory an einer Stelle weitere Parameter hinzufügen.

-- Kenneth Kasajian

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X