Проблема следующая. Можно ли как-то заставить линкер включить в линковку модули из статической либы, которые по его мнению не используются в исполняемом файле. Т.е. примерный код:
classA123.cpp
ClassA123::ClassA123()
{
}
//ClassA123 methodsnamespace
{
auto p = new ClassA123();
auto reg = Repository::Instance().Register(p);
}
Если подобный код находится в ехе проекте, все отлично работает. Как только я выношу это в статическую либу, линкер считает, что classA123 у меня нигде не используется и не линкует модуль classA123.cpp. Фиаско.
Пробовал /FORCE:UNRESOLVED и /OPT:NOREF — не помогает.
Если добавить в classA123.cpp любую void f(){}; и соответственно вызвать ее в любом модуле ехе проекта, то опять все работает, но это не выход, конечно.
df>namespace
df>{
df> auto p = new ClassA123();
df> auto reg = Repository::Instance().Register(p);
df>}
df>
Это вы в статической библиотеке создаете глобальные переменные? Да еще и надеясь на порядок инициализации (пусть эти переменные и находятся в одном файле)? Зачем там вообще переменная p? Избавляйтесь от всего этого безобразия. Сделайте нормальную функцию, которая создаст и зарегистрирует вам экземпляр класса.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
VTT>Это вы в статической библиотеке создаете глобальные переменные? Да еще и надеясь на порядок инициализации (пусть эти переменные и находятся в одном файле)?
Мне не важен порядок инициализации.
VTT>Зачем там вообще переменная p?
разве эта мелочь стоила упоминания? Вопрос вообще не в этом был.
VTT>Избавляйтесь от всего этого безобразия.
Какого?
VTT>Сделайте нормальную функцию, которая создаст и зарегистрирует вам экземпляр класса.
у меня 100500 таких классов и я их добавляю. "Нормальная" функция потребует включения каждого хэдера. Т.е. она должна будет знать о каждом классе. При добавлении/удалении класса я должен буду редактировать ее.
В моем случае — нет. Это удобно. Вообще это известный паттерн.
Здравствуйте, df, Вы писали:
df>Мне не важен порядок инициализации. df>разве эта мелочь стоила упоминания? Вопрос вообще не в этом был.
У вас там явная зависимость от порядка инициализации, если Repository::Instance().Register(p); будет вызван до инициализации p, то все сломается. Это на самом деле вряд ли произойдет, так как переменные объявляются в одном .cpp (а для одного .cpp стандарт вроде как устанавливает порядок). Но суть в том что тут задействуются два антипаттерна сразу: и глобальные переменные, и зависимость от порядка их инициализации.
Добавление по одному заголовочному файлу на каждый класс с объявлением одной единственной функции, создающей и регистрирующей соответствующий класс мне кажется вполне приемлемым решением. Реализацию этой функции можно было бы разместить в статической библиотеке, в том же .cpp. Написание 100500 заголовочных файлов с единственной функции в каждом я считаю меньшим злом чем такое же количество глобальных переменных. Тем более, что их генерацию можно атоматизировать. По-хорошему их можно было бы регистрировать и напрямую в общей функции типа Register_AllClasses(), знающей обо всех классах. Но такое решение наверняка вскроет проблемы с организацией заголовочных файлов, временем компиляции и т.д.
Если же хочется просто заставить это работать, то попробуйте объявлять свои глобальные переменные как extern.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Здравствуйте, df, Вы писали:
df>Привет всем.
df>Проблема следующая. Можно ли как-то заставить линкер включить в линковку модули из статической либы, которые по его мнению не используются в исполняемом файле. Т.е. примерный код:
можно
df>Если добавить в classA123.cpp любую void f(){}; и соответственно вызвать ее в любом модуле ехе проекта, то опять все работает, но это не выход, конечно.
это тоже выход, попробуйте развить идею
df>Upd: Студия 2013
у нас используется следующий трюк:
ClassA123::ClassA123()
{
}
//ClassA123 methodsnamespace
{
auto p = new ClassA123();
auto reg = Repository::Instance().Register(p);
}
#if defined(_WIN64)
extern"C"void __cdecl _Dummy() {}
#else
extern"C"void __cdecl Dummy() {}
#endif
в том модуле, куда надо это влинковать мы добавляем команду для линкера:
module.cpp
#pragma comment(linker, "/include:_Dummy")
если классов много и они раскиданы по файлам, то на уровне одной статической либы лучше сделать одну регистрирующую функцию, а не много. за нее уже можно линкером цепляться
Здравствуйте, df, Вы писали:
df> auto p = new ClassA123();
у меня с кучей компиляторов (включая msvc2013) работает
static int PPMD_x = AddCompressionMethod (parse_PPMD); // Зарегистрируем парсер метода PPMD
разница в том что это obj а не lib, namespace глобальный, AddCompressionMethod из другого obj. думаю что причина именно в первом — к lib линкер относится более агрессивно
ps: по карйней мере gcc делает именно так: "Also, gcc will ALWAYS link .o files, but it will only search libraries and link from them if there are undefined names still to resolve."
pps: изучение списка опций линкера даёт нам только один вариант. как я понимаю, библиотека рассматривается как набор всякого мусора, из которого следует включить только то, что непосредственно используется программой, и никакого способа принудительно включить все obj из библиотеки у нас нет
можешь попробовать -Gy- при компиляции, но как я понимаю с opt:noref это роли уже не играет
Здравствуйте, uzhas, Вы писали:
U>если классов много и они раскиданы по файлам, то на уровне одной статической либы лучше сделать одну регистрирующую функцию, а не много. за нее уже можно линкером цепляться
кстати да. в моём коде вызывается AddCompressionMethod, добавляющий записи в глобальную таблицу, используемую FindCompressionMethod, который вызывается главной программой. т.е. у вызовов AddCompressionMethod налицо видимый в основной программе побочный эфект, исчезающий при их удалении. может, компилятор замечает эти зависимости и именно поэтому подцепляет этот код? можно попробовать сделать такую же схему
Здравствуйте, BulatZiganshin, Вы писали:
BZ>кстати да. в моём коде вызывается AddCompressionMethod, добавляющий записи в глобальную таблицу, используемую FindCompressionMethod, который вызывается главной программой. т.е. у вызовов AddCompressionMethod налицо видимый в основной программе побочный эфект, исчезающий при их удалении. может, компилятор замечает эти зависимости и именно поэтому подцепляет этот код? можно попробовать сделать такую же схему
ты же вроде правильно уже отметил, что линковщик .obj линкует без анализа. это должно все объяснять
что тебя заставило думать в другом направлении?
Здравствуйте, uzhas, Вы писали:
U>ты же вроде правильно уже отметил, что линковщик .obj линкует без анализа. это должно все объяснять U>что тебя заставило думать в другом направлении?
я использую -Gy и opt:ref:
When /OFT:REF is enabled, LINK removes unreferenced packaged functions and data. An object contains packaged functions and data (COMDATs) if it was compiled by using the /Gy option.
т.е. по идее вещей моя статпеременная вместе со всей своей инициализацией должна элиминироваться? это как раз тот момент, который я ни черта не понимаю
U>ClassA123::ClassA123()
U>{
U>}
U>//ClassA123 methods
U>namespace
U>{
U> auto p = new ClassA123();
U> auto reg = Repository::Instance().Register(p);
U>}
U>#if defined(_WIN64)
U>extern"C"void __cdecl _Dummy() {}
U>#else
U>extern"C"void __cdecl Dummy() {}
U>#endif
U>
U>в том модуле, куда надо это влинковать мы добавляем команду для линкера: U>module.cpp U>
U>#pragma comment(linker, "/include:_Dummy")
U>
U>если классов много и они раскиданы по файлам, то на уровне одной статической либы лучше сделать одну регистрирующую функцию, а не много. за нее уже можно линкером цепляться
Ну это то, что VTT предлагал. Хотелось бы уйти от этого.
Прелесть решения (когда оно в ехе) в том, что классов много и они "живые". Т.е. добавляются, удаляются постоянно, и никто ни кому не мешает. Пока не было нужды выносить это в либу, все было просто замечательно... да вы это и сами знаете.
BZ>у меня с кучей компиляторов (включая msvc2013) работает BZ>static int PPMD_x = AddCompressionMethod (parse_PPMD); // Зарегистрируем парсер метода PPMD
BZ>разница в том что это obj а не lib, namespace глобальный, AddCompressionMethod из другого obj. думаю что причина именно в первом — к lib линкер относится более агрессивно
да, здесь все ясно.
BZ>как я понимаю, библиотека рассматривается как набор всякого мусора, из которого следует включить только то, что непосредственно используется программой, и никакого способа принудительно включить все obj из библиотеки у нас нет
грусть-засада. Придется общий сортир добавлять видимо.
Здравствуйте, BulatZiganshin, Вы писали:
BZ>кстати да. в моём коде вызывается AddCompressionMethod, добавляющий записи в глобальную таблицу, используемую FindCompressionMethod, который вызывается главной программой. т.е. у вызовов AddCompressionMethod налицо видимый в основной программе побочный эфект, исчезающий при их удалении. может, компилятор замечает эти зависимости и именно поэтому подцепляет этот код? можно попробовать сделать такую же схему
если упростить — модуль orphan:
static int x = add();
модуль resident:
int y = 0;
add() {y++;}
основная программа:
main() {return y;}
здесь инициализация x приводит к изменению переменной y, используемой в main. можно проэкспериментировать с различными вариантами компиляции (Gy/Gy-, ref/noref, lib/obj) и посмотреть какое y будет возвращаться
BZ>When /OFT:REF is enabled, LINK removes unreferenced packaged functions and data. An object contains packaged functions and data (COMDATs) if it was compiled by using the /Gy option.
BZ>т.е. по идее вещей моя статпеременная вместе со всей своей инициализацией должна элиминироваться? это как раз тот момент, который я ни черта не понимаю
почему вообще /OPT:NOREF не работает так как от него ждешь....
Ведь вся проблема в линковке. 1000 чертей!
BZ>если упростить — модуль orphan:
BZ>static int x = add();
BZ>модуль resident:
BZ>int y = 0; BZ>add() {y++;}
BZ>основная программа:
BZ>main() {return y;}
BZ>здесь инициализация x приводит к изменению переменной y, используемой в main. можно проэкспериментировать с различными вариантами компиляции (Gy/Gy-, ref/noref, lib/obj) и посмотреть какое y будет возвращаться
Если добавить в classA123.cpp любую void f(){}; и соответственно вызвать ее в любом модуле ехе проекта, то опять все работает, но это не выход, конечно.
Здравствуйте, df, Вы писали:
df>почему вообще /OPT:NOREF не работает так как от него ждешь.... df>Ведь вся проблема в линковке. 1000 чертей!
по идее вещей, obj-файл — это неотъемлемая часть программы, исключение части его кода — уже рассматривается как оптимизация, которая не должна ничего менять (вероятно поэтмоу у меня инициализхация и срабатывает, поскольку она модифицирует глобальную переменную). opt:noref — это способ отключить оптимизацию, если она всё же начудила, и вернуть всё на свои места
библиотека — это набор полезных артефактов, которые могут пригодиться в программе. по умолчанию, пока на них нет ссылок, они в программу включаться не должны. соответственно opt:[no]ref влияет только на оптимизацию obj-файлов, но ничего не меняет для lib. что собственно логично — представь что ты внезапно получил бы все msvc*.lib включёнными в свой exe
Здравствуйте, df, Вы писали:
df>Если добавить в classA123.cpp любую void f(){}; и соответственно вызвать ее в любом модуле ехе проекта, то опять все работает, но это не выход, конечно.
My current solution (hack?) is to use a Ruby script as a post build step on my libraries. This script uses the dumpbin.exe tool to find all symbols containing the word 'forceLink', and it outputs a header file containing
#pragma comment(linker, "/include:symbol")
for each of the symbols. I then include that header file from my main project, and then everything just works.
Здравствуйте, df, Вы писали:
df>Привет всем.
df>Проблема следующая. Можно ли как-то заставить линкер включить в линковку модули из статической либы, которые по его мнению не используются в исполняемом файле.
...
auto __declspec(dllexport) ClassA123_registered = Repository::Instance().Register(p);
Похожая практика применяется в boost.serialization