ATTENTIONThis FlexSim Community Forum is read-only. Please post any new questions, ideas, or discussions to our new community (we call it Answers) at https://answers.flexsim.com/. Our new Question & Answer site brings a modern, mobile-friendly interface and more focus on getting answers quickly. There are a few differences between how our new Q&A community works vs. a classic, threaded-conversation-style forum like the one below, so be sure to read our Answers Best Practices. |
flexsim.com |
|
Downloads |
Wiki Articles Instructional documents authored collectively by the Flexsim community |
#1
|
||||
|
||||
Creating DLLs that attach to Flexsim
This article explains how to implement a dll using Flexsim.
To build a Flexsim DLL, you first need to start with a Visual Studio DLL project. Below is a link to the latest version of the DLL Maker project. https://bitbucket.org/flexsim/flexsi...er/get/tip.zip Included with the above mentioned projects are two essential c++ header files, namely flexsimfuncs.h and flexsimdefs.h. Over time and as new versions of Flexsim are released, these files may need to be changed to access new functionality that is made available by Flexsim in newer versions (as well as to better access functionality that is already available). To allow for this regular maintenance, we have the source code hosted on BitBucket. The project can be found at https://bitbucket.org/flexsim/flexsim-dll-maker/. We use mercurial for source code management, so you can get a clone with: hg clone https://bitbucket.org/flexsim/flexsim-dll-maker The latest version of flexsimfuncs.h and flexsimdefs.h are available at: https://bitbucket.org/flexsim/flexsi...FlexsimFuncs.h. https://bitbucket.org/flexsim/flexsi.../FlexsimDefs.h. Implementing the DLL to connect with Flexsim There are multiple steps involved with implementing the DLL so that it properly connects with Flexsim. The DLL's purpose is to implement functions that will supplant functions normally called on flexscript/c++ toggled nodes in your model. The advantage of this is two-fold. First, and probably most important, it gives you the advantages of C++, including speed and ultimate flexibility, without the need for the modeler to compile every time they open the model. Second, it allows you to hide your code if you would like to protect certain intellectual property. First, you'll want to implement the appropriate function on the dll side. An example function is provided in the DLL Maker project: Code:
#include "FlexsimDefs.h" visible double mydllfunction1(FLEXSIMINTERFACE) { pt("This will print to the Output Console");pr(); return 0; } visible double the_function_name(FLEXSIMINTERFACE) Once you've declared the function appropriately, you can access parameters and write code just like you would write code in a trigger or other code piece in Flexsim. For example, you can use parnode, parval, msgsendingobject and msgparam for message triggers, etc. For the most part you can just copy the code straight from a c++/flexscript code field into the function (although there are a few caveats that are explained later). Once you've defined the function on the dll side, you need to go into Flexsim and define how the node connects to the dll. If you, for example, want to have a message trigger call a dll function, go to the code field of that trigger and press the "DLL" radio button at the bottom of the code field. You should get a message saying something like "Would you like to format the code for dll linking." Click OK. This will toggle the trigger for dll connection and change the code to be the a default format. When Flexsim executes a dll toggled node, it looks for two quote-enclosed texts, and ignores the rest of the field. This lets you define template code with the /** and /**/ syntax, so that the template window will give useful information, while still letting you define the dll-linking information properly. The first quote-enclosed text should be the path to the dll. This can be an absolute path, such as "C:\Documents and Settings\Anthony Johnson\My Documents\dllname.dll", or it can be a relative path. If it is a relative path, then there are two options. Either you can have the relative path be relative to the Flexsim4\program directory, such as, if you put your dll in the Flexsim4\libraries directory, the path could be "..\libraries\dllname.dll". Also, if the dll is specifically used for a model (instead of for a user library), you can have the dll path be relative to the model path. For example, if the dll is in the same directory as the model, the path can simply be "dllname.dll". The second quote-enclosed text should be the name of the function that the trigger calls. In the example provided in DLL Maker, the name should be "mydllfunction1". Once you have built the dll and have toggled the right triggers as "DLL" and specified the proper information in Flexsim's triggers, you should be able to run the model, and whenever one of your DLL toggled triggers is fired, Flexsim will call the appropriate function from the dll. Configuring Visual Studio to Place the DLL in the Proper Location The location where the dll is placed is very important, as mentioned above, so you'll want to configure your Visual Studio project to place the DLL in the proper location. Here are some suggestions for configuring the Visual Studio project if you are building your dll just to be used for a specific model. Place the DLL Maker folder in the same directory as the model .fsm file that you have saved. Then open Visual Studio by double-clicking on DLL Maker\DLL Maker.vcproj. In the Solution Explorer on the left, right-click on the DLL Maker project (not Solution 'DLL Maker', but the project icon below that), and choose Properties. In the "Configuration" drop-down at the top, choose "All Configurations". In the tree on the left go to Configuration Properties > Linker > General. In the Output File field, type: ../dllname.dll (replace "dllname.dll" with whatever you want to name your dll). Then click OK. This will cause the Project to put the dll into your model directory, so that when you configure your dll path in Flexsim, you can just put it as "dllname.dll" since it's already in the same directory as your model. Rebuilding the DLL While Flexsim is Still Open Often you may want to rebuild the DLL after Flexsim has already connected to it. If you don't explicitly disconnect Flexsim from using those dlls, Visual Studio will give you build errors saying it can't write to the dll file. To disconnect, choose from Flexsim's menu: File|System|Disconnect DLLs. If you have draw code that connects to the dll, you'll need to first minimize all your ortho/perspective windows, then choose File|System|Disconnect DLLs. Moving Model Code to a dll "All-In-One" Here's a script that will go through your model and move everything into a cpp file for the dll, and toggle all nodes as dll. Just paste it into a script console and execute it. You'll need to configure the right path to the cpp file and dll file (the first two statements of the code). Note that this may cause dll compile errors if you have objects in your model with the same name. The code can be downloaded in the Flexsim-dll-project at: https://bitbucket.org/flexsim/flexsi...scriptToDLL.fs How it all works Below is an explanation of how the dll mechanism works, as copied from the documentation within flexsimfuncs.h. This information is not necessary for you to know if you just want to get a simple dll going. However, if you are contributing to the bitbucket project or if you are implementing advanced concepts in your dll, you should make sure you understand how this works. /** - flexsimfuncs.h - Flexsim function definition and binding file - ****************************** This file declares and implements all functions associated with Flexsim, including all of the standard flexsim commands. Please read this documentation carefully to understand how this file works. To get access to these functions in a cpp file of your own, just include flexsimdefs.h. Do not include this file directly. I'm making this documentation in the hope that you will use it to become much more familiar with the way the dll works, and if needed to add/customize your functionality (and so that you don't have to come back to me with every problem that you come across, as I'd like to spend more time developing for our more basic users and less time doing tech support for the hard core developers). This file is actually traversed several times during the compilation phase. It is included once by flexsimdefs.h, and twice by DllMain.cpp (three times if you count the fact that DllMain.cpp also includes flexsimdefs.h). Each time it is traversed during compilation, it does a specific task based on the defined value of DECLARATIONTYPE. When DECLARATIONTYPE is 1, this file acts like a regular header file in that functions are declared but not defined. When DECLARATIONTYPE is 2, this files acts like a cpp file, in that space is allocated in the compiled dll for function pointers, and, if function pointers are not being used, but instead regularly declared functions are being used, those functions are defined (here I make a distinction between function declarations and function definitions. Function declarations are like place holders for functions. They are placed in header files, basically saying there is a function with this name and this parameter list, but I'm not going to define it here, I'm going to define it somewhere else. Function definitions, on the other hand, define the actual implementation of the function, and are placed in cpp files). When DECLARATIONTYPE is 3, this is a binding phase, where the file actually executes code that binds function pointers to their implementations on the flexsim side. So, 1 - declaration, 2 - definition, 3 - binding. Example: Here's how the factorial() command is declared, defined, and bound within this file. First, if you are implementing your own cpp file, you will include flexsimdefs.h. This will subsequently define DECLARATIONTYPE as 1 and include flexsimfuncs.h. For DECLARATIONTYPE = 1, I define DECLARE_FLEXSIM_FUNCTION(name) as: extern _##name name; This will come into play later, but for now, just be aware of that definition. Then, further down in the file you'll see the line: typedef double (*_factorial)(double n1); What this does is it defines a new c++ type identifier for a function pointer that takes a double and returns a double. It's a bit confusing so let's back up and first talk about the typedef keyword. This keyword lets you customize your own types in c++. For example, if you don't like the word 'double' and think the word 'real' is a better name for double precision floating point numbers, then in c++ you can have a statement that says: typedef double real; This creates a new c++ type called real that is really just an alias for the double type. So in your code you can say: real myval = 5.4; Now let's talk about function pointers. A function pointer is like a place holder for a function. It can be treated like any other variable like an int or double in that its value can be set or changed at any time, it can be passed into functions, etc. These functions are declared differently than regular functions, as shown below; regular function definition: Code:
double addfunction(double p1, double p2) { return p1 + p2; } Code:
double (*fncptr)(double p1, double p2); Code:
int main() { fncptr = addfunction; double result = fncptr(3, 7); } typedef double (*_factorial)(double n1); What this does is define a type named _factorial, and that type is really just an alias for some function pointer that takes a double and returns a double. Now I define these function pointers very easily. The following definition: Code:
_factorial fncptr; Code:
double (*fncptr)(double n1); Code:
DECLARE_FLEXSIM_FUNCTION(factorial) Code:
extern _##name name; extern _factorial factorial; Now I have declared a variable of type _factorial (by our typedef this is a pointer to a function that takes a double and returns a double) and have given it the name factorial. I have also given it the extern identifier. This means that I don't want to actually allocate a memory slot for this pointer at compile time. If the extern identifier were excluded, then every time you include flexsimdefs.h (and consequently include flexsimfuncs.h), a compile-time memory slot would be reserved for the factorial function pointer, so each cpp file that is compiled in your dll would have its own copy of the factorial function pointer, and you'd get linkage errors because multiple copies of the same function pointer are defined. We want just one copy for the entire dll, so we add the extern identifier. So that's all that is done to factorial() for DECLARATIONTYPE 1. It just sets up the type, and then defines a function pointer for use, but doesn't allocate a memory slot for it. Once that's done, the cpp file you are implementing can use factorial however it wants. Now, how does the memory slot for the factorial function pointer get allocated? This is actually through the DECLARATIONTYPE = 2. Note that in this case, DECLARE_FLEXSIM_FUNCTION(name) is defined as _##name name; This is the same as in DECLARATIONTYPE 1 except the extern has been taken out. If you look in DllMain.cpp, there is a spot that says: Code:
#define DECLARATIONTYPE 2 #include "flexsimfuncs.h" Code:
DECLARE_FLEXSIM_FUNCTION(factorial) Code:
_factorial factorial; The last thing that needs to be done is actually bind the factorial function to the function that is exposed from the Flexsim side. This is done with DECLARATIONTYPE 3. Note that in DllMain.cpp, there is a function called DllMain(). This function is called as soon as the dll is loaded from Flexsim. Note that in that function, I just call bindfunctions(). In bindfunctions() I have a little setup and then the statements: Code:
#define DECLARATIONTYPE 3 #include "flexsimfuncs.h" Code:
#define DECLARE_FLEXSIM_FUNCTION(name) \ name = (_##name)GetProcAddress(themodule, #name);\ if(!name){ successful = 0; unboundfunctions += #name; unboundfunctions += "\n"; } Code:
DECLARE_FLEXSIM_FUNCTION(factorial) Code:
factorial = (_factorial)GetProcAddress(themodule, "factorial"); if(!factorial){ successful = 0; unboundfunctions += "factorial"; unboundfunctions += "\n"; } So that's how it all works. Here are the caveats: Caveats 1. Unfortunately you can't declare a function pointer with optional parameters. I don't know why c++ won't allow this (I would assume it's not that hard to implement in a compiler), but it just doesn't. With a standard function declaration you would declare optional parameters like so: declaration Code:
void initkinematics(fsnode* infonode, fsnode* object, int managerotation = 0, int relativelocs = 0); Code:
void initkinematics(fsnode* infonode, fsnode* object, int managerotation, int relativelocs) { code... } Code:
void (*initkinematics)(fsnode* infonode, fsnode* object, int managerotation = 0, int relativelocs = 0) This issue can sometimes be resolved by just making sure that all of your calls to Flexsim functions pass all of the parameters in. Unfortunately, this can be very tedious, especially if you've already implemented code in Flexsim and want to port it to a dll. 2. Overloading functions that are accessible across dll boundaries is not possible, and I understand why this is the case. The functions in the dll are bound using the GetProcAddress() function, which takes a single string as the name of the function. Thus, if multiple functions are defined with the same name (which is what function overloading is) only one of them can be "exposed" by flexsim, or else GetProcAddress() won't know which one to return. This also means that we at flexsim have necessarily decided on exactly one parameter set that we are going to expose for a given function, and that is the only one that you can have access to(unless in the future we expose alternately named functions that do the same as an overload). The parameter set that we have exposed is the one that is defined in the typedefs section below, and you cannot change that. 3. There are issues with allocating memory on one side of the dll boundary and then deallocating it on the other side. For the most part this is not a big issue, except for with strings. Whenever you pass a string type across the dll boundary, either as a return value, or as a parameter to a function, it will inherently be allocated on one side and deallocated on another. The reason that this is an issue is because the dll implements its own 'local' memory heap. The commands new, delete, malloc, realloc, free, etc. may be defined differently based on the version of visual studio that you have, so they have to have their own local heap so as not to cause memory corruption. So allocating a string on one heap and deallocating it on another can cause errors. With the 4.3 release, we have disabled any "visible" functions from the engine side that either return strings or take them as parameters, and replaced most of them with comparable functions that take char*'s instead, or with methods of getting around it. This will unfortunately break compatibility with all dlls that were built in previous versions, so you will need to rebuild your dll with this updated header file in order to be compatible with version 4.3. This is a one-time thing, so in the future you will not need to re-compile your dll's to be forward compatible with later versions. Also, because the visible functions no longer take strings, there are some issues with calling those functions and passing strings into them. The 4.3 update of this file should handle most of those issues, but if you still have some functions that aren't overloaded properly, you can implement your own overload mechanism using one of the methods mentioned below. The above issues can often be resolved with one of the following solutions: 1. You completely define the function on the dll side so that you don't even have to bind anything. This method can often be done for simple string operations such as stringtonum, numtostring, etc. and is already used to define concat() 2. You work through other flexsim functions to get the result that you need This method is already used to define the following functions (and perhaps some more): Code:
double getlabelnum(fsnode* object, int labelrank); string getlabelstr(fsnode* object, int labelrank); double setlabelnum(fsnode* object, int labelrank, double val); double setlabelstr(fsnode* object, int labelrank, char * val); double gettablenum(fsnode* thetable, int row, int col); fsnode* gettablecell(fsnode* thetable, int row, int col); string gettablestr(fsnode* thetable, int row, int col); double settablenum(fsnode* thetable, int row, int col, double val); double settablestr(fsnode* thetable, int row, int col, char* val); double time(); declaration: Code:
double getlabelnum(fsnode* object, string labelname); Code:
double getlabelnum(fsnode* object, string labelname){string path = concat("/",labelname); return getnodenum(node((char*)(path.c_str()), labels(object)));} For both of these issue resolutions, you need to make your declaration in the DECLARATIONTYPE == 1 area, and you make your definition in the DECLARATIONTYPE == 2 area. I believe these solutions should allow you to implement most of the functionality you need, although I know it won't provide all of it, especially with some of the string issues. For this remaining stuff that you just can't implement on the dll side, you'll just have to wait for another Flexsim release, when we will expose more functionality and expose it properly. Also, to help you in your work in getting around the issues, in my initial implementation for the CT library I found that there were several lower-level class methods I needed that were not part of the command list, so I created and exposed some wrapper functions that encapsulate the class method call, since the classes are not defined in the dll. So these are accessible, and automatically bound in the dll. These are not command list functions, but were specially made for the dll. They expose methods on the linkedlist (or fsnode) class, and on the byteblock class. You will probably not need the linkedlist functions, but the byteblock methods are very useful because they let you work with strings while being able to skirt around the issues of passing the string class across. The linkedlist (or fsnode) functions are as follows: fsnode * llstep(fsnode* thenode, int stepnum); - This exposes the step() method of linkedlist. Not essential because you can use rank() instead int llsize(fsnode* thenode); - This exposes the size() method of linkedlist. Not essential because you can use content() instead. fsnode * llbranch(fsnode* thenode); - This exposes the branch() method of linkedlist. You don't need to use this unless you're using the other two methods. The byteblock functions are as follows. The byteblock class allows you to directly store and retrieve the data on a node that has been given string data. char * bbgetbuffer(fsnode* thenode); - This returns a direct pointer to the memory location that stores the node's string data int bbgetsize(fsnode* thenode); - This returns the size of the allocated space for the node's string data, which should be the size of the string + 1 (for the null terminating character) int bbsetsize(fsnode* thenode, int size); - This sets the size of the allocated memory block. Be aware that after you call bbsetsize, you will need to get the buffer again with bbgetbuffer since it will have changed location. You can use the byteblock functions, for example, to re-implement the setnodestr() function, as follows: Code:
int setnodestr(linkedlist * attnode, string s){ bbsetsize(attnode, s.length() + 1); char* deststr = bbgetbuffer(attnode); strcpy(deststr, s.c_str()); } Again, I give you this documentation in the hope that you will use it to become much more familiar with the way the dll works, and if needed to add/customize your functionality. ************************************************** *************************/ Last edited by Anthony Johnson; 01-10-2014 at 01:11 PM. Reason: Updated links |
Tags |
dll, dll maker |
Thread | Thread Starter | Forum | Replies | Last Post |
Creating fsm-Files | Ralfi | Q&A | 4 | 10-16-2007 06:07 PM |
creating a batch trip task sequence | Paul Dowling | Q&A | 5 | 09-11-2007 06:55 PM |