diff --git a/doc/APIreference/APIglobals.rst b/doc/APIreference/APIglobals.rst index 951d0b34..c2e614d8 100644 --- a/doc/APIreference/APIglobals.rst +++ b/doc/APIreference/APIglobals.rst @@ -691,17 +691,13 @@ mjPLUGIN_LIB_INIT .. code-block:: C - #define mjPLUGIN_LIB_INIT \ - static void _mjplugin_dllmain(void); \ - mjEXTERNC int __stdcall mjDLLMAIN(void* hinst, unsigned long reason, void* reserved) { \ - if (reason == 1) { \ - _mjplugin_dllmain(); \ - } \ - return 1; \ - } \ - static void _mjplugin_dllmain(void) + #define mjPLUGIN_LIB_INIT(n) \ + static void _mj_init_##n(void) __attribute__((constructor)); \ + static void _mj_init_##n(void) -Register a plugin as a dynamic library. See :ref:`plugin registration` for more details. +Register a plugin before `main()` is called. This macro takes a unique identifier `n` as an argument that is used to avoid +name collisions between different plugin initialization functions. See :ref:`plugin registration` for +more details. .. _tyXMacro: diff --git a/doc/changelog.rst b/doc/changelog.rst index c14b133b..b97dce5e 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -44,6 +44,10 @@ General - The ``vertcollide`` field in :ref:`mjsFlex` has been removed. It is no longer required since :doc:`MuJoCo Warp ` supports native flex collisions. + - :ref:`mjPLUGIN_LIB_INIT` macro now requires a name argument to avoid initialization function name collisions. + When building with MSVC, we now use the C runtime initialization section to initialize plugins instead of + ``DllMain``. See :ref:`plugin registration` for more details. + - The :ref:`mjtWarning` enum value ``mjWARN_VGEOMFULL`` is removed. Exhaustion of visual geoms is now handled internally by the :ref:`mjvScene`. diff --git a/doc/programming/extension.rst b/doc/programming/extension.rst index 2b25d493..f4a8ee0f 100644 --- a/doc/programming/extension.rst +++ b/doc/programming/extension.rst @@ -230,12 +230,11 @@ troubleshoot issues with a model) can be statically linked into the application. :ref:`mjpPlugin` struct in the ``main`` function, then passing it to :ref:`mjp_registerPlugin` to be registered with MuJoCo. -Generally, reusable plugins are expected to be packaged as dynamic libraries. A dynamic library containing one or more -MuJoCo plugins should make sure that all plugins are registered when the library is loaded. In GCC-compatible compilers, -this can be achieved by calling :ref:`mjp_registerPlugin` in a function that is declared with -``__attribute__((constructor))``, while in MSVC this can be done in a DLL entry point (canonically known as -``DllMain``). MuJoCo provides a convenience macro :ref:`mjPLUGIN_LIB_INIT` that expands to either of these -constructs depending on the compiler used. +Generally, reusable plugins are expected to be packaged as libraries and should be registered when the library is +loaded. In GCC-compatible compilers, this can be achieved by calling :ref:`mjp_registerPlugin` in a function that is +declared with ``__attribute__((constructor))``, while in MSVC this can be done by injecting code into the C runtime +initialization. MuJoCo provides a convenience macro :ref:`mjPLUGIN_LIB_INIT` that expands to either of these constructs +depending on the compiler used. Users of plugins that are delivered as dynamic libraries as described above can load the library using the function :ref:`mj_loadPluginLibrary`. This is the preferred way to load dynamic libraries containing MuJoCo plugins (rather than, diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h index 7c6300b9..174a1f63 100644 --- a/include/mujoco/mjplugin.h +++ b/include/mujoco/mjplugin.h @@ -182,39 +182,46 @@ struct mjSDF_ { }; typedef struct mjSDF_ mjSDF; +//------------------------------------ Initialization ---------------------------------------------- + #if defined(__has_attribute) - #if __has_attribute(constructor) - #define mjPLUGIN_LIB_INIT __attribute__((constructor)) static void _mjplugin_init(void) - #endif // __has_attribute(constructor) - -#elif defined(_MSC_VER) - - #ifndef mjDLLMAIN - #define mjDLLMAIN DllMain + #define mjPLUGIN_LIB_INIT(n) \ + static void _mj_init_##n(void) __attribute__((constructor)); \ + static void _mj_init_##n(void) #endif - - #if !defined(mjEXTERNC) - #if defined(__cplusplus) - #define mjEXTERNC extern "C" +#elif defined(_MSC_VER) + // on x86, symbols are decorated with a leading underscore + #ifdef _M_IX86 + #define LINKER_NAME "__mj_ptr_" #else - #define mjEXTERNC - #endif // defined(__cplusplus) - #endif // !defined(mjEXTERNC) + #define LINKER_NAME "_mj_ptr_" + #endif - // NOLINTBEGIN(runtime/int) - #define mjPLUGIN_LIB_INIT \ - static void _mjplugin_dllmain(void); \ - mjEXTERNC int __stdcall mjDLLMAIN(void* hinst, unsigned long reason, void* reserved) { \ - if (reason == 1) { \ - _mjplugin_dllmain(); \ - } \ - return 1; \ - } \ - static void _mjplugin_dllmain(void) - // NOLINTEND(runtime/int) + #pragma section(".CRT$XCU", read) -#endif // defined(_MSC_VER) + #if !defined(mjEXTERNC) + #if defined(__cplusplus) + #define mjEXTERNC extern "C" + #else + #define mjEXTERNC + #endif // defined(__cplusplus) + #endif // !defined(mjEXTERNC) + + #define mjPLUGIN_LIB_INIT(n) \ + static void __cdecl _mj_init_##n(void); \ + /* use mjEXTERNC to prevent C++ name mangling */ \ + /* allocate the function pointer to the .CRT$XCU section of the executable */ \ + /* functions in this section are executed on startup before calling main() */ \ + mjEXTERNC __declspec(allocate(".CRT$XCU")) \ + void (__cdecl * _mj_ptr_##n)(void) = _mj_init_##n; \ + /* Force the linker to include the pointer symbol */ \ + __pragma(comment(linker, "/include:" LINKER_NAME #n)) \ + static void __cdecl _mj_init_##n(void) + +#else + #error "Unknown compiler: Plugin registration not supported." +#endif // function pointer type for mj_loadAllPluginLibraries callback typedef void (*mjfPluginLibraryLoadCallback)(const char* filename, int first, int count); diff --git a/plugin/actuator/register.cc b/plugin/actuator/register.cc index 66f24b93..e3c3e2f5 100644 --- a/plugin/actuator/register.cc +++ b/plugin/actuator/register.cc @@ -17,6 +17,6 @@ namespace mujoco::plugin::actuator { -mjPLUGIN_LIB_INIT { Pid::RegisterPlugin(); } +mjPLUGIN_LIB_INIT(actuator) { Pid::RegisterPlugin(); } } // namespace mujoco::plugin::actuator diff --git a/plugin/elasticity/register.cc b/plugin/elasticity/register.cc index ab8d283a..b4a68754 100644 --- a/plugin/elasticity/register.cc +++ b/plugin/elasticity/register.cc @@ -17,7 +17,7 @@ namespace mujoco::plugin::elasticity { -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(elasticity) { Cable::RegisterPlugin(); } diff --git a/plugin/obj_decoder/obj_decoder.cc b/plugin/obj_decoder/obj_decoder.cc index 18cfdf5f..929e959a 100644 --- a/plugin/obj_decoder/obj_decoder.cc +++ b/plugin/obj_decoder/obj_decoder.cc @@ -114,7 +114,7 @@ int CanDecode(const mjResource* resource) { } // namespace -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(obj_decoder) { mjpDecoder decoder; mjp_defaultDecoder(&decoder); decoder.content_type = "model/obj"; diff --git a/plugin/sdf/register.cc b/plugin/sdf/register.cc index 4e727e38..97f140da 100644 --- a/plugin/sdf/register.cc +++ b/plugin/sdf/register.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include "bolt.h" #include "bowl.h" #include "gear.h" @@ -20,7 +21,7 @@ namespace mujoco::plugin::sdf { -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(sdf) { Bolt::RegisterPlugin(); Bowl::RegisterPlugin(); Gear::RegisterPlugin(); diff --git a/plugin/sensor/register.cc b/plugin/sensor/register.cc index dd8a70d8..b3a587af 100644 --- a/plugin/sensor/register.cc +++ b/plugin/sensor/register.cc @@ -17,7 +17,7 @@ namespace mujoco::plugin::sensor { -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(sensor) { TouchGrid::RegisterPlugin(); } diff --git a/plugin/stl_decoder/stl_decoder.cc b/plugin/stl_decoder/stl_decoder.cc index c6932d9a..7e1d1a5a 100644 --- a/plugin/stl_decoder/stl_decoder.cc +++ b/plugin/stl_decoder/stl_decoder.cc @@ -132,7 +132,7 @@ int CanDecode(const mjResource* resource) { } // namespace -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(stl_decoder) { mjpDecoder decoder; mjp_defaultDecoder(&decoder); decoder.content_type = "model/stl"; diff --git a/plugin/usd_decoder/usd_decoder.cc b/plugin/usd_decoder/usd_decoder.cc index 58a24590..f1ab1c05 100644 --- a/plugin/usd_decoder/usd_decoder.cc +++ b/plugin/usd_decoder/usd_decoder.cc @@ -2459,7 +2459,7 @@ int CanDecode(const mjResource* resource) { } // namespace // clang-format off -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(usd_decoder) { mjpDecoder decoder; mjp_defaultDecoder(&decoder); decoder.content_type = "model/usd"; diff --git a/src/experimental/mjz/mjz_decoder.cc b/src/experimental/mjz/mjz_decoder.cc index 8defa31d..ee29a80f 100644 --- a/src/experimental/mjz/mjz_decoder.cc +++ b/src/experimental/mjz/mjz_decoder.cc @@ -192,7 +192,7 @@ static mjSpec* ParseZipBuffer(const void* buffer, int nbuffer, const char* name, return mj_parseXML(root.c_str(), vfs, error, error_sz); } -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(mjz_decoder) { mjpDecoder decoder; decoder.content_type = "application/zip"; decoder.extension = ".mjz|.zip"; diff --git a/src/experimental/platform/hal/renderer.cc b/src/experimental/platform/hal/renderer.cc index 4a9be6c1..2dc60e18 100644 --- a/src/experimental/platform/hal/renderer.cc +++ b/src/experimental/platform/hal/renderer.cc @@ -238,7 +238,7 @@ void Renderer::UpdateFps() { } // namespace mujoco::platform -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(renderer) { mujoco::platform::GuiPlugin plugin; plugin.name = "Filament"; plugin.update = [](mujoco::platform::GuiPlugin* self) { diff --git a/src/experimental/platform/ux/object_launcher_plugin.cc b/src/experimental/platform/ux/object_launcher_plugin.cc index d4de9414..b012e957 100644 --- a/src/experimental/platform/ux/object_launcher_plugin.cc +++ b/src/experimental/platform/ux/object_launcher_plugin.cc @@ -175,7 +175,7 @@ class ObjectLauncher { } // namespace mujoco::studio -mjPLUGIN_LIB_INIT { +mjPLUGIN_LIB_INIT(object_launcher) { using mujoco::studio::ObjectLauncher; static ObjectLauncher plugin;