круговые зависимости между dll с визуальной студией - PullRequest
10 голосов
/ 12 декабря 2008

У меня круговая зависимость между двумя функциями. Я хотел бы, чтобы каждая из этих функций находилась в своей собственной 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. Поиграв с 'References' и скомпилировав несколько раз, мне удалось получить нужные мне dll. Я хотел бы знать, однако, предлагает ли Visual Studio способ сделать это чистым способом.

Если foo изменяется, bar не нужно перекомпилировать, потому что я зависит только от сигнатуры bar, а не от реализации bar. Если на обоих dll есть библиотека lib, я могу перекомпилировать новую функциональность в любую из двух, и вся система по-прежнему работает.

Причина, по которой я пытаюсь это сделать, заключается в том, что у меня есть устаревшая система с круговыми зависимостями, которая в настоящее время статически связана. Мы хотим двигаться к DLL по разным причинам. Мы не хотим ждать, пока мы очистим все циклические зависимости. Я думал о решениях и попробовал кое-что с gcc на linux, и там можно сделать то, что я предлагаю. Таким образом, вы можете иметь две общие библиотеки, которые зависят друг от друга и могут создаваться независимо друг от друга.

Я знаю, что круговые зависимости нехорошо иметь, но я не хочу обсуждать это.

Ответы [ 9 ]

11 голосов
/ 23 декабря 2008

Причина, по которой он работает в Unix-подобных системах, заключается в том, что они выполняют фактическое разрешение соединения во время загрузки. Общая библиотека не знает, откуда придет определение ее функции, пока не будет загружена в процесс. Недостатком этого является то, что вы тоже не знаете. Библиотека может находить и вызывать функции в любой другой библиотеке (или даже в основном двоичном файле, который первым запустил процесс). Также по умолчанию все в разделяемой библиотеке экспортируется.

Windows совсем не работает. Экспортируются только явно экспортированные вещи, и все операции импорта должны выполняться во время компоновки библиотеки, после чего была определена идентичность библиотеки DLL, которая будет предоставлять каждую импортированную функцию. Для этого требуется библиотека импорта для связи.

Однако, вы можете (с некоторой дополнительной работой) обойти это. Используйте LoadLibrary, чтобы открыть любую DLL, которая вам нравится, а затем используйте GetProcAddress, чтобы найти функции, которые вы хотите вызвать. Таким образом, нет никаких ограничений. Но ограничения в обычном методе существуют по причине.

Поскольку вы хотите перейти от статических библиотек к DLL, похоже, вы предполагаете, что вы должны превратить каждую статическую библиотеку в DLL. Это не единственный вариант. Почему бы не начать перемещение кода в библиотеки DLL только тогда, когда вы идентифицируете его как автономный модуль, который вписывается в многоуровневый дизайн без округлости? Таким образом, вы можете начать процесс сейчас, но все равно атаковать его по частям.

7 голосов
/ 12 декабря 2008

Я глубоко сочувствую вашей ситуации (как разъяснено вашим редактированием), но как твердый сторонник делать правильные вещи, а не то, что работает на данный момент , если вообще есть какая-то возможность, я думаю вам нужно рефакторинг этих проектов.

Исправить проблему, а не симптом.

5 голосов
/ 29 мая 2011

Можно использовать утилиту LIB с файлами .EXP для «начальной загрузки» (сборки без предыдущих файлов .LIB) набора DLL с циклической ссылкой, такой как эта. Подробнее см. Статья MSDN .

Я согласен с другими людьми выше, что такого рода ситуации следует избегать путем пересмотра дизайна.

1 голос
/ 12 декабря 2008

Как насчет этого:

Проект 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
0 голосов
/ 24 февраля 2019

Этот вопрос был первым в моем поиске «циклической зависимости dll», и даже если ему 10 лет, это позор, что большинство ответов указывают на «рефакторинг», который является очень и очень глупым советом для крупного проекта и все равно не было вопроса.

Итак, я должен указать, что циклическая зависимость не так опасна. Они совершенно нормально работают в Unix / Linux. Они упоминаются во многих статьях MSDN как возможные ситуации с способами их обойти . Это происходит в JAVA (компилятор решает это путем muilti-pass компиляции). Сказать, что рефакторинг - единственный способ, это все равно, что запретить «друзей» на занятиях.

  • этот параграф в руководстве для начинающих по линкерам (Дэвид Драйсдейл) хорошо объясняет это для VS-линкеров.

Так что хитрость заключается в том, чтобы использовать двухпроходную компиляцию: первая, которая создаст просто 'import-libs', и вторая, которая сама сгенерирует dll.

Для визуальной студии и любой графической компиляции это, вероятно, все еще что-то странное. Но если вы создаете свои собственные файлы 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.lib и 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.dll и b.dll с довольно чистым методом. Использование компиляторов Microsoft должно быть простым, с их советом переключить link.exe на lib.exe (не проверял его, кажется, даже чище, но, вероятно, сложнее сделать что-то продуктивное по сравнению с mingw + make tools).

0 голосов
/ 04 сентября 2009

Единственный способ обойти это "чисто" (и я свободно использую этот термин) - это устранить одну из статических / зависимостей времени соединения и изменить ее на зависимость во время выполнения.

Может быть, что-то вроде этого:

// 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 больше не пытается импортировать функцию; вместо этого он разрешает адрес функции во время выполнения.

Сверните свои собственные улучшения обработки ошибок и производительности.

0 голосов
/ 04 сентября 2009

Вам необходимо разделить две библиотеки 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 на заводе в одном месте.

- Кеннет Касаджян

0 голосов
/ 22 декабря 2008

Visual Studio будет применять эти зависимости, вообще говоря, поскольку адреса функций могут изменяться во вновь скомпилированной DLL. Даже если подпись может совпадать, открытый адрес может измениться.

Однако, если вы заметили, что Visual Studio обычно сохраняет одинаковые адреса функций между сборками, вы можете использовать один из параметров сборки "Project Only" (игнорирует зависимости). Если вы сделаете это и получите ошибку о невозможности загрузить DLL зависимостей, просто пересоберите обе.

0 голосов
/ 12 декабря 2008

Невозможно сделать чисто. Поскольку оба они зависят друг от друга, если A изменяется, то B необходимо перекомпилировать. Поскольку B был перекомпилирован, он изменился, и необходимо перекомпилировать A и т. Д.

Это является причиной того, что круговые зависимости являются плохими, и хотите вы этого или нет, вы не можете оставить это вне обсуждения.

...