circular dependencies between dlls with visual studio
Asked Answered
S

10

16

I have a circular dependency between two functions. I would like each of these functions to reside in its own dll. Is it possible to build this with visual studio?

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

-> should compile into foo.dll

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

-> should compile into bar.dll

I have created two projects in visual studio, one for foo and one for bar. By playing with the 'References' and compiling a few times, I managed to get the dll's that I want. I would like to know however whether visual studio offers a way to do this in a clean way.

If foo changes, bar does not need to be recompiled, because I only depend on the signature of bar, not on the implementation of bar. If both dll's have the lib present, I can recompile new functionality into either of the two and the whole system still works.

The reason I am trying this is that I have a legacy system with circular dependencies, which is currently statically linked. We want to move towards dll's for various reasons. We don't want to wait until we clean up all the circular dependencies. I was thinking about solutions and tried out some things with gcc on linux and there it is possible to do what I suggest. So you can have two shared libraries that depend on each other and can be built independent of each other.

I know that circular dependencies are not a good thing to have, but that is not the discussion I want to have.

Suhail answered 12/12, 2008 at 13:59 Comment(2)
Very interesting question. I know something can like this is possible, but I don't really know how - I think it involves some special command-line params to the linker of ONE of the dll's and the other dll should have a simple dependency.Push
@PM: If that's the case then it's the kind of thing which is going to be a constant problem forgotten about, misconfigured, etc..Voter
U
15

The reason it works on Unix-like systems is because they perform actual linking resolution at load time. A shared library does not know where its function definition will come from until it's loaded into a process. The downside of this is that you don't know either. A library can find and call functions in any other library (or even the main binary that launched the process in the first place). Also by default everything in a shared library is exported.

Windows doesn't work like that at all. Only explicitly exported things are exported, and all imports must be resolved at library link-time, by which point the identity of the DLL that will supply each imported function has been determined. This requires an import library to link against.

However, you can (with some extra work) get around this. Use LoadLibrary to open any DLL you like, and then use GetProcAddress to locate the functions you want to call. This way, there are no restrictions. But the restrictions in the normal method are there for a reason.

As you want to transition from static libraries to DLLs, it sounds like you're assuming that you should make each static library into a DLL. That's not your only option. Why not start moving code into DLLs only when you identify it as a self-contained module that fits into a layered design with no circularity? That way you can begin the process now but still attack it a piece at a time.

Undercarriage answered 22/12, 2008 at 21:16 Comment(1)
I think that "Delay-Loaded" option in Visual studio solves OP's question as well, in the same manner Daniel wrote here.Jahncke
V
6

I deeply sympathise with your situation (as clarified by your edit), but as a firm believer in doing the correct thing, not the thing which works for now, if there's any possibility at all I think you need to refactor these projects.

Fix the problem not the symptom.

Voter answered 12/12, 2008 at 15:18 Comment(0)
B
6

It's possible to use the LIB utility with .EXP files to "bootstrap" (build without prior .LIB files) a set of DLLs with a circular reference such as this one. See MSDN article for details.

I agree with other people above that this kind of situation should be avoided by revising the design.

Bereave answered 28/5, 2011 at 20:39 Comment(0)
H
3

This question was first in my search for 'dll cyclic dependency', and even if it is 10 years old, it is a shame that most answers points to 'refactoring' which is a very very very stupid advice for large project and was not a question anyway.

So I need to point out that cyclic dependency are not so dangerous. They are totally ok in unix/linux. They are mentioned in many msdn articles as possible situations with ways to go arround them. They happens in JAVA (compiler solving it by muilti-pass compiling). Saying that refactoring is the only way is like forbidding 'friends' in classes.

  • this pragraph in some begginers guide to linkers (David Drysdale) explains it well for VS-linkers.

So the trick is to use two-pass compiling: first one that will create just 'import-libs', and the second one that will generate dll's itself.

For visual studio and any graphic-ide compiling, it is probably still something strange. But if you make your own Makefiles, and have better controll of linker process and flags, than it is not so hard to do.

Using OP exampe files and mingw-gcc syntax as a concept to show (because i tested it and know for sure that it works ok on windows), one must: - compile/link a.lib and b.lib without specifing cyclic libraries:

g++ -shared -Wl,--out-implib=a.lib -o a.dll a.obj //without specifying b.lib  
g++ -shared -Wl,--out-implib=b.lib -o b.dll b.obj //without specifying a.lib

... will show 'undefined refernce errors' and fail to provide dll-s, but it will create a.lib and b.lib, which we want foor second-pass linking:

g++ -shared -Wl,--out-implib=a.lib -o a.dll a.obj b.lib  
g++ -shared -Wl,--out-implib=b.lib -o b.dll b.obj a.lib

and the result is a.dll and b.dll with pretty clean method. Using Microsoft compilers should be simmilar, with their advice to switch link.exe to lib.exe (did not tested it, seems even cleaner, but probably harder to make something productive from it comparing to mingw + make tools).

Humanitarian answered 24/2, 2019 at 4:3 Comment(0)
A
1

How about this:

Project A

Public Class A Implements C.IA

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

End Class

Project B

Public Class B Implements C.IB

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

End Class

Project 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

Project D

Sub Main()

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

    a.foo(b)

End Sub
Analogous answered 12/12, 2008 at 15:23 Comment(1)
Thanks for the quick responses. This would work, but is not what I want to do. I am looking for the compiler trick that allows me to do in a clean way, what I did by compiling a few times and playing with the dependencies.Suhail
L
1

The only way you'll get around this "cleanly" (and I use the term loosely) will be to eliminate one of the static/link-time dependencies and change it to a run-time dependency.

Maybe something like this:

// 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 will export the function. Bar.dll no longer tries to import the function; instead, it resolves the function address at runtime.

Roll your own error handling and performance improvements.

Loquacity answered 4/9, 2009 at 1:4 Comment(1)
Ugh. Now you're making the circle invisible to analysis tools and fellow programmers, hiding the problem instead of resolving it.Offstage
L
1

As I came across this very problem recently I wanted to share a solution using CMake. The idea is that we have two dlls a and b that have a circular dependency and one main object that calls a.dll. To make the linking work we create an additional library b_init that is compiled with the /FORCE:UNRESOLVED flag in order to allow building with incomplete symbols as it is not linking against a. Additionally, b_inits output name is b, so it will create b.lib.

In the next step we link a to the newly created b.lib. Note, the PRIVATE keyword to avoid transitive adding of b.lib to the b library below.

Here's the 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)
Lanalanae answered 11/8, 2021 at 12:45 Comment(0)
G
0

You need to decouple the two DLLs, placing the interfaces and implementation in two different DLLs, and then using late binding to instantiate the class.


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

The "Factory" classes are technically not necessary, but it's much nicer to say:

IFoo objFoo = FooFactory.CreateInstance();

in application code than:

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

because of the following reasons:

  1. You can avoid a "cast" in application code, which is a good thing
  2. If the DLL that hosts the class changes, you don't have to change all the clients, just the factory.
  3. Code-completion still wroks.
  4. Depending on your needs, you have to call culture-aware or key-signed DLLs, in which case you can add more parameters to the CreateInstance call in the factory in one place.

-- Kenneth Kasajian

Gizmo answered 12/12, 2008 at 13:59 Comment(0)
K
0

It is not possible to do cleanly. Because they both depend on each other, if A changes, then B must be recompiled. Because B was recompiled, it has changed and A needs to be recompiled and so on.

That is part of the reason circular dependencies are bad and whether you want to or not, you cannot leave that out of the discussion.

Kamseen answered 12/12, 2008 at 14:31 Comment(2)
If A changes, B does not need to be recompiled, because I only depend on the signature of A, not on the implementation of A. If both dll's have the lib present, I can recompile new functionality into either of the two and the whole system still works.Suhail
You mentioned working with References, thus I assumed you were working with Managed code. Managed code does not use the headers from other projects and works this way.Kamseen
M
0

Visual Studio will enforce the dependencies, generally speaking, since function addresses may change within the newly-compiled DLL. Even though the signature may be the same, the exposed address may change.

However, if you notice that Visual Studio typically manages to keep the same function addresses between builds, then you can use one of the "Project Only" build settings (ignores dependencies). If you do that and get an error about not being able to load the dependency DLL, then just rebuild both.

Mission answered 22/12, 2008 at 20:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.