-
Notifications
You must be signed in to change notification settings - Fork 168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide functions in ModelicaUtilities.h to external objects implemented as shared libraries #4476
Comments
@t-sommer this is a good proposal to address a long-standing problem, see modelica/ModelicaSpecification#2191. We proposed an alternative solution there a few months ago, which just requires all Modelica tools and simulation runtimes to export the symbols of those functions, so they can be dynamically linked both at compile time and at run time. Unfortunately there was no feedback from tool vendors (other than OMC, that is) back then. This already works in OpenModelica, we implemented and tested it in the ExternalMedia library. Unfortunately Dymola.exe does not export those symbols, so if some external functions are called during translation and they give an error, there is no way to properly report it. Hence, we conjured up a hack to pass those pointers to ExternalMedia static interface. I'm not sure which of the two solutions is better. Our proposal requires to slighly change how you building the Modelica compiler and the simulation runtime, all tool vendors could do it right away. Yours is easier to understand for non-hackers like myself, but it requires an addition to ModelicaServices, so it could be released with 4.2.0 sometime next year. For sure we should go ahead and implement one of these two ASAP. BTW, this is yet another issue at the boundary between MAP-Lang and MAP-Lib. @fedetftpolimi what do you think? |
From a quick check it should allow both static and dynamic libraries to access the pointers to the modelicaUtilities functions, so from a C/C++ perspective it seems good. However, the example is geared to external libraries based on external objects, for this approach to be complete is should also allow libraries based solely on external functions (such as ExternalMedia) to get the pointers. This could be as simple as allowing to declare an external function taking the parameter |
Passing instances of external objects to functions is already allowed today, so this should not require any changes to the Modelica language. |
@casella By the way, the two proposal are also not mutually exclusive. We may even support both allowing to retrieve the functions as symbols by the linker or pointers through the struct, even though there's no need to standardize both. In any case, what we need is at least one solution implemented by all tool vendors asap (and one that isn't just "use static libraries" because that's a dead end when you account for dependencies). |
For the sake of completeness:
|
The proposal in modelica/ModelicaSpecification#2191 we were referring to is the one @casella posted on on May 22, 2024: exporting symbols to allow automatic lookup by the dynamic linker/loader. The idea of providing a tool-specific shared object as far as I know is not portable, and it forces C/C++ library developers to provide different versions of their library differing only by each version being linked with the shared object of a particular vendor, which does not scale well. |
You're right, see Section 12.9.6.2 they were added in Modelica 3.5. @t-sommer you may edit your original proposal to keep it consistent to the latest standard. |
@HansOlsson the related issue modelica/ModelicaSpecification#2191 has been open for 7 years. I understand we now have two good proposals to fix it, so I would put it in the agenda of the MAP-Lang group. I have a slight preference for @t-sommers's proposal since it relies less on low-level C-code functionality (importing symbols in the external C functions) and uses explicitly defined Modelica functionality instead, but I'm not 100% sure it always works (see below). Besides that, @t-sommer's proposal requires changes to the language spec (regarding the contents of ModelicaUtilities.h) and to the MSL (adding the ModelicaUtilityFunctions to ModelicaServices library), while mine only requires to change the language spec. But I don't see that as a big deal, with a modicum of coordination between the two groups 😃 |
@t-sommer maybe @fedetftpolimi has a point. It is not clear to me where the external object should be instantiated in some cases. Consider for example this MWE (which is relevant for ExternalMedia): package MWE
partial function baseFunction
input Real x;
output Real y;
end baseFunction;
function function1
extends baseFunction;
algorithm
y := 2*x;
end function1;
function function2
extends baseFunction;
external;
end function2;
model M
replaceable function f = function1 constrainedby baseFunction;
Real y = f(time);
end M;
end MWE; Now, we want the external |
One idea could be to amend the specification of the external function interface by defining a pointer to an implicitly defined default instance function function2
extends baseFunction;
external "C"
y = extFunction2(x, modelicaUtilityFunctions.functions);
end function2; double extFunction2(double x, void *callbacks)
{
if (x > 0)
return 2*x;
else
(ModelicaUtilityFunctions_t *)callbacks().ModelicaError("Negative input not allowed");
} without the need of explicitly instantiating the As I understand, that's the only way (besides exporting and importing symbols) to handle utility functions in purely function-based implementations, such as is the case of the |
Please try to avoid using To me the current C-calling interface for Modelica was designed to interface existing C-code, and then people wrote "small" C-glue functions (or what-ever you call them) to handle other data-types etc. The ModelicaUtilities header works for the glue-functions, but not when building larger libraries specifically for Modelica-libraries. Obviously it is still possible to create glue-functions for a Modelica-library, but it becomes cumbersome to use it every time:
|
Oh, I looked more and ExternalMedia is written in C++ and uses That C/C++ interfacing is a real problem, that requires a different good solution which is completely different from what is discussed here. |
I like @casella idea of a pre-defined instance of the ModelicaUtilityFunctions external object. We should avoid the situation of creating several redundant instances of the ModelicaUtilityFunctions object. Now that I think of it, this is the first time I came up with the need for singletons in Modelica, is there a clean way to implement them? @HansOlsson true, whatever interface we come up with, we should avoid using C features that are incompatible with C++. So far a struct containing function pointers seems safe to me. One can still dream and hope that someday a true direct interface between Modelica and C++ will be standardized, and in that case ModelicaUtilityFunctions would become a C++ class as well as a Modelica ExternalObject, but I'd rather have the struct asap, as the current mess for calling modelicaError and such makes it impossible to support all tools and that is a bigger problem. |
The problem is that calling |
Am I missing something? I always thought it was implemented by throwing an exception (C code can be compiled with exceptions on essentially all platforms) so that if there are C++ functions on the call stack the destructors will be called. |
By the way, if this is not addressed, libraries such as ExternalMedia will always leak memory and likely crash the entire Modelica environment after attempting to recover from a ModelicaError... |
Yes.
Exceptions are a C++-feature, we have standardized on a C-interface for simplicity (and many C++-compilers can disable exceptions for some reason) - so that even other languages like Java etc can be called. |
The fact that the interface is in C does not forbid to implement ModelicaError by throwing an exception under the hood. If the exception propagates through C code it doesn't hurt, while if it propagates through C++ code it calls the proper destructors. You can have the best of both worlds, you just chose not to. You can make a pure C interface and yet be compatible with other languages, you just need to acknowledge that exceptions exist and handle them appropriately. The easiest way would be for the tool vendors to implement ModelicaError as an extern "C" function (thus keeping the interface in C) with a single throw statement in a C++ source file. The code calling external functions would need to go through a level of function calls including a try..catch statement, which honestly you should do anyway because what if the external function you're calling is not written in C but in a language that throws an exception? You don't want the Modelica tool to crash altogether, do you? |
Motion of order: the discussion about exception handling and ModelicaError is very interesting and important for future development of Modelica, but it is out of scope here, I would warmly invite @fedetftpolimi to make a proposal in a separate ticket and discuss it there 😃 The topic of this ticket is how to solve handle ModelicaError and other ModelicaUtilities.h functions in external objects that are implemented as shared libraries. In fact, I jumped in almost immediately with a related problem, namely handling these functions also in external functions that have no direct access to instantiated external objects. I think it would be valuable if we could find a common solutions to these two problems. |
I had some discussion with @henrikt-ma and @t-sommer about the issue that I raised with my MWE. This could be solved by providing a constant external object instance in ModelicaServices, besides the definition of such external object. within ModelicaServices;
package ModelicaUtilities "Provides external objects to access ModelicaUtilities.h functions"
class Callbacks "External object with pointers to ModelicaUtilities.h functions"
extends ExternalObject;
function constructor
// the implementation is provided by the tool developer, not specified in the MA ModelicaServices
end constructor;
function destructor
// ditto
end destructor;
end Callbacks;
constant Callbacks callbacks = Callbacks() "To be used in state-less functions not tied to external objects";
end ModelicaUtilities; The pointer to these Callbacks object should have a structure like the one proposed by t-sommer in his first post, or possibly, even better, following the same pattern that is used in FMI for the same purposes, see https://github.com/modelica/fmi-standard/blob/v2.0.x/headers/fmi2FunctionTypes.h#L119. As suggested by t-sommer at the beginning of this post, the package MWE
partial function baseFunction
input Real x;
output Real y;
end baseFunction;
function function1
extends baseFunction;
algorithm
y := 2*x;
end function1;
function function2
extends baseFunction;
function helper
input Real x;
input ModelicaServices.ModelicaUtilities.Callbacks callbacks;
output Real y;
external "C";
end helper;
y = helper(x, ModelicaServices.ModelicaUtilities.callbacks);
end function2;
function function3 // nice to have, but not strictly necessary
extends baseFunction;
external "C"
y = ext_function2(x, ModelicaServices.ModelicaUtilities.callbacks);
end function2;
model M
replaceable function f = function1 constrainedby baseFunction;
Real y = f(time);
end M;
end MWE; There would be two ways to do that. One, as in This proposal can be made into a PR to the ModelicaServices library. To complete it, we will also need a minor addition to the ModelicaSpecification Section 12.9.6, to specify the C types of the structure returned by the Callbacks external object. That is actually not a change to the Modelica language, as it has no impact on Modelica compilers, which just need to handle |
Except:
But, yes, it will be discussed. |
There's still one question I have. Once we have evidenced the need for the constant external object Is there even a point in creating multiple instances of the |
I've created an example library to demonstrate the implementation. It already works in Dymola and OpenModelica. |
@fedetftpolimi had a quite good idea, i.e. to combine @t-sommer's concept of an external callback object with his idea of importing symbols. This combination has many nice features:
@fedetftpolimi is implementing a prototype, that could become a reference implementation, and testing it with OpenModelica, see https://github.com/fedetftpolimi/ModelicaUtilities/tree/CallbacksAsConstant. If we see that it works, we can report it here and use it as a basis for a refined proposal. The prototype will work out of the box in any Modelica tool that exports the symbols of ModelicaUtilities function from both the compiler/GUI and the simulation runtime. |
I will investigate if it is possible to do this without external object. Specifically:
Or simply put: it will just work seamlessly. If one really wants to be safe there's not even a requirement that the Modelica tool exports the symbols in ModelicaUtilities.h - only that it makes them callable. |
We already did that research, and so far the issue of "just including ModelicaUtilities.h" is that it breaks when doing so from within a shared library (DLL) on Windows. I'm not a super-expert in that OS, but so far the only solution to force the Windows runtime linker to resolve symbols to the ModelicaUtilities functions is this piece of code That's why I jumped in at @t-sommer idea of the external object, as it would hide that low-level code behind a completely hidden tool-provided library that third-party libraries don't have to link against (otherwise we'd be back to making it impossible to make a third-party Modelica library distributed together with a shared library that works with all tools). |
There should be better of doing that. |
On Linux when building a shared library, by default by just providing the function declaration you can call a function not defined anywhere. At the linking stage when the shared object is built, it will be listed as an undefined reference without the need to tell where it can be found (that is in which other shared object or executable it is, as this is key to achieve tool vendor independence of third party libraries). When the shared object is loaded, then the runtime linker will try to find it and fail to load it if it can't. On Mac OS it is possible, but not the default. See this line I added to the ExternalMedia build system for how to do it: On Windows, both me and @mahge who knows about Windows more than me had to resort to basically doing the symbol lookup in code using the Windows API. I don't exclude there's an easier way, but until someone posts a working proof of concept implementation I'll keep assuming there isn't. |
Apparently this proposal of using an external object is not trivial to implement in OpenModelica because it currently lacks support for instantiating external objects at compile time, see OpenModelica/OpenModelica#13044 I underline the issue is in the external object and not in the idea of loading symbols at run-time, as proved by the fact that my MWE https://github.com/user-attachments/files/15516624/mwe.zip uses the same importer code without using an external object and already works with OpenModelica even at compile time. I'd be curious to see if @HansOlsson can provide a simpler MWE to resolve ModelicaUtilities functions's symbols in Windows from a DLL, as that could open the possibility to 'just #include "ModelicaUtilties.h"' without the need for an external object. |
We are working on it 😃 At the moment, the best solution I see is to have a constant external object defined in ModelicaServices, which all external functions and object can use by simply referring to it, Now, the whole point (and crucial issue) of this proposal is that it should work seamlessly both during compile time and simulation time. Implementing external object handling at compile time in general is a non-trivial task, but just implementing handling of constant external objects, which are nothing but a pointer to something that is obtained by calling the constructor, seems much easier to me. Please give us some time to figure this out and test it properly. |
After more investigation I thought that it didn't fully work; due to the order of DLL-loading. (But see the end.) Overall it is almost possible to handle it perfectly: in particular the library code could contain Specifically, what is possible is:
The only drawback is that you need a small wrapper in the Include-file with a forwarding function. Basically the above mean that you have
Having a struct with function pointer would also be possible, but seems messier. For FMUs the overhead can be made insignificant as the logging functions are inputs when creating the FMU-instance, and not needed for other calls. A similar approach also works if the library function differ from the ModelicaUtilities-functions. The hack is to use: |
Are you sure it works at compile time? The easiest way to make sure all tools support calling external functions at compile time is to have an header file included in the
and have the function definition in compiled form in the shared library. If the header file contains inline and/or static function definitions, such as in your example, then it becomes much more difficult for tool vendors to implement compile-time evaluation of external functions. Said otherwise, if the header only contains the function declaration, the Modelica compiler can just dlopen()[Linux/MacOS]/LoadLibrary()[Windows] the shared library, then dlsym()[Linux/MacOS]/GetProcAddress()[Windows] the pointer to the function, and use a library such as libffi to create a valid stack frame with the function parameters and call into it. I think this is slow (calling a C compiler isn't fast), complex, painful, error-prone, takes a lot of development effort from the tool vendors and ultimately is just an unnecessary hack. |
Yes, although there are other issues with that. As soon as you have functions using ModelicaMessage (and others) you potentially have side-effects, so it seems somewhat odd to evaluate it at compile time. It may be that it is only a debug-message that can be ignored (so why spend so much time getting it right?), or the good case of an error-message that shouldn't be triggered. ...
Well, loading unknown dlls into the main tool is problematic for a number of reasons, and with external objects that approach becomes even messier.
It only relies on what is currently specified in Modelica, whereas the procedure above relies on a number of assumptions - including the obvious one that libraries can also be statically linked. So, calling that a hack isn't accurate. |
The reason why getting it right is important is that external functions used to initialize package constants may fail and need to call ModelicaError. This happens with the ExternalMedia library for example, and is how I discovered the issue. Why you say an error message shouldn't be triggered at compile time? If you specify invalid parameters to an external function used to initialize a package constant I sure think you do want the compilation to fail with a meaningful error message, and the only way to report it would be calling ModelicaError. |
As I stated, that it is the good case. |
Currently it is not possible to access the functions in
ModelicaUtilities.h
from anExternalObject
that is implemented as a shared library in a generic way. Therefore I propose to introduce anExternalObject
into the MSL that is implemented by the tools and returns a pointer to the following structure.
This pointer can then be used by external objects to access these functions.
The text was updated successfully, but these errors were encountered: