Integrating external programs#
This tutorial shows how to integrate a third-party command-line tool into a SAMSON extension. You will build an app that exports selected-atom data, launches an external executable, reads the computed result back, and displays it inside SAMSON.
Use this pattern when your extension depends on software that already exists outside the SDK, such as a simulation engine, a numerical solver, or a legacy scientific tool.
What you will build#
By the end of the tutorial, you will have:
- a SAMSON app that gathers data from the current selection;
- a small external executable that reads input and writes output;
- a file-based exchange contract between SAMSON and that executable;
- a Qt
QProcesslaunch path that runs the external program from your extension.
Prerequisites#
- You know how to generate an app extension.
- You can build, install, and run the extension on your platform.
- You are comfortable compiling a small helper executable alongside your extension.
Sample code#
The source code for an Extension created in this tutorial can be found at SAMSON-Developer-Tutorials.
Generate the SAMSON Extension#
Use the SAMSON Extension Generator to create a new SAMSON Extension called Barycenter, containing an app called SEBarycenterApp.
Setting up the external executable#
Implement the following simple demo program and save it as barycenter.cpp into the external subfolder of the generated extension:
#include <sstream>
#include <fstream>
#include <string>
#include <iostream>
int main(int argc, char* argv[]) {
// we expect the program name plus two arguments:
// input file path and output file path
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <input-file> <output-file>\n";
return 1;
}
// retrieve the paths from the command-line arguments
std::string inputFilePath = argv[1];
std::string outputFilePath = argv[2];
unsigned int numberOfAtoms;
// open the input file
std::ifstream inputFile(inputFilePath);
if (!inputFile.is_open()) {
std::cerr << "Error: could not open input file: "
<< inputFilePath << "\n";
return 1;
}
// open the output file
std::ofstream outputFile(outputFilePath);
if (!outputFile.is_open()) {
std::cerr << "Error: could not open output file: "
<< outputFilePath << "\n";
return 1;
}
// read the number of atoms
std::string line;
std::getline(inputFile, line);
std::stringstream firstLineParser(line);
firstLineParser >> numberOfAtoms;
if (!numberOfAtoms) {
outputFile << "No atoms" << std::endl;
return 0;
}
// read the coordinates of the atoms and update the barycenter
double x_c = 0.0, y_c = 0.0, z_c = 0.0;
while (std::getline(inputFile, line)) {
std::stringstream lineParser(line);
double x = 0.0, y = 0.0, z = 0.0;
lineParser >> x >> y >> z;
x_c += x;
y_c += y;
z_c += z;
}
x_c /= (double)numberOfAtoms;
y_c /= (double)numberOfAtoms;
z_c /= (double)numberOfAtoms;
outputFile << x_c << "A " << y_c << "A " << z_c << "A" << std::endl;
return 0;
}
This is a simple program for demo purposes, it opens an input text file containing atom coordinates and writes into an output file coordinates of the barycenter of the atoms (or the string "No atoms" if the input file did not contain any atom).
Compile it to produce an executable called barycenter:
- Open the Developer Command Prompt for Visual Studio (or any command prompt where the Visual C++ compiler cl.exe is available).
- Navigate to the directory containing
barycenter.cpp. -
Compile using the Visual C++ compiler:

Shipping the external executable#
To ship the external executable with your SAMSON Extension:
- in the Extension's folder, create the
externalsubfolder with the following subfolders:win,mac,lin(each to place an executable for the corresponding platform); - place the executable into the corresponding folder, e.g. on Windows into
external/win.
Modify the CMakeLists.txt file of the extension by adding the following lines that will copy the executable into the Resource folder shipped with the Extension:
if (WIN32)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/external/win/barycenter.exe
DESTINATION ${CMAKE_SAMSON_DEVELOPER_DATA_PATH}/Elements/${OUTPUT_UUID}/Resource/barycenter)
endif (WIN32)
if (UNIX AND NOT APPLE)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/external/lin/barycenter
DESTINATION ${CMAKE_SAMSON_DEVELOPER_DATA_PATH}/Elements/${OUTPUT_UUID}/Resource/barycenter
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
endif (UNIX AND NOT APPLE)
if (APPLE)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/external/mac/barycenter
DESTINATION ${CMAKE_SAMSON_DEVELOPER_DATA_PATH}/Elements/${OUTPUT_UUID}/Resource/barycenter
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
endif (APPLE)
Note
You can place executables in different subfolders and modify the CMakeLists.txt file accordingly.
Setting up the interface#
Open the UI file form/SEBarycenterAppGUI.ui in Qt Creator and add:
- a
QPushButtonnamedpushButtonCompute - a
QLabelnamedlabelResult

Add the slot declaration to the SEBarycenterGUI.hpp file:
In the SEBarycenterGUI constructor, connect the push button's clicked() signal to this slot:
QObject::connect(ui.pushButtonCompute, &QPushButton::clicked,
this, &SEBarycenterAppGUI::onCompute);
Modify the getName() function of the SEBarycenterGUI.cpp file in order to show a user-friendly name (e.g. "Barycenter") in Home > Apps.
Executing the external executable#
Since the actual barycenter calculations are going to be performed by an external executable, we could assume that it's OK to implement the main functionality of the app in the interface class, i.e. in the file SEBarycenterGUI.cpp. However, if later we wanted to expose the functionality of our SAMSON Extension to other SAMSON Extensions (thanks to the SAMSON's introspection mechanism, explored in another tutorial), we would only be able to expose the app itself, and not its interface. As a result, even in this case, we still want to implement the core functionality in the app itself, and not in its interface.
Now, add the slot definition to the SEBarycenterGUI.cpp file. This just calls the app, and update the label to show the result:
void SEBarycenterAppGUI::onCompute() {
ui.labelResult->setText(QString("Result: ") +
QString::fromStdString(getApp()->compute()));
}
Note that to separate functionality from interface, the app is going to return a std::string instead of a QString.
Add the function declaration inside the SEBarycenterApp in the SEBarycenterApp.hpp header:
We are going to use QProcess to execute an external process, SAMSON facade class to find atoms in the document, and fstream to manipulate files, so we add the following headers at the beginning of the SEBarycenterApp.cpp file:
Now, add the function definition at the end of the the SEBarycenterApp.cpp file:
std::string SEBarycenterApp::compute() {
// find all selected atoms in the active document
SBNodeIndexer atomIndexer;
SAMSON::getActiveDocument()->getNodes(atomIndexer,
SBNode::IsType(SBNode::Atom) && SBNode::IsSelected());
// set the temporary files in the SAMSON's scratch folder
const std::string scratchPath = SAMSON::getScratchPath();
const std::string inputFilePath = scratchPath + "/tmp-barycenter-input.txt";
const std::string outputFilePath = scratchPath + "/tmp-barycenter-output.txt";
// generate the input file for the executable
std::ofstream input(inputFilePath);
input << atomIndexer.size() << std::endl;
SB_FOR(SBNode * node, atomIndexer) {
SBAtom* atom = static_cast<SBAtom*>(node);
SBPosition3 position = atom->getPosition();
input <<
SBQuantity::angstrom(position.v[0]).getValue() << " " <<
SBQuantity::angstrom(position.v[1]).getValue() << " " <<
SBQuantity::angstrom(position.v[2]).getValue() << std::endl;
}
// execute the program and wait until it finishes
// Note: the path to the executable corresponds to
// the path where it was copied in CMakeLists.txt
QString program = QString::fromStdString(SB_ELEMENT_PATH +
"/Resource/barycenter/barycenter");
#if SB_WINDOWS
program += ".exe";
#endif
QStringList arguments{
QString::fromStdString(inputFilePath),
QString::fromStdString(outputFilePath)
};
QProcess* process = new QProcess();
process->setWorkingDirectory(QString::fromStdString(scratchPath));
process->start(program, arguments);
// wait until the process is finished or until time out at 60 seconds
process->waitForFinished(60000);
delete process;
// read the output generated by the external executable
std::ifstream output(outputFilePath);
std::string result;
std::getline(output, result);
// delete the temporary files
QFile(QString::fromStdString(inputFilePath)).remove();
QFile(QString::fromStdString(outputFilePath)).remove();
return result;
}
After compiling and installing (do not forget to build the INSTALL target), you may now use the app.
Note
In this tutorial, the external executable is expected to be pretty efficient so that we wait for it to finish before continuing. In the general case, you would connect the finished signal of the process to a Qt slot. Please refer to the documentation of QProcess for more information.
If you have any questions or feedback, please use the SAMSON Connect Forum.
Validate the result#
Your integration is working if:
- the app can generate temporary input for the external program;
- the external executable runs from the installed extension location;
- SAMSON reads the output and shows the expected result;
- temporary files are removed after execution.
Common pitfalls#
- Hardcoding development-only paths makes the extension fail after installation.
- Blocking on
waitForFinishedis acceptable for a tiny demo, but not for long-running real tools. - Forgetting to ship the external executable with the extension breaks the workflow on clean machines.
Next steps#
For more advanced integrations, move to asynchronous QProcess handling, richer parameter passing, and explicit error reporting. Related pages are Apps, Importing files, and Exporting files.