Здравствуйте, Pavel Dvorkin, Вы писали:
PD> Да ради бога, не имеет значения. Важно, что можно в конечном счете сделать то, что нужно ТС.
Ну да.
PD> ·>Именно. Поэтому твои рекомендации использовать GetJavaVM не в ту степь. PD> Равно как и твои насчет версий JDK .
Это имеет значение по нескольким причинам. Главное тут, что он, похоже, собирается организовывать интеграцию частей от разных источников/вендоров плагинов. И тут возникает проблема, что все они одновременно должны работать вместе и как-то взаимодействовать.
Этим наелись уже давно, во времена application servers и прочего энтерпрайза. Мороки много, хитрые грабли вылазят. А если ещё и выгрузка плагинов/классов...
Версия JDK: представь себе какой-то старинный, но очень нужный плагин где-то там через зависимости хочет sun.misc.*, который доступен в JDK 7. И всё работает. А потом выходит новый и ещё более нужный плагин на JDK 21, а там какой-то класс deprecated и removed.
Вдобавок, кастомные ClassLoader-ы требуют осторожного обращения и понимания что происходит. Например, один и тот же класс загруженный разными класслоадерами — по сути два разных класса, у которых, например, свои глобальные переменные и хитрые исключения вроде cannot cast my.cool.SomeClass to my.cool.SomeClass.
Здравствуйте, ·, Вы писали:
·>Это имеет значение по нескольким причинам. Главное тут, что он, похоже, собирается организовывать интеграцию частей от разных источников/вендоров плагинов. И тут возникает проблема, что все они одновременно должны работать вместе и как-то взаимодействовать.
·>Этим наелись уже давно, во времена application servers и прочего энтерпрайза. Мороки много, хитрые грабли вылазят. А если ещё и выгрузка плагинов/классов...
·>Версия JDK: представь себе какой-то старинный, но очень нужный плагин где-то там через зависимости хочет sun.misc.*, который доступен в JDK 7. И всё работает. А потом выходит новый и ещё более нужный плагин на JDK 21, а там какой-то класс deprecated и removed.
·>Вдобавок, кастомные ClassLoader-ы требуют осторожного обращения и понимания что происходит. Например, один и тот же класс загруженный разными класслоадерами — по сути два разных класса, у которых, например, свои глобальные переменные и хитрые исключения вроде cannot cast my.cool.SomeClass to my.cool.SomeClass.
А вот с этим соглашусь, конечно, без сомнения.
Собственно, я уже собрался спать ложиться, но вспомнил об этом и решил еще один ответ написать. Но теперь не буду, так как ты изложил все, что я собирался написать.
Кстати, и не только экзотика в виде sun.misc. И не только версии JDK. А еще и просто зависимости от прочих артефактов, которые могут быть в основной java программе и других jar различными по версии. Если они там окажутся одновременно в итоге загрузки, то хорошего будет тоже мало.
Впрочем, это к JNI не имеет отношения, к плагинам на C/C++ и к runtime-загрузке jar тоже. Это обычная проблема на java. Если бы он просто добавил все свои jar в java — проект без всякого C/C++, было бы то же самое.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD> Кстати, и не только экзотика в виде sun.misc.
А ещё весёлые матрёшки как в jni плагине jar-зависимость, которая испольует jni... happy degugging.
PD> И не только версии JDK. А еще и просто зависимости от прочих артефактов, которые могут быть в основной java программе и других jar различными по версии. Если они там окажутся одновременно в итоге загрузки, то хорошего будет тоже мало.
Формально этот самый кастомный ClassLoader можно создавать "чистым", т.е. ты контролируешь что туда попадает и иметь плагины изолированными от всего, кроме JRE, системного classpath.
Но скорее всего плагинам надо между собой как-то взаимодействовать (вопрос к топикстартеру) и тут придётся серьёзно продумать архитектуру... и, скорее всего получится кошмар в духе OSGi. Более разумным решением, имхо, будет явное взаимодействие через какие-нибудь сообщения protobuf или подобное.
PD> Впрочем, это к JNI не имеет отношения, к плагинам на C/C++ и к runtime-загрузке jar тоже. Это обычная проблема на java. Если бы он просто добавил все свои jar в java — проект без всякого C/C++, было бы то же самое.
Угу. Хотя это не проблема конкретно java, а общая проблема сборки гигантских монолитных приложений из множества разрозненных компонент. А тут ещё и кросс-языковое приложение java/c++.
Здравствуйте, Ablex, Вы писали:
A>Java-приложение (назовем его "главное" приложение) загружает библиотеку на C++, которая грузит плагины (тоже на C++) A>Далее хотелось бы из некоторых плагинов (видимо используя JNI) приаттачиться к VM из "главного" приложения и догрузить туда еще пачку JAR файлов.
A>Можно такое (выделенное жирным) сделать и как?
Резюмируя срач дискуссию
* загрузить jar в java нельзя. в java загружаются классы и ресурсы. для загрузки классов нужен класслоадер. Для загрузки из jar файлов обычно используется стандартный URLClassLoader
* самое простое это настроить этот самый класслоадер при старте JVM (см параметры команды java в частности cp)
От себя
* динамическая загрузка классов подразумевает динамическую же выгрузку классов — отдельный головняк
* схема в целом выглядит кринжово, я бы не стал так делать. если можешь стартовать свой си++ код в отдельном процессе то так и делай, не морочь голову.
Здравствуйте, ·, Вы писали:
·>А ещё весёлые матрёшки как в jni плагине jar-зависимость, которая испольует jni... happy degugging.
PD>> И не только версии JDK. А еще и просто зависимости от прочих артефактов, которые могут быть в основной java программе и других jar различными по версии. Если они там окажутся одновременно в итоге загрузки, то хорошего будет тоже мало. ·>Формально этот самый кастомный ClassLoader можно создавать "чистым", т.е. ты контролируешь что туда попадает и иметь плагины изолированными от всего, кроме JRE, системного classpath. ·>Но скорее всего плагинам надо между собой как-то взаимодействовать (вопрос к топикстартеру) и тут придётся серьёзно продумать архитектуру... и, скорее всего получится кошмар в духе OSGi. Более разумным решением, имхо, будет явное взаимодействие через какие-нибудь сообщения protobuf или подобное.
Может быть и protobuf, может, и просто что-то из IPC, но это вопрос к ТС. И это значит все же несколько процессов. Мы же не знаем, зачем эти jar подгружать вообще, что они делать должны. Более того, и плагинами их можно называть с натяжкой. Зачем их нужно загружать, почему именно по требованию и что они будут делать — этого мы не знаем. Может, вообще есть более простое решение.
PD>> Впрочем, это к JNI не имеет отношения, к плагинам на C/C++ и к runtime-загрузке jar тоже. Это обычная проблема на java. Если бы он просто добавил все свои jar в java — проект без всякого C/C++, было бы то же самое. ·>Угу. Хотя это не проблема конкретно java, а общая проблема сборки гигантских монолитных приложений из множества разрозненных компонент. А тут ещё и кросс-языковое приложение java/c++.
Ну кроссплатформенное он постольку-поскольку. В той модели, что предложил ТС, java и C++ части достаточно хорошо отделены. Но вообще да. Не знаю, почему в java столь усложнили динамическую загрузку кода. В C++ (точнее, в Win API) загрузка и выгрузка DLL в рантайме совершенно штатный и банальный процесс, на то они и DLL. И грузить их можно из любого места на диске, без всяких classpath и лоадеров. Но если EXE использует одну версию C++ RTL, а DLL другую, то мало никому не покажется, если, например, выделять память в DLL и освобождать ее в EXE.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Ну кроссплатформенное он постольку-поскольку. В той модели, что предложил ТС, java и C++ части достаточно хорошо отделены. Но вообще да. Не знаю, почему в java столь усложнили динамическую загрузку кода. В C++ (точнее, в Win API) загрузка и выгрузка DLL в рантайме совершенно штатный и банальный процесс, на то они и DLL. И грузить их можно из любого места на диске, без всяких classpath и лоадеров. Но если EXE использует одну версию C++ RTL, а DLL другую, то мало никому не покажется, если, например, выделять память в DLL и освобождать ее в EXE.
Потому что в java загружается не код, а класс (ClassLoader же!), а значит тут тебе и статические переменные, и сборка мусора. И проблема собственно не в загрузке, а в общем дизайне решения, архитектуре системы, когда загружается много чего и начинает друг с другом взаимодейстовать. Так что по честному надо не с DLL сравнивать, а хотя бы с COM.
Да и вообще, ничего усложнённого я не вижу, две строчки кода. Гораздо проще, чем загружать dll — по сути указали источники, взяли класс по имени и готово.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Pavel Dvorkin, Вы писали:
·>Потому что в java загружается не код, а класс (ClassLoader же!), а значит тут тебе и статические переменные, и сборка мусора. И проблема собственно не в загрузке, а в общем дизайне решения, архитектуре системы, когда загружается много чего и начинает друг с другом взаимодейстовать. Так что по честному надо не с DLL сравнивать, а хотя бы с COM.
Отчасти согласен. В DLL, конечно, нет уже фактически классов, только код и данные. Впрочем, и это не совсем так. Экспортируемые классы есть, вот от внутренних действительно почти ничего не осталось. И сборки мусора, конечно, тоже нет. Но там тоже всякого хватает. Статические переменные тоже есть, более того, есть shared данные , это один из механизмов IPC — через общую секцию в DLL. Да и DllMain вообще-то не подарок, со всеми правилами, что можно в ней делать, а что нет. Плюс еще загрузка всех import-DLL этой DLL и рекурсивно далее.
Но COM, да, больше подходит для сравнения.
·>Да и вообще, ничего усложнённого я не вижу, две строчки кода. Гораздо проще, чем загружать dll — по сути указали источники, взяли класс по имени и готово.
А почему действительно нет загрузки jar ? Он же по сути аналог DLL/COM тут — набор клаcсов. Почему нельзя его просто загрузить ? При статическом подключении (явно или через pom.xml) фактически он же загружается со всеми своими потрохами.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD> Отчасти согласен. В DLL, конечно, нет уже фактически классов, только код и данные. Впрочем, и это не совсем так. Экспортируемые классы есть, вот от внутренних действительно почти ничего не осталось.
Классы в dll? Гы, такой гемор. Обычно из юзабельного только extern "C".
PD> Но COM, да, больше подходит для сравнения.
Угу, и в COM гораздо более грустно.
PD> ·>Да и вообще, ничего усложнённого я не вижу, две строчки кода. Гораздо проще, чем загружать dll — по сути указали источники, взяли класс по имени и готово. PD> А почему действительно нет загрузки jar ? Он же по сути аналог DLL/COM тут — набор клаcсов. Почему нельзя его просто загрузить ? При статическом подключении (явно или через pom.xml) фактически он же загружается со всеми своими потрохами.
Я не знаю что может означать "jar загружается". Есть classpath — места где ищутся классы. И это вовсе не обязательно должны быть jar (который просто обычный zip-архив). Классы могут грузится и из обычной директории, из на лету сгенерённого массива байтов и даже из сети.
В качестве аналогии можно сказать так: dll — соответсвует java-классу, а jar — это LD_LIBRARY_PATH. Вот и попробуй представь что может означать "загрузка LD_LIBRARY_PATH".
Здравствуйте, ·, Вы писали:
·>Классы в dll? Гы, такой гемор. Обычно из юзабельного только extern "C".
Да нет, ничего особенного. Вполне можно использовать.
Собственно говоря, класс есть класс независимо от того, лежит он в EXE или DLL
А как, собственно, делать иначе, если некий класс нужен в нескольких project из этого solution ? Дублировать его ?
Ну а уж в C# в DLL только классы и есть. Там даже есть специальный тип проекта в VS — Class Library
PD>> ·>Да и вообще, ничего усложнённого я не вижу, две строчки кода. Гораздо проще, чем загружать dll — по сути указали источники, взяли класс по имени и готово. PD>> А почему действительно нет загрузки jar ? Он же по сути аналог DLL/COM тут — набор клаcсов. Почему нельзя его просто загрузить ? При статическом подключении (явно или через pom.xml) фактически он же загружается со всеми своими потрохами. ·>Я не знаю что может означать "jar загружается". Есть classpath — места где ищутся классы. И это вовсе не обязательно должны быть jar (который просто обычный zip-архив). Классы могут грузится и из обычной директории, из на лету сгенерённого массива байтов и даже из сети.
Означать может просто "подключить все классы из этого архива". Иными словами, сделать то же самое, что делается при прописывании dependency на него в pom.xml. Только в рантайме, а не статически.
Так же как LoadLibrary означает "загрузить все, что в ней есть"
·>В качестве аналогии можно сказать так: dll — соответсвует java-классу, а jar — это LD_LIBRARY_PATH. Вот и попробуй представь что может означать "загрузка LD_LIBRARY_PATH".
Ну даже чисто формально DLL не есть один класс (или namespace), а целая коллекция средств — например, WinSock или GDI. Ты же не скажешь, что GDI32.DLL — это один класс ?
И уж в любом случае это один файл, а не path
Здравствуйте, Pavel Dvorkin, Вы писали:
PD> ·>Классы в dll? Гы, такой гемор. Обычно из юзабельного только extern "C". PD> Да нет, ничего особенного. Вполне можно использовать.
Можно, но только когда у тебя один большой проект, собирается одной версией компилятора с идентичным списком ключей. И то тебе придётся преобразовывать std::string в CString из BSTR и ещё хз чего. В случае плагинов, как у топикстартера, когда dll-и приходят из разных источников от разных вендоров, то упс.
PD> Собственно говоря, класс есть класс независимо от того, лежит он в EXE или DLL PD> А как, собственно, делать иначе, если некий класс нужен в нескольких project из этого solution ? Дублировать его ?
Если нужен реюзабельный компонент, то оборачивать в extern "C".
PD> ·>Я не знаю что может означать "jar загружается". Есть classpath — места где ищутся классы. И это вовсе не обязательно должны быть jar (который просто обычный zip-архив). Классы могут грузится и из обычной директории, из на лету сгенерённого массива байтов и даже из сети. PD> Означать может просто "подключить все классы из этого архива".
Что значит "подключить класс"?
PD> Иными словами, сделать то же самое, что делается при прописывании dependency на него в pom.xml. Только в рантайме, а не статически.
Ну так и с dll — всё так же. При запуске приолжения dll-ки времени сборки проекта подгружаются так же как обычно и jar-ки в pom.xml. А динамической подгрузкой надо звать LoadLibrary и GetProcAddress и мучиться с источниками. Всё то же, всё так же.
PD> Так же как LoadLibrary означает "загрузить все, что в ней есть"
Под "загрузить" ты, наверное, имеешь в виду "вызвать DllMain". Потом всё равно придётся выковыривать части по строковым именам используя GetProcAddress.
LoadLibrary — соответсвует new ClassLoader, а GetProcAddress — loadClass().
PD> ·>В качестве аналогии можно сказать так: dll — соответсвует java-классу, а jar — это LD_LIBRARY_PATH. Вот и попробуй представь что может означать "загрузка LD_LIBRARY_PATH". PD> Ну даже чисто формально DLL не есть один класс (или namespace), а целая коллекция средств — например, WinSock или GDI. Ты же не скажешь, что GDI32.DLL — это один класс ? PD> И уж в любом случае это один файл, а не path
Ну я же говорю, что это аналогия, а не точное соответствие.
ClassLoader позволяет хитрые манипуляции, полностью контролируя загрузку каждого класса. Примерно как допустим ты дёргаешь функцию в GDI32, а это что-то где-то внутре хочет дёрнуть другую функцию, а твой кастомный ClassLoader может взять его из другого места или даже пропатчить байткод на лету.
Здравствуйте, ·, Вы писали:
PD>> ·>Классы в dll? Гы, такой гемор. Обычно из юзабельного только extern "C". PD>> Да нет, ничего особенного. Вполне можно использовать. ·>Можно, но только когда у тебя один большой проект, собирается одной версией компилятора с идентичным списком ключей. И то тебе придётся преобразовывать std::string в CString из BSTR и ещё хз чего. В случае плагинов, как у топикстартера, когда dll-и приходят из разных источников от разных вендоров, то упс.
Ну это проблемы ТС. Он о том, что это за плугины и даже о том, сам они их пишет или нет, ничего не говорил, да и не надо было.
А так да, одной версий компилятора. Впрочем, если в проекте окажется просто DLL, сделанная другой версией компилятора, то проблем может быть тоже достаточно даже без классов в DLL.
PD>> Собственно говоря, класс есть класс независимо от того, лежит он в EXE или DLL PD>> А как, собственно, делать иначе, если некий класс нужен в нескольких project из этого solution ? Дублировать его ? ·>Если нужен реюзабельный компонент, то оборачивать в extern "C".
Ну это порой легче сказать, чем сделать. Есть какая-то иерархия классов, с виртуальными методами, попробуй ее обернуть в extern "C". Я не говорю, что вообще невозможно, но проблем будет намного больше. А потом однажды к этой иерархии добавится еще один наследник и станет совсем хорошо. А если этот наследник понадобится почему-то разместить в другой DLL или в EXE, то станет очень весело
Ну и опять же — C++ все-таки legacy язык, в него классы были добавлены, когда его из C сделали. А вот в C# прекрасно импортируются классы, потому что кроме классов, там ничего и нет.
PD>> ·>Я не знаю что может означать "jar загружается". Есть classpath — места где ищутся классы. И это вовсе не обязательно должны быть jar (который просто обычный zip-архив). Классы могут грузится и из обычной директории, из на лету сгенерённого массива байтов и даже из сети. PD>> Означать может просто "подключить все классы из этого архива". ·>Что значит "подключить класс"?
Так в следующей строчке объяснение, оно теперь чуть ниже. Выделю.
PD>> Иными словами, сделать то же самое, что делается при прописывании dependency на него в pom.xml. Только в рантайме, а не статически. ·>Ну так и с dll — всё так же. При запуске приолжения dll-ки времени сборки проекта подгружаются так же как обычно и jar-ки в pom.xml. А динамической подгрузкой надо звать LoadLibrary и GetProcAddress и мучиться с источниками. Всё то же, всё так же.
Динамическая загрузка DLL мало чем отличается от статической. То же проецирование DLL в АП процесса, то же разрешение импортов. Только делается в рантайме, а не при запуске. А GetProcAddress к LoadLibary отношения не имеет, это потом. Я спокойно могу сделать DLL с одними ресурсами и/или внутренним кодом (в DLLMain), в которой не будет ни одного экспорта.
PD>> Так же как LoadLibrary означает "загрузить все, что в ней есть" ·>Под "загрузить" ты, наверное, имеешь в виду "вызвать DllMain". Потом всё равно придётся выковыривать части по строковым именам используя GetProcAddress.
Нет, не только. Сначала маппируется DLL на АП процесса, а потом действительно вызов DLLMain. Собственно, вызов DLLMain к загрузке отношения не имеет. Она будет вызываться и при загрузке, и при запуске/удалении потока. Это просто некое место для настройки одному из 4 событий.
·>LoadLibrary — соответсвует new ClassLoader, а GetProcAddress — loadClass().
new ClassLoader может загрузить все классы из этого jar ? Если да — то нет вопроса. Но, как я понимаю, нет ?
И вообще, в чем, собственно, концептуальная разница между DLL в .net и jar в Java ? ИМХО ее практически нет. И там и тут набор доступных извне классов. Ну а то, что там их упаковали в MZ/PE-файл, а тут в zip-файл — это дело вкуса. Смотреть что есть в этом jar/zip, конечно, проще, но и содержимое DLL посмотреть тоже не очень сложно.
PD>> ·>В качестве аналогии можно сказать так: dll — соответсвует java-классу, а jar — это LD_LIBRARY_PATH. Вот и попробуй представь что может означать "загрузка LD_LIBRARY_PATH". PD>> Ну даже чисто формально DLL не есть один класс (или namespace), а целая коллекция средств — например, WinSock или GDI. Ты же не скажешь, что GDI32.DLL — это один класс ? PD>> И уж в любом случае это один файл, а не path ·>Ну я же говорю, что это аналогия, а не точное соответствие.
·>ClassLoader позволяет хитрые манипуляции, полностью контролируя загрузку каждого класса. Примерно как допустим ты дёргаешь функцию в GDI32, а это что-то где-то внутре хочет дёрнуть другую функцию, а твой кастомный ClassLoader может взять его из другого места или даже пропатчить байткод на лету.
Да бога ради. LoadLibrary может взять из любого места. Загрузка DLL из памяти тоже возможна.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD> то проблем может быть тоже достаточно даже без классов в DLL.
... PD> Ну это порой легче сказать, чем сделать. Есть какая-то иерархия классов, с виртуальными методами, попробуй ее обернуть в extern "C". Я не говорю, что вообще невозможно, но проблем будет намного больше. А потом однажды к этой иерархии добавится еще один наследник и станет совсем хорошо. А если этот наследник понадобится почему-то разместить в другой DLL или в EXE, то станет очень весело
Именно, поэтому твоё "Не знаю, почему в java столь усложнили динамическую загрузку кода.". Ответ очевидный: "ничего не усложнили, всё очень просто".
PD> Ну и опять же — C++ все-таки legacy язык, в него классы были добавлены, когда его из C сделали. А вот в C# прекрасно импортируются классы, потому что кроме классов, там ничего и нет.
Ну а как думаешь с кого c# содрал все идеи? Правда хреновенько содрал...
PD> ·>Что значит "подключить класс"? PD> Так в следующей строчке объяснение, оно теперь чуть ниже. Выделю.
Ну так не класс подключается же, а просто classpath прописывается, аналог LD_PATH. Я тебе который раз объясняю.
PD> Динамическая загрузка DLL мало чем отличается от статической.
В java вообще ничем не отличается. Если посмотришь устройство "java.exe" он просто создаёт самый обыкновенный class loader, ищет в нём указанный класс и дёргает у него метод по имени "main".
PD> То же проецирование DLL в АП процесса, то же разрешение импортов. Только делается в рантайме, а не при запуске. А GetProcAddress к LoadLibary отношения не имеет, это потом. Я спокойно могу сделать DLL с одними ресурсами и/или внутренним кодом (в DLLMain), в которой не будет ни одного экспорта.
Ну так и loadClass можешь не дёргать, а брать ресурсы через getResource.
PD> ·>Под "загрузить" ты, наверное, имеешь в виду "вызвать DllMain". Потом всё равно придётся выковыривать части по строковым именам используя GetProcAddress. PD> Нет, не только. Сначала маппируется DLL на АП процесса, а потом действительно вызов DLLMain.
Это уже невидимая кухня как конкретно реализован LoadLibary унутре.
PD> Собственно, вызов DLLMain к загрузке отношения не имеет.
У тебя этот параграф напрямую противоречит следующему: PD> Она будет вызываться и при загрузке, и при запуске/удалении потока. Это просто некое место для настройки одному из 4 событий.
Именно. Вот теперь объясни что же по-твоему должна делать эта твоя "загрузка jar".
PD> ·>LoadLibrary — соответсвует new ClassLoader, а GetProcAddress — loadClass(). PD> new ClassLoader может загрузить все классы из этого jar ? Если да — то нет вопроса. Но, как я понимаю, нет ?
ЕЩЁ РАЗ. Что значит "загрузить все классы"? Это бессмысленный вопрос.
Когда зовёшь loadClass напрямую (аналог GetProcAddress) ты достаёшь некий известный ожидаемый класс из заданных путей для поиска. Потом его можешь инстанциировать. Вот в этот момент VM смотрит что для инстанциации нужно. Напрмер, она рекурсивно дёргает loadClass для родительских классов, для типов параметров методов, вызывает статические инициализаторы для глобальных переменных и т.д, и т.п.
PD> И вообще, в чем, собственно, концептуальная разница между DLL в .net и jar в Java ? ИМХО ее практически нет.
DLL в .net это индуские перепевы jar в Java. Речь первоначально шла о C++.
PD> ·>ClassLoader позволяет хитрые манипуляции, полностью контролируя загрузку каждого класса. Примерно как допустим ты дёргаешь функцию в GDI32, а это что-то где-то внутре хочет дёрнуть другую функцию, а твой кастомный ClassLoader может взять его из другого места или даже пропатчить байткод на лету. PD> Да бога ради. LoadLibrary может взять из любого места.