Skip to content

Commit

Permalink
Fix compilation on Linux and use Catch2 v3
Browse files Browse the repository at this point in the history
  • Loading branch information
t-sommer committed Nov 29, 2023
1 parent 57d10cc commit 65a0696
Show file tree
Hide file tree
Showing 8 changed files with 24,052 additions and 11,674 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ jobs:
- run: cmake --build python_build --config Release
- run: python -m pip install -e Python
- run: python_build\Release\ExternalLibraryTest.exe
build-linux:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- run: cmake . -Bcpp_build
- run: cmake --build cpp_build
- run: ./cpp_build/ExternalLibraryTest
1 change: 1 addition & 0 deletions C/src/ExternalLibraryCPP.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <stdlib.h>
#include <fstream>
#include <cstring>

#include "ExternalLibrary.h"

Expand Down
174 changes: 58 additions & 116 deletions C/src/ExternalLibraryPython.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <stdlib.h>

#ifdef _WIN32
#include <Windows.h>
#endif

#ifdef _DEBUG
#undef _DEBUG
Expand All @@ -13,15 +15,11 @@
#include "ExternalLibrary.h"

#define PYTHON_MODULE "external_library"
#define ENV_VAR_MAX_SIZE 32767

ModelicaUtilityFunctions_t s_callbacks = { NULL };


static const char *initializePython(const char *pythonHome) {
const char* externalFunction(const char* filename, const char* moduleName, const char* functionName, const char* pythonHome, int nu, const double u[], int ny, double y[]) {

#ifdef _WIN32
DWORD dwAttrib = GetFileAttributes(pythonHome);
DWORD dwAttrib = GetFileAttributesA(pythonHome);

int valid = dwAttrib != INVALID_FILE_ATTRIBUTES;
int isdir = dwAttrib & FILE_ATTRIBUTE_DIRECTORY;
Expand All @@ -30,26 +28,7 @@ static const char *initializePython(const char *pythonHome) {
return "Argument pythonHome must be a valid directory.";
}

char path[ENV_VAR_MAX_SIZE];

strcpy(path, pythonHome);
strcat(path, ";");
strcat(path, pythonHome);
strcat(path, "\\Library\\mingw-w64\\bin;");
strcat(path, pythonHome);
strcat(path, "\\Library\\usr\\bin;");
strcat(path, pythonHome);
strcat(path, "\\Library\\bin;");
strcat(path, pythonHome);
strcat(path, "\\Library\\Scripts;");
strcat(path, pythonHome);
strcat(path, "\\bin;");

size_t len = strlen(path);

// add the Anaconda environment to the system path
GetEnvironmentVariable("PATH", &path[len], ENV_VAR_MAX_SIZE);
SetEnvironmentVariable("PATH", path);
BOOL res = SetDllDirectoryA(pythonHome);
#endif

size_t size;
Expand All @@ -59,43 +38,19 @@ static const char *initializePython(const char *pythonHome) {

Py_Initialize();

return NULL;
}

static const char *handlePythonError() {

PyObject *err = PyErr_Occurred();

if (!err) return NULL;

PyObject *ptype, *pvalue, *ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);

PyObject *pystr = PyObject_Str(pvalue);

Py_ssize_t size;
const char *str = PyUnicode_AsUTF8AndSize(pystr, &size);

return str;
}

#define HANDLE_PYTHON_ERROR if (error = handlePythonError()) { goto out; }

#define HANDLE_ERROR(F) if (error = F) { goto out; }

const char *externalFunction(const char *filename, const char *moduleName, const char *functionName, const char *pythonHome, int nu, const double u[], int ny, double y[]) {

const char *error = NULL;

HANDLE_ERROR(initializePython(pythonHome))

PyObject *py_moduleName = PyUnicode_FromString(moduleName);
PyObject *py_moduleName = PyUnicode_FromString(PYTHON_MODULE);

PyObject *py_module = PyImport_Import(py_moduleName);
HANDLE_PYTHON_ERROR

if (!py_module) {
return "Failed to load Python module \"" PYTHON_MODULE "\".";
}

PyObject *py_function = PyObject_GetAttrString(py_module, functionName);
HANDLE_PYTHON_ERROR
PyObject *py_function = PyObject_GetAttrString(py_module, "external_library_function");

if (!py_function) {
return "Failed to load function \"external_library_function\" from Python module \"" PYTHON_MODULE "\".";
}

PyObject *py_filename = PyUnicode_FromString(filename);

Expand All @@ -115,8 +70,7 @@ const char *externalFunction(const char *filename, const char *moduleName, const
Py_ssize_t py_size = PyTuple_Size(py_y);

if (ny != py_size) {
error = "The Python function returned the wrong number of values.";
goto out;
return "Function \"interpolate\" returned the wrong number of values.";
}

for (int i = 0; i < ny; i++) {
Expand All @@ -126,8 +80,7 @@ const char *externalFunction(const char *filename, const char *moduleName, const

Py_FinalizeEx();

out:
return error ? error : "";
return "";
}

typedef struct {
Expand All @@ -138,72 +91,70 @@ typedef struct {
PyObject *methodName;
} PythonObjects;

void* createExternalObject(const char *filename, const char *moduleName, const char *className, const char *pythonHome, const ModelicaUtilityFunctions_t *callbacks) {

s_callbacks = *callbacks;
const char *error = NULL;
void* createExternalObject(const char* filename, const char* moduleName, const char* className, const char* pythonHome, const ModelicaUtilityFunctions_t* callbacks) {

if (!filename) {
error = "Argument filename must not be NULL.";
goto out;
callbacks->ModelicaError("Argument filename must not be NULL.");
return NULL;
}

if (!moduleName) {
error = "Argument moduleName must not be NULL.";
goto out;
#ifdef _WIN32
DWORD dwAttrib = GetFileAttributesA(pythonHome);

int valid = dwAttrib != INVALID_FILE_ATTRIBUTES;
int isdir = dwAttrib & FILE_ATTRIBUTE_DIRECTORY;

if (!pythonHome || !valid || !isdir) {
callbacks->ModelicaError("Argument pythonHome must be a valid directory.");
return NULL;
}

if (!className) {
error = "Argument className must not be NULL.";
goto out;
BOOL res = SetDllDirectoryA(pythonHome);
#endif

size_t size;
const wchar_t *python_home_w = Py_DecodeLocale(pythonHome, &size);

Py_SetPythonHome(python_home_w);

Py_Initialize();

PythonObjects *o = malloc(sizeof(PythonObjects));

if (!o) {
return NULL;
}

HANDLE_ERROR(initializePython(pythonHome))
o->moduleName = PyUnicode_FromString(PYTHON_MODULE);
o->module = PyImport_Import(o->moduleName);
o->module = PyImport_ReloadModule(o->module);

PythonObjects o;
if (!o->module) {
return NULL;
}

o.moduleName = PyUnicode_FromString(moduleName);
o.module = PyImport_Import(o.moduleName);
HANDLE_PYTHON_ERROR
o->methodName = PyUnicode_DecodeFSDefault("evaluate");

o.methodName = PyUnicode_DecodeFSDefault("evaluate");
o->class = PyObject_GetAttrString(o->module, "ExternalLibraryObject");

o.class = PyObject_GetAttrString(o.module, className);
HANDLE_PYTHON_ERROR
//int is_type = PyType_Check(o->class);

PyObject *py_filename = PyUnicode_FromString(filename);

PyObject *py_ctor_args = PyTuple_New(1);

PyTuple_SetItem(py_ctor_args, 0, py_filename);

o.instance = PyObject_CallObject(o.class, py_ctor_args);
HANDLE_PYTHON_ERROR
o->instance = PyObject_CallObject(o->class, py_ctor_args);

out:
if (error) {
if (s_callbacks.ModelicaError) {
s_callbacks.ModelicaError(error);
}
return NULL;
}
PyErr_Print();

PythonObjects *po = malloc(sizeof(PythonObjects));

*po = o;

return po;
return o;
}

void evaluateExternalObject(void *externalObject, int nu, const double u[], int ny, double y[]) {

const char *error = NULL;

if (!externalObject) {
error = "Argument externalObject must not be NULL.";
goto out;
}

PythonObjects *o = (PythonObjects *)externalObject;

PyObject *py_u = PyTuple_New(nu);
Expand All @@ -217,26 +168,17 @@ void evaluateExternalObject(void *externalObject, int nu, const double u[], int
PyTuple_SetItem(py_args, 0, py_u);

PyObject *py_y = PyObject_CallMethodObjArgs(o->instance, o->methodName, py_u, NULL);
HANDLE_PYTHON_ERROR

PyErr_Print();

Py_ssize_t size = PyTuple_Size(py_y);

if (ny != size) {
error = "The size of the return value does not match the number of outputs.";
goto out;
}
if (ny != size) return;

for (int i = 0; i < ny; i++) {
PyObject *py_v = PyTuple_GetItem(py_y, i);
y[i] = PyFloat_AsDouble(py_v);
}

out:
if (error) {
if (s_callbacks.ModelicaError) {
s_callbacks.ModelicaError(error);
}
}
}

void freeExternalObject(void *externalObject) {
Expand Down
31 changes: 27 additions & 4 deletions C/tests/ExternalLibrary_test.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"

#include "catch_amalgamated.hpp"


# ifdef _WIN32
#include <Windows.h>
# else
#include <unistd.h>
#include <dlfcn.h>
typedef void * HMODULE;
#include <stdio.h>
#include <limits.h>
#include <libgen.h>
# endif

#ifdef _MSC_VER
#define strdup _strdup
#endif

#define EXPORT typedef

Expand All @@ -18,7 +35,7 @@ template<typename T> T *get(HMODULE libraryHandle, const char *functionName) {
# ifdef _WIN32
auto *fp = GetProcAddress(libraryHandle, functionName);
# else
auto *fp = dlsym(m_libraryHandle, functionName);
auto *fp = dlsym(libraryHandle, functionName);
# endif

REQUIRE(fp);
Expand All @@ -29,7 +46,7 @@ template<typename T> T *get(HMODULE libraryHandle, const char *functionName) {
static const char *s_errorMessage = nullptr;

static void ModelicaError(const char *string) {
s_errorMessage = _strdup(string);
s_errorMessage = strdup(string);
}


Expand All @@ -39,7 +56,13 @@ TEST_CASE("External functions can be loaded and called", "[ExternalLibrary]") {
# ifdef _WIN32
auto l = LoadLibraryA("ExternalLibrary.dll");
# else
auto l = dlopen("ExternalLibrary.so", RTLD_LAZY);
char buf[FILENAME_MAX];
ssize_t nbytes, bufsiz;
// get the path of the executable
nbytes = readlink("/proc/self/exe", buf, FILENAME_MAX);
auto basename = dirname(buf);
auto filename = strcat(basename, "/ExternalLibrary.so");
auto l = dlopen(filename, RTLD_NOW);
# endif

ModelicaUtilityFunctions_t callbacks = { nullptr };
Expand Down
Loading

0 comments on commit 65a0696

Please sign in to comment.