dll与visual studio之间的循环依赖关系

时间:2008-12-12 13:59:40

标签: c++ dll circular-dependency

我在两个函数之间存在循环依赖关系。我希望这些函数中的每一个都驻留在它自己的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。然而,我想知道视觉工作室是否提供了一种以干净的方式做到这一点的方法。

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

我尝试这个的原因是我有一个循环依赖的遗留系统,它目前是静态链接的。出于各种原因,我们想要转向dll。我们不想等到清理所有循环依赖项。我正在考虑解决方案,并在linux上用gcc尝试了一些东西,在那里可以做我建议的。因此,您可以拥有两个彼此依赖的共享库,并且可以彼此独立构建。

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

9 个答案:

答案 0 :(得分:11)

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

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

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

当您想要从静态库转换为DLL时,听起来您假设您应该将每个静态库转换为DLL。那不是你唯一的选择。为什么不将代码识别为一个独立的模块(适合没有圆度的分层设计)时才开始将代码移动到DLL中?这样你就可以开始这个过程,但仍然可以一次攻击它。

答案 1 :(得分:7)

我非常同情你的情况(正如你的编辑所澄清的那样),但是坚定地相信做正确的事情,而不是现在有效的事情,如果我有可能的话认为你需要重构这些项目。

解决问题而不是症状。

答案 2 :(得分:5)

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

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

答案 3 :(得分: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

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

这个问题是我搜索“ dll循环依赖项”时首先遇到的问题,即使它已经使用了10年,大多数答案都指向“重构”,这是一个非常可惜的建议,对于大型项目和反正不是问题。

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

  • this pragraph在一些入门指南(链接器,David Drysdale)中对VS链接器进行了很好的解释。

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

对于Visual Studio和任何图形化的编译,它可能仍然有些奇怪。但是,如果您创建自己的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.libb.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工具相比,可能很难从中生产出东西)。

答案 6 :(得分:0)

干净利落是不可能的。因为它们彼此依赖,如果A发生变化,那么B必须重新编译。因为B被重新编译,它已经改变,A需要重新编译等等。

这是循环依赖关系不好的部分原因,无论你是否愿意,你都不能将其排除在讨论之外。

答案 7 :(得分: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调用添加更多参数。
  5. - Kenneth Kasajian

答案 8 :(得分: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不再尝试导入该函数;相反,它在运行时解析函数地址。

滚动您自己的错误处理和性能改进。

豫ICP备18024241号-1