15

我有两个函数之间的循环依赖。我希望这些函数中的每一个都驻留在自己的 dll 中。是否可以使用 Visual Studio 构建它?

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

-> 应该编译成 foo.dll

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

-> 应该编译成 bar.dll

我在 Visual Studio 中创建了两个项目,一个用于 foo,一个用于 bar。通过使用“参考”并编译几次,我设法获得了我想要的 dll。但是,我想知道 Visual Studio 是否提供了一种以干净的方式执行此操作的方法。

如果 foo 发生变化,bar 不需要重新编译,因为我只依赖 bar 的签名,而不依赖于 bar 的实现。如果两个 dll 都存在 lib,我可以将新功能重新编译到两者中的任何一个中,整个系统仍然可以工作。

我尝试这样做的原因是我有一个具有循环依赖关系的遗留系统,该系统当前是静态链接的。出于各种原因,我们希望转向 dll。我们不想等到清理完所有循环依赖项。我正在考虑解决方案并在 linux 上使用 gcc 尝试了一些事情,并且可以按照我的建议进行操作。因此,您可以拥有两个相互依赖并且可以相互独立构建的共享库。

我知道循环依赖不是一件好事,但这不是我想要的讨论。

4

10 回答 10

14

它在类 Unix 系统上工作的原因是因为它们在加载时执行实际的链接解析。共享库在加载到进程之前不知道其函数定义来自何处。这样做的缺点是你也不知道。库可以在任何其他库(甚至是最初启动该进程的主二进制文件)中查找和调​​用函数。此外,默认情况下,共享库中的所有内容都会被导出。

Windows 根本不能那样工作。仅导出显式导出的内容,并且所有导入必须在库链接时解析,此时已确定将提供每个导入函数的 DLL 的标识。这需要一个导入库来链接。

但是,您可以(通过一些额外的工作)解决这个问题。用来LoadLibrary打开任何你喜欢的DLL,然后GetProcAddress用来定位你要调用的函数。这样,没有任何限制。但是普通方法的限制是有原因的。

当您想从静态库转换到 DLL 时,听起来您假设您应该将每个静态库都变成一个 DLL。这不是你唯一的选择。为什么不将代码移动到 DLL 中,仅当您将其识别为适合分层设计且没有循环性的独立模块时?这样你现在就可以开始这个过程,但仍然一次只攻击它。

于 2008-12-22T21:16:54.750 回答
5

我对你的情况深表同情(正如你的编辑所澄清的那样),但作为一个坚定的相信做正确的事情,而不是现在可行的事情,如果有任何可能的话,我认为你需要重构这些项目。

解决问题而不是症状。

于 2008-12-12T15:18:28.153 回答
5

可以使用带有 .EXP 文件的 LIB 实用程序来“引导”(在没有先前的 .LIB 文件的情况下构建)一组具有循环引用的 DLL,例如这个。有关详细信息,请参阅MSDN 文章

我同意上面其他人的观点,应该通过修改设计来避免这种情况。

于 2011-05-28T20:39:19.520 回答
3

这个问题是我搜索“dll 循环依赖”时的第一个问题,即使它已有 10 年的历史,但遗憾的是大多数答案都指向“重构”,这对于大型项目来说是一个非常非常非常愚蠢的建议,而不是无论如何问题。

所以我需要指出,循环依赖并不是那么危险。它们在 unix/linux 中完全没问题。在许多 msdn 文章中都提到了它们作为可能的情况以及绕过它们的方法。它们发生在 JAVA 中(编译器通过多遍编译来解决它)。说重构是唯一的方法就像在课堂上禁止“朋友”一样。

所以诀窍是使用两遍编译:第一个将创建“import-libs”,第二个将生成 dll 本身。

对于视觉工作室和任何图形IDE 编译,它可能仍然有些奇怪。但是,如果您制作自己的 Makefile,并且对链接器进程和标志有更好的控制,那么做起来并不难。

使用 OP 示例文件和 mingw-gcc 语法作为一个概念来展示(因为我对其进行了测试并确定它在 Windows 上可以正常工作),必须: - 编译/链接 a.lib 和 b.lib 而不指定循环库:

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

... 将显示“未定义的引用错误”并且无法提供 dll-s,但它会创建a.liband b.lib,我们希望用于第二遍链接:

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

结果是非常干净的方法a.dllb.dll使用 Microsoft 编译器应该是类似的,他们的建议是将 link.exe 切换到 lib.exe(没有测试它,看起来更干净,但与 mingw + make 工具相比,可能更难从中产生生产力)。

于 2019-02-24T04:03:29.993 回答
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

一般来说,Visual Studio 将强制执行依赖关系,因为函数地址可能会在新编译的 DLL 中发生变化。即使签名可能相同,暴露的地址也可能会改变。

但是,如果您注意到 Visual Studio 通常设法在构建之间保持相同的函数地址,那么您可以使用“仅项目”构建设置之一(忽略依赖项)。如果你这样做并得到一个关于无法加载依赖 DLL 的错误,那么只需重建两者。

于 2008-12-22T20:55:24.327 回答
0

干净利落是不可能的。因为它们都相互依赖,所以如果 A 发生变化,则 B 必须重新编译。因为B被重新编译了,它已经改变了,A需要重新编译等等。

这就是循环依赖不好的部分原因,不管你想不想,你都不能把它排除在讨论之外。

于 2008-12-12T14:31:35.317 回答
0

您需要将两个 DLL 解耦,将接口和实现放在两个不同的 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 调用添加更多参数。

——肯尼斯·卡萨吉安

于 2009-09-04T00:49:47.437 回答
0

您将“干净地”解决这个问题(我松散地使用该术语)的唯一方法是消除一个静态/链接时依赖项并将其更改为运行时依赖项。

也许是这样的:

// 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

当我最近遇到这个问题时,我想分享一个使用 CMake 的解决方案。这个想法是我们有两个 dlla并且b有一个循环依赖和一个调用a.dll. 为了使链接工作,我们创建了一个b_init使用标志编译的附加库,/FORCE:UNRESOLVED以便允许使用不完整的符号进行构建,因为它没有链接到a. 此外,b_inits 输出名称是b,因此它将创建b.lib.

在下一步中,我们链接a到新创建的b.lib. 注意,PRIVATE避免传递添加b.libb下面库的关键字。

这是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)
于 2021-08-11T12:45:21.907 回答