[latexpage]
Prerequisites
To be able to follow this report, you will need to have the PSIM and Visual Studio software installed on your computer. Additionally, you should have prior knowledge of simulation using the PSIM software and the C/C++ programming language.
PowerSIM DLL Block
PSIM’s general DLL block provides more flexibility and capability in interfacing PSIM with custom DLL files. The general DLL block allows users to write code in C or C++, compile it as a Windows DLL, and link it to PSIM. The prototypes for the exported simulation functions from the DLL file for use with PSIM are:
extern "C" __declspec(dllexport) void SimulationBegin(const char* szId, int nInputCount,
int nOutputCount, int nParameterCount, const char** pszParameters, int* pnError,
char* szErrorMsg, void** reserved_UserData, int reserved_ThreadIndex, void* reserved_AppPtr);
extern "C" __declspec(dllexport) void SimulationStep(double t, double delt, double* in,
double* out, int* pnError, char* szErrorMsg, void** reserved_UserData,
int reserved_ThreadIndex, void* reserved_AppPtr);
extern "C" __declspec(dllexport) void SimulationEnd(const char* szId,
void** reserved_UserData, int reserved_ThreadIndex, void* reserved_AppPtr);
The general DLL block can be accessed in PSIM via View → Library Browser and searching for the term General DLL Block. A more precise description of each function follows:
SimulationBegin: This function is called once at the beginning of the simulation. It is used to initialize the user data structure and record the parameters and input/output nodes. Here are the parameters:
const char* szId
(input): The name of the DLL block.int nInputCount
(input): The number of input nodes.int nOutputCount
(input): The number of output nodes.int nParameterCount
(input): The number of parameters.const char** pszParameters
(input): A pointer to the parameters.int* pnError
(output): A pointer to an error value.char* szErrorMsg
(output): A string where you can set an error message.void** reserved_UserData
(output): A pointer to a pointer to the user data.int reserved_ThreadIndex
(input): The index of the current thread.void* reserved_AppPtr
(input): A pointer to the application.
SimulationStep: This function is called at every time step during the simulation. It is used to calculate the output values based on the input values and the current parameters. Here are the parameters:
double t
(input): The current time in the simulation.double delt
(input): The time step in the simulation.double* in
(input): A pointer to an array of input values.double* out
(output): A pointer to an array of output values.int* pnError
(output): A pointer to an error value.char* szErrorMsg
(output): A string where you can set an error message.void** reserved_UserData
(input/output): A pointer to a pointer to the user data.int reserved_ThreadIndex
(input): The index of the current thread.void* reserved_AppPtr
(input): A pointer to the application.
SimulationEnd: This function is called once at the end of the simulation. It is used to free any memory allocated during the simulation and to save the simulation results, if necessary. Here are the parameters:
const char* szId
(input): The name of the DLL block.void** reserved_UserData
(input/output): A pointer to a pointer to the user data.int reserved_ThreadIndex
(input): The index of the current thread.void* reserved_AppPtr
(input): A pointer to the application.
Creating the DLL File in Visual Studio
To create the DLL file, Microsoft Visual Studio 2022 Community is used. To download the software, visit this link. To read the license terms for individual users and organizations, visit this page.
During the installation of Visual Studio, check the box for “Desktop development with C++”. This will install the C++ compiler, the integrated development environment (IDE), and other necessary tools for DLL development in C++.
To create a DLL project that you can use with the PSIM software, follow these steps after opening Visual Studio 2022 on your computer:
- Create a new project: From the “File” menu, select “New” and then “Project…”. This will open the “Create a new project” window.
- Select the project type: In the “Create a new project” window, search for “DLL” using the search box. Select “Dynamic-Link Library (DLL)” from the list of project types and click “Next”.
- Configure the project: In the next window, enter the name of your project, choose a location to save the project, and set other options as needed. Click “Create” to create the project.
- Add your DLL code: In the Solution Explorer, right-click on your project’s name and select “Add” → “New Item…”. Select “C++ File (.cpp)”, give the file a name, and click “Add”. Now you can add your DLL code to this file. * For how to set up your first project, please read the section Setting Up Your First Project in Visual Studio.
- Compile the DLL: From the “Build” menu, select “Build Solution” to compile the DLL. If there are no errors, the DLL will be created in the project’s output directory.
- Use the DLL in PSIM: To use the DLL in PSIM, you need to load the DLL into PSIM. This can usually be done from the PSIM’s user interface.
Setting Up Your First Project in Visual Studio
When you create a new DLL project in Visual Studio, it automatically generates some code and header files for you, as shown in the figure below.

Here’s what each of those files does:
- framework.h: This is a header file that typically contains include statements for framework header files that are frequently used but rarely changed. This is used for precompiled header (PCH) compilation, which can speed up compilation.
- pch.h: This is the precompiled header file. It is used to hold header inclusions (like
#include <windows.h>
) that are needed for most source (.cpp
) files in your project. - dllmain.cpp: This is the entry point for the DLL. It contains the
DllMain
function that is called when the DLL is loaded and unloaded. TheDllMain
function is an optional function that you can use to initialize and clean up resources. - pch.cpp: This code file is where the precompiled header compilation is initiated. It includes the
pch.h
file.
If you don’t need precompiled header compilation and don’t need a DllMain
function, you can delete these files. However, if you’re not sure, it might be better to leave them in place. They won’t harm your project and can be useful as your project grows.
If you choose to remove these files, you need to disable precompiled header (PCH) compilation as follows:
- Open Project Properties: Right-click on your project in the Solution Explorer and select “Properties”.
- Go to C/C++ Configuration: In the left panel of the Properties window, expand the “C/C++” section.
- Disable Precompiled Headers: Select “Precompiled Headers” from the C/C++ options list. In the right panel, set the “Precompiled Header” option to “Not Using Precompiled Headers”. Click “OK” to save changes, as shown in the figure below.

To add your own code to the DLL, you can add new .cpp
and .h
files to the project, or you can add your code to the existing .cpp
files. Be sure to declare any function or variable that you want to export from the DLL using extern "C" __declspec(dllexport)
. Tip: It is also possible to use a .def
file to control which functions the DLL should export.
Compiling and Debugging the Project in Visual Studio
Follow these steps to build the project:
- Select Release Mode: At the top of the Visual Studio window, there is a toolbar with a dropdown menu that allows you to select the build configuration. By default, it is often set to “Debug”. Click on this dropdown menu and select “Release”.
- Build the Project: After selecting “Release”, go to the “Build” menu at the top of the screen. Click on “Build Solution” or “Build [Your Project Name]”. This will compile your project in Release mode.
- Check the Output: After the build process is complete, you can check the “Output” window at the bottom of the screen to see if the build was successful. It’s shown in Figure 3.
Once the build is complete, you should find the .dll
at: ProjectName / x64 / Release / ProjectName.dll. Then, create the project in PSIM, add the “General DLL Block”, and load the .dll
from the indicated path, as shown in Figure 4.


Follow these steps to build and debug the project:
- Set Debug Mode: At the top of the Visual Studio window, there is a toolbar with a dropdown menu that allows you to select the build configuration. Click on this dropdown menu and select “Debug”.
- Set PSIM as the Debugging Application: Go to the “Project” menu and select “[Your Project Name] Properties”. In the Properties window, go to the “Debugging” section. In the “Command” field, enter the path to the PSIM executable. This tells Visual Studio to start PSIM when you start debugging, as shown in Figure below.
- Start Debugging: Go to the “Debug” menu and select “Start Debugging”, or press F5. This will start PSIM.
- Run the PSIM Simulation: In PSIM, run the simulation that uses your DLL. Execution should pause in Visual Studio when it hits a breakpoint. You should find the
.dll
at: - Step Through Your Code: In Visual Studio, you can now step through your code one line at a time, inspect variables, and perform other debugging tasks. Use the “Step Over”, “Step Into”, and “Step Out” commands in the “Debug” menu to control execution.

Creating the DLL File in VS Code
The VS Code (Visual Studio Code) is a free and open-source code editor developed by Microsoft. It’s highly customizable and supports a wide range of programming languages. When you create a project in VS Code, a .vscode folder may be created in your project’s root directory. This folder contains configuration files to customize the behavior for the project. Here’s what each file typically does:
- settings.json: This file contains local settings for your project, overriding global settings on a per-project basis. This can include editor preferences, linting and formatting rules, and more.
- launch.json: This file is used when you’re debugging your code. It tells VS Code how to launch your application and attach the debugger. This can include the command to start your program, arguments to pass to the program, the location of source files, and more.
- tasks.json: This file is used to automate common tasks. You can define tasks for building your application, running tests, or any other command-line task you want to automate.
- c_cpp_properties.json: This file is used by the C/C++ extension and contains configuration settings related to C/C++ development, such as include paths and compiler paths.
To create a DLL project that you can use with the PSIM software, follow these steps:
- Install Visual Studio Code, if you haven’t already. You can download it from the official website:
https://code.visualstudio.com/ - Install the MinGW compiler. It’s necessary for compiling C/C++ code on Windows. You can download it here:
https://www.mingw-w64.org/downloads - Install the C/C++ extension for Visual Studio Code.
- Open Visual Studio Code.
- Click on “View” → “Extensions”.
- Search for
"C++"
and install the extension named “C/C++” from Microsoft.
- Open your project folder.
- Click on “File” → “Open Folder” to open your project folder.
- This is where you’ll store all your .cpp and .h files.
- Create a new source file.
- Click on “File” → “New File” to create a new file.
- Save it with the
.cpp
extension (for example, “simulation.cpp”). - Insert your DLL code inside this file.

Compiling and Debugging the Project in VS Code
Follow the steps to build the project in VS Code:
- Select your project file. In this example, the file is “simulation.cpp”.
- Open the “Command Palette”.
- Press Ctrl+Shift+P.
- Type “Tasks: Configure Task” and press Enter.
- This will create a file named “tasks.json”, which you can use to automate the compilation and creation of your DLL.
- You can also see this file in Figure below.
- Run the build task.
- Click on “Terminal” → “Run Build Task”.
- Or press Ctrl+Shift+B to start the build process.
This is a basic example of how to compile a DLL using MinGW and Visual Studio Code. Depending on the specific code you’re writing and the compiler you’re using, you may need to adjust the settings and compile commands.
To assist, below is an example of a tasks.json configuration. This configuration executes commands to create a folder called “output” within the project workspace, where the DLL file named “WorkspaceName.dll” will be saved.
{
"version": "2.0.0",
"tasks": [
{
"label": "create output dir",
"type": "shell",
"command": "mkdir",
"args": [
"-p",
"\${workspaceFolder}\\output"
],
"windows": {
"command": "cmd",
"args": [
"/C",
"if not exist \"\${workspaceFolder}\\output\" mkdir \"\${workspaceFolder}\\output\""
]
},
"group": {
"kind": "build",
"isDefault": false
}
},
{
"label": "build dll",
"type": "shell",
"command": "g++",
"args": [
"-g",
"-shared",
"-o",
"\${workspaceFolder}\\output\\\${workspaceFolderBasename}.dll",
"\${fileBasenameNoExtension}.cpp"
],
"dependsOn": "create output dir",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always"
},
"problemMatcher": []
}
]
}
Debugging a DLL within an external software environment like PSIM via Visual Studio Code and GDB is complex due to the intricate coordination required between the debugger, DLL, and host software. Various factors, including the build environment, runtime parameters, and system configurations, can further complicate this process.
When a DLL is loaded into a process, it is given a block of memory addresses. Breakpoints are set at specific addresses within this block. However, if the DLL is unloaded and loaded again, it may not receive the same memory addresses, making the old breakpoints invalid. This issue occurs every time PSIM restarts the simulation.
Visual Studio provides more advanced debugging tools that handle this issue automatically, whereas in VS Code, manual configuration is required.
Due to these reasons, the launch.json file used for debugging has some limitations:
- You must set the breakpoints before starting the debugging process.
- After debugging in PSIM, the simulation cannot be restarted, as the breakpoint references will be lost.
- You must close PSIM and end the debugging process, then start the process again.
Follow the steps to debug the Project in VS Code:
- Select your project file.
- In this example, the file is “simulation.cpp”.
- Add a Debug Configuration.
- Click on “Run” → “Add Configuration…” in the menu.
- This will create a file named “launch.json”.
- Modify the “launch.json” file according to your debugging requirements.
- Below is an example configuration for debugging a DLL using GDB from MinGW.
- Change the
"program"
path to point to PSIM or the program that will load the DLL.- Ensure that
"miDebuggerPath"
is correctly set to the GDB from MinGW.
- Ensure that
- Start debugging.
- Click on “Run” → “Start Debugging”. Or press F5 to initiate the debugging process.
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug PSIM DLL",
"type": "cppdbg",
"request": "launch",
"program": "C:\\Altair\\PSIM_2022.2\\PSIM.exe",
"args": [],
"stopAtEntry": false,
"cwd": "\${workspaceFolder}",
"environment": [],
"externalConsole": false,
"preLaunchTask": "build dll",
"MIMode": "gdb",
"miDebuggerPath": "C:\\msys64\\mingw64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
Example – RMS Calculation
As an example, the process for compiling and debugging a code for RMS value calculation using Visual Studio is presented.
- Create the initial project in Visual Studio
- Allocate the functions “SimulationBegin()”, “SimulationEnd()”, and “SimulationStep(*)” in the
"simulation.cpp"
file. - Refer to Section 1 for more details.
- Allocate the functions “SimulationBegin()”, “SimulationEnd()”, and “SimulationStep(*)” in the
- Create the
"rms.h"
file- Add the following code:
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
double inputSignal;
double syncSignal;
} PSIM_Input;
typedef struct {
double rms;
} PSIM_Output;
typedef struct {
double sumSquared;
int sampleCount;
bool hasCrossedZero;
double prevSyncSignal;
} RMS_Calculator_State;
void rms_calculator(PSIM_Input* input, PSIM_Output* output,
RMS_Calculator_State* state);
#ifdef __cplusplus
}
#endif
- Create the “rms.c” file and add the following code:
#include <math.h>
#include "rms.h"
void rms_calculator(PSIM_Input* input, PSIM_Output* output, RMS_Calculator_State* state) {
double currentSyncSignal = input->syncSignal;
if (state->prevSyncSignal <= 0 && currentSyncSignal > 0) {
if (state->hasCrossedZero) {
output->rms = sqrt(state->sumSquared / state->sampleCount);
state->sumSquared = 0;
state->sampleCount = 0;
}
else {
state->hasCrossedZero = true;
}
}
if (state->hasCrossedZero) {
state->sumSquared += pow(input->inputSignal, 2);
state->sampleCount++;
}
state->prevSyncSignal = currentSyncSignal;
}
- In “simulation.cpp”, add the code within the “SimulationStep(*)” function and the header #include “rms.h”:
// Declarar Variáveis
static PSIM_Input input2;
static PSIM_Output output2;
static RMS_Calculator_State state2;
// Preencher os valores de input
input2.inputSignal = in[0];
input2.syncSignal = in[1];
// Chamar a função rms_calculator
rms_calculator(&input2, &output2, &state2);
// Preencher os valores de output
out[0] = output2.rms;
Your project should look similar to the one in Figure below:

The input parameter of the function “rms_calculator(*)” is defined by the struct “PSIM_Input”. Two values are required. The first one, “inputSignal”, is the signal itself on which the calculation will be executed and the second, “syncSignal”, is a synchronization signal used to identify the zero crossing and calculate the RMS value within one cycle of the sine wave. In other words, unlike the RMS function of PSIM, the code allows calculating the RMS value of signals with any frequency depending on the synchronization signal giving the trigger to start and finish the calculation.
We can see the PSIM simulation with General DLL BLock shown in Figure below:

As the reader may have noticed, within “SimulationStep(*)” we declare the variables of the “rms_calculator(*)” function as static. There is a problem with doing this. Every new simulation without closing and reopening PSIM again, the value of the static variables is not zeroed, which can lead to initial value problems in your code. One of the solutions would be to zero the variables at the start of the simulation within “SimulationBegin(*)”.
Another problem can occur when using the same DLL block more than once in the same simulation, the DLL instances will be sharing the same static variables, and the result can be seen in Figure below.

Let’s mention two possible solutions in the case of this example. The first one is to simply duplicate the “CalculoRMS.dll” file, ending up with “CalculoRMS.dll” and “CalculoRMS2.dll”. For each “General DLL Block” in PSIM, a different “.dll” file is associated. The second solution is more elegant and recommended in the PSIM software manual, which consists of allocating new memory space at the beginning of the simulation and releasing this memory space at the end of the simulation. This procedure can be performed by changing the “simulation.cpp” code to:
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "rms.h"
// PLACE GLOBAL VARIABLES OR USER FUNCTIONS HERE...
struct Internal_DLL_Block_SimulationData
{
PSIM_Input input;
PSIM_Output output;
RMS_Calculator_State state;
};
/////////////////////////////////////////////////////////////////////
// FUNCTION: SimulationStep
// This function runs at every time step.
// DO NOT CHANGE THE NAME OR PARAMETERS OF THIS FUNCTION
/////////////////////////////////////////////////////////////////////
extern "C" __declspec(dllexport) void SimulationStep(double t, double delt, double* in, double* out,
int* pnError, char* szErrorMsg,
void** reserved_UserData, int reserved_ThreadIndex, void* reserved_AppPtr)
{
Internal_DLL_Block_SimulationData* pData
= (Internal_DLL_Block_SimulationData*)(*reserved_UserData);
if (pData == NULL)
{
return;
}
//// Declarar Variáveis
//static PSIM_Input input2;
//static PSIM_Output output2;
//static RMS_Calculator_State state2;
//// Preencher os valores de input
//input2.inputSignal = in[0];
//input2.syncSignal = in[1];
//// Chamar a função rms_calculator
//rms_calculator(&input2, &output2, &state2);
//// Preencher os valores de output
//out[0] = output2.rms;
// Preencher os valores de input
pData->input.inputSignal = in[0];
pData->input.syncSignal = in[1];
// Chamar a função rms_calculator
rms_calculator(&pData->input, &pData->output, &pData->state);
// Preencher os valores de output
out[0] = pData->output.rms;
*pnError = 0; //Success
}
/////////////////////////////////////////////////////////////////////
// FUNCTION: SimulationBegin
// Initialization function. This function runs once at the beginning of simulation.
// DO NOT CHANGE THE NAME OR PARAMETERS OF THIS FUNCTION
/////////////////////////////////////////////////////////////////////
extern "C" __declspec(dllexport) void SimulationBegin(const char* szId, int nInputCount,
int nOutputCount, int nParameterCount, const char** pszParameters, int* pnError, char* szErrorMsg,
void** reserved_UserData, int reserved_ThreadIndex, void* reserved_AppPtr)
{
// ENTER INITIALIZATION CODE HERE...
*reserved_UserData = new Internal_DLL_Block_SimulationData;
Internal_DLL_Block_SimulationData* pData
= (Internal_DLL_Block_SimulationData*)(*reserved_UserData);
memset(pData, 0, sizeof(Internal_DLL_Block_SimulationData));
*pnError = 0; //Success
}
/////////////////////////////////////////////////////////////////////
// FUNCTION: SimulationEnd
// Termination function. This function runs once at the end of simulation.
// DO NOT CHANGE THE NAME OR PARAMETERS OF THIS FUNCTION
/////////////////////////////////////////////////////////////////////
extern "C" __declspec(dllexport) void SimulationEnd(const char* szId, void** reserved_UserData,
int reserved_ThreadIndex, void* reserved_AppPtr)
{
// ENTER END CODE HERE...
Internal_DLL_Block_SimulationData* pData
= (Internal_DLL_Block_SimulationData*)(*reserved_UserData);
if (pData == NULL)
{
return;
}
delete pData;
*reserved_UserData = NULL;
}
Note that the parameter “void** reserved_UserData (output):” from the PSIM functions was used, which is intended to pass user data by pointer. The result can be seen in the simulation of Figure below:

📥 Download the Project Files
Below, you can download all the necessary files for this project, including the source code, configuration files, and additional resources. These files will help you replicate the implementation and follow the tutorial with ease.
Click the links below to download: