The purpose of this tutorial is to help you become comfortable with writing and compiling a DLL that can interface with a GoldSim model. It begins with setup of the programming environment, followed by building a simple standalone program that can be run from a command line. Knowing how to do this is an introductory step but can be skipped if you are already familiar with compiling C programs. To dive right into creating a DLL, please skip to the section called "Linking an Executable to a DLL". By the end of this tutorial, you will have a working GoldSim model that interacts dynamically with an external program.
The tutorial is divided up into the following sections:
- Installation and Configuration
- Create a Visual Studio Project
- Write a Standalone Program
- Create a Dynamic Link Library (DLL)
- Link a Program to a DLL
- Send Data To/From DLL
- Link a GoldSim Model to a DLL
This tutorial assumes you are using MS Visual Studio Community 2019. Other versions of Visual Studio should work as well but the configuration might be slightly different. This tutorial assumes you have some experience with GoldSim. It is extremely helpful to have some familiarity with C/C++ but not necessary to complete the tutorial. Either before or after this tutorial, we recommend that you spend some time with introductory documentation if you plan to build DLLs that interact with GoldSim. Here are some good places to start:
Installation and Configuration
At a bare minimum, you need to have a C++ compiler and text editor to complete this tutorial. We will use a free version of Visual Studio for this tutorial but you are free to use any of your choosing. If you already have an editor and compiler, skip to the section "Write the Program".
- Download and install the latest version of Visual Studio (Community is the free version, which will work for this example)
- Make sure to select Visual Studio workload that enables "Desktop development with C++".
- Go here for more information about system requirements and the installation process.
Create a Visual Studio Project
Now that you have Visual Studio Community installed, we are ready to create a project and write our first program. Follow these steps to create a simple program that prints "Hello, World!" to the console. The purpose of this task is to make sure that your environment is configured correctly and this project will be used for the remainder of the tutorial.
- Start Visual Studio and create a new project following these instructions. Choose a Windows Console Application using C++.
- Configure the project with a name and project location. For this tutorial, I named the project "TutorialGoldSimDLL".
- The project will open and you should see the main source file ("TutorialGoldSimDLL.cpp" in my case) with the program ready to print "Hello World!" to the console.
- Click the button "Local Windows Debugger"
- You should see that the program compiles then executes, showing the result in the console:
- We are now ready to write a standalone program that will take user input and print output to the Windows console.
Write a Standalone Program
Before we dive into creating a DLL, we should write, build, and run a standalone program to make sure it is working. In this section, we will write a program that asks for 2 user inputs as numbers then prints back to the console the sum of the 2 numbers provided. This program is very similar to what our final GoldSim + DLL model will do so it is important that you follow along with the progression of this tutorial if you are new to building DLLs.
The first thing we should do is write a function that takes 2 numbers as input and returns the sum. This way, our main program will only need to worry about handling the user interface and simply call our function when it's needed. Let's write the function as follows, just above the main() function:
double add_numbers(double num1, double num2)
{
return num1 + num2;
}
This function takes 2 values (doubles) as input and returns 1 double, which is the sum of the 2 inputs. The other part of this program needs to do the work of the user interface, similar to what we will eventually use GoldSim for. Below is a simple function that gets a number from the user, makes sure it is valid then returns the number. We will call this function in the main part of the program. Add this code just above the main() function.
using namespace std;
double get_user_number()
{
double user_input;
cout << "Enter a number: \n";
cin >> user_input;
int num_tries = 0;
while (cin.fail())
{
// User didn't enter a numberic value
cin.clear(); // reset failbit
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // Skip bad input
cout << "Please enter a numeric value. Try again!\n";
cin >> user_input;
}
return double(user_input);
}
This program will ask for a number and if you type a number, the function will return the value as a double. We will use this to assign the values to variables that are used in our add_numbers function written earlier. If a non-numeric value is entered, you will be asked to enter a number again and this will be repeated until it finds a number. This is what the while loop is for.
Let's finish writing the program by calling these functions in the main() function:
void main()
{
cout << "This is a program that adds 2 numbers!\n";
double firstNumber = get_user_number();
double secondNumber = get_user_number();
cout << "The answer is: \n";
cout << add_numbers(firstNumber, secondNumber);
}
Since we have most of the work being performed in our first 2 functions, the main function is fairly simple. We just get the numbers from the user and assign them to variables then use those in the add_function.
#include <iostream>
using namespace std;
double add_numbers(double num1, double num2)
{
return num1 + num2;
}
double get_user_number()
{
double user_input;
cout << "Enter a number: \n";
cin >> user_input;
int num_tries = 0;
while (cin.fail())
{
// User didn't enter a numberic value
cin.clear(); // reset failbit
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // Skip bad input
cout << "Please enter a numeric value. Try again!\n";
cin >> user_input;
}
return double(user_input);
}
void main()
{
cout << "This is a program that adds 2 numbers!\n";
double firstNumber = get_user_number();
double secondNumber = get_user_number();
cout << "The answer is: \n";
cout << add_numbers(firstNumber, secondNumber);
}
Because we set up a function outside of the main() function to do the work, we are set up to build the external DLL, which will also hold the same add_numbers function. Before we dive into the DLL, you should look over the entire program (below) and make sure it compiles and runs okay.
You can run the program from within Visual Studio by clicking on the Local Windows Debugger button or by running the TutorialGoldSimDLL.exe program from the command line.
Running this program looks like this:
Create a Dynamic Link Library (DLL)
Now that you have been introduced to writing and compiling a standalone console program in Visual Studio, it is time to extend our program to rely on an external DLL for the add_numbers functionality. Let’s create a DLL that will link to the user interface portion of our program. The purpose of this DLL is to provide a library containing 1 or more functions that can be called from the main program.
Before continuing, it is recommended that you rename the original project in order to differentiate it from the DLL we are about to create. In my case, I renamed the original project ConsoleInterface and console_interface.cpp since this project is meant to be the user interface.
You should re-build the project to make sure everything is still working after this change.
- Let’s start by creating a new project that will reside in the same solution we created before. Right-click the 'Solution 'TutorialGoldSimDLL' and Select Add -> New Project... (alternatively, you could go to File -> Add -> New Project...).
- This will open the New Project Wizard. Find and click on the "Empty Project" template, then click Next.
- Give it a name like "MyLibraryDLL" then click Create.
- Now, configure the project to compile as a DLL. In the solutions explorer, right click on the new project (in the browser tree) and click on Properties.
- Go to the "General" section of Configuration Properties and change the Configuration Type to Dynamic Library (.dll). Click OK.
- Right-click the Header Files folder and select Add -> New Item....
- Select the option Header File (.h) and name your file add_numbers.h and click Add.
- Edit the new header file (add_numbers.h) to add the function declaration:
-
__declspec(dllexport) double add_numbers(double, double);
- The statement "__declspec(dllexport)" allows the compiler to generate the export name and place it in a .LIB file. This .LIB file will be used just like a static .LIB to link with a DLL.
-
- Right-click the Source Files folder under the heading of your new project and select Add -> New Item.... Select the option 'C++ File (.cpp)' and name your file the same as your header file add_numbers.cpp.
- You should now see the following files in the DLL project directory.
- Add the following code to the add_numbers.cpp file:
-
#include "add_numbers.h"
double add_numbers(double num1, double num2)
{
return num1 + num2;
}
-
- We are now ready to build our DLL. Right-click the project name in the browser tree and select the option to ‘Build’.
- It should build successfully.
- The resulting DLL should now be located in the same 'Debug' folder as the .exe
- We cannot run the DLL as a standalone program. It needs to be called from a main executable program. Let's configure the ConsoleInterface project so that it links to the DLL and uses it to execute the DLL's functionality.
Link the Program to a DLL
Now we have a main executable program (which acts like a GoldSim model) that we will want to link to the new DLL. We need to do this by calling the function in the library and also by creating a link between the two. In this example, we'll link our executable (MyFirstProject) to the library we just created.
- Remove the add_numbers function from the main program. Or comment it out.
- Include the new add_numbers.h file at the top of the console_interface.cpp file.
-
#include "add_numbers.h"
-
- The resulting program now looks like this:
-
#include <iostream>
#include "add_numbers.h"
using namespace std;
double get_user_number()
{
cout << "Enter a number: \n";
double user_input;
cin >> user_input;
int num_tries = 0;
while (cin.fail())
{
// User didn't enter a numberic value
cin.clear(); // reset failbit
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // Skip bad input
cout << "Please enter a numeric value. Try again!\n";
cin >> user_input;
}
return double(user_input);
}
void main()
{
cout << "This is a program that adds 2 numbers!\n";
double firstNumber = get_user_number();
double secondNumber = get_user_number();
cout << "The answer is: \n";
cout << add_numbers(firstNumber, secondNumber);
}
-
- Right-click the ConsoleInterface Project directory and select Properties
- Go into Configuration Properties -> C/C++ -> General, click on the drop-list arrow at the right side of the 'Additional Include Directories' field, select <Edit...>, click the folder icon and browse to the \MyLibraryDLL folder where the header file is located ("add_numbers.h"). On my machine, the full path is ("C:\Users\JasonLillywhite\source\repos\TutorialGoldSimDLL\MyLibraryDLL"). That will make it so the compiler can find the appropriate header file. Alternatively, you can just use a relative path (..\MyLibrary).
- Now go into Configuration Properties -> Linker -> General, click on the drop-list arrow at the right side of the Additional Library Directories field, select <Edit...>, click the folder icon and browse to the main 'Debug' folder where your .exe and your .dll built to. On my machine, the path is "C:\Users\JasonLillywhite\source\repos\TutorialGoldSimDLL\Debug". Alternatively, you can just use a relative path (..\Debug). After you've selected the appropriate folder, press OK.
- Now we need to provide the name of the .lib file so it can be found when building the solution. This tells Visual C++ where to find the header file (add_numbers.h). Go into Configuration Properties -> Linker -> Input, click the drop-list arrow at the right side of the Additional Dependencies field, click <Edit...> and simply type the name of the library file: MyLibraryDLL.lib (assuming you named it this way) in the input field and press OK.
- Ensure that Visual C++ always builds the library before it then builds the executable (since the executable depends on the .dll). Right-click on the Solution name TutorialGoldSimDLL and select Properties.
- Select Common Properties --> Project Dependencies. From the drop-list labeled "Projects:", select ConsoleInterface (or whatever your executable project name is). In the "Depends on:" field, check the box next to MyLibraryDLL (or whatever the name of your library project is). Press OK.
- Re-build the solution. Right-click on the solution name in the browser tree and select 'Rebuild Solution'.
- It should build successfully and display the following at the end of the build log: "Rebuild All: 2 succeeded, 0 failed, 0 skipped".
- Double-click on the ConsoleInterface.exe program in the Debug directory. You should see the console program appear:
Now that we have a working program that depends on a DLL for a function, we are now ready to extend our DLL to interact with a GoldSim model. The GoldSim model will become the new interface that will replace our ConsoleInterface.exe program.
Link a GoldSim Model to a DLL
In this next part of the tutorial, we will add some functionality to the DLL that GoldSim requires. GoldSim will provide the 2 numbers to the DLL and the DLL will return the sum back to GoldSim just like our console program does. In addition, GoldSim also requires the following information:
- Initialization
- Reporting the version number
- Reporting the number of inputs/outputs
- Reporting any error messages.
- You can read more about these functions in Appendix X of this document.
Follow these steps to add the GoldSim functionality needed for the DLL interface:
- Open the add_numbers.h file in Visual Studio and add the following expression:
-
extern "C" __declspec(dllexport) void goldsim_add_numbers(int, int*, double*, double*);
-
- Open the add_numbers.cpp file and add the following code just below the existing add_numbers function:
-
extern "C" void goldsim_add_numbers(int methodID, int* status, double* inargs, double* outargs)
{
*status = 0;
switch (methodID)
{
case 0: // Initialize (called at beginning of each realization)
break;
case 1: // Perform function calls
outargs[0] = add_numbers(inargs[0], inargs[1]);
break;
case 2: // Version number of the DLL
outargs[0] = 1.03;
break;
case 3: // Number of input and output arguments
outargs[0] = 2.0; // Number of inputs
outargs[1] = 1.0; // Number of outputs
break;
case 99: // Optionally release any memory that's been allocated
break;
default: // Error if this point is reached
*status = 1;
break;
}
}
-
Every external function in GoldSim must have the 5 cases (0, 1, 2, 3, and 99). Case 1 is where you call the add_numbers function (shown in red above).
- Right click on the "GoldSim DLL" project and click on "Build". You should see that the project builds without any errors. The DLL is now ready for use and can be found in the Debug folder of your project directory.
- Create a new GoldSim model and save it in that same directory as the DLL.
- In the GoldSim model, add a new External element and refer to the name of the DLL.
- Add the name of our function "GoldSimDLLFunctions" into the Function Name field.
- Add 2 new Data elements to the model (A and B) then add them to the input interface of the External element.
- Add a single output definition to the output side of the interface and give it the name "Sum". This is where the DLL output will be returned to.
- Now, run the GoldSim model and view the results of the External element. You should see the value of "Sum" is what is expected when the values A and B are added.
- It's recommended that you run the ConsoleInterface.exe to check that the outcome is the same.
This concludes the tutorial. At this point, you can continue adding functions to the DLL and use them in GoldSim through the External element's interface. If you have any questions, feel free to comment on this article or ask on our Community Forum.
Comments
0 comments
Please sign in to leave a comment.