@@ -34,6 +34,9 @@ static PyObject* pFunc = nullptr;
3434// Python-Callable Logging Function
3535// =================================================================
3636
37+ // Global pointer to store error message for GoldSim (static - internal use only)
38+ static std::string* g_python_error_message = nullptr ;
39+
3740// Python-callable logging function (static - internal use only)
3841static PyObject* PythonLog (PyObject* self, PyObject* args) {
3942 const char * message;
@@ -66,9 +69,40 @@ static PyObject* PythonLog(PyObject* self, PyObject* args) {
6669 Py_RETURN_NONE;
6770}
6871
72+ // Python-callable error function that signals a fatal error to GoldSim
73+ // NOTE: For 64-bit DLLs running in separate process space, we cannot pass
74+ // pointers to GoldSim. Instead, we raise a Python exception that C++ will catch.
75+ static PyObject* PythonError (PyObject* self, PyObject* args) {
76+ const char * message;
77+
78+ // Parse arguments: message (required)
79+ if (!PyArg_ParseTuple (args, " s" , &message)) {
80+ return nullptr ;
81+ }
82+
83+ // Log the error
84+ LogError (std::string (message));
85+
86+ // Store the error message for GoldSim
87+ if (g_python_error_message == nullptr ) {
88+ g_python_error_message = new std::string (message);
89+ } else {
90+ *g_python_error_message = message;
91+ }
92+
93+ LogDebug (" gspy.error() called, message stored: " + std::string (message));
94+
95+ // Raise a Python RuntimeError with the message
96+ // This will cause PyObject_CallObject to return NULL, triggering our error handling
97+ PyErr_SetString (PyExc_RuntimeError, message);
98+
99+ return nullptr ; // Return NULL to indicate an exception occurred
100+ }
101+
69102// Method definition for the gspy module
70103static PyMethodDef GSPyMethods[] = {
71104 {" log" , PythonLog, METH_VARARGS, " Write a message to the GSPy log file" },
105+ {" error" , PythonError, METH_VARARGS, " Signal a fatal error to GoldSim and terminate the simulation" },
72106 {nullptr , nullptr , 0 , nullptr } // Sentinel
73107};
74108
@@ -360,6 +394,12 @@ void FinalizePython() {
360394 Py_XDECREF (pFunc);
361395 Py_XDECREF (pModule);
362396
397+ // Clean up the error message pointer
398+ if (g_python_error_message != nullptr ) {
399+ delete g_python_error_message;
400+ g_python_error_message = nullptr ;
401+ }
402+
363403 if (Py_IsInitialized ()) {
364404 // LOGGING: Confirm that we are shutting down the interpreter.
365405 LogInfo (" Shutting down Python interpreter." );
@@ -448,6 +488,33 @@ void ExecuteCalculation(double* inargs, double* outargs, std::string& errorMessa
448488 PyObject* pResultTuple = PyObject_CallObject (pFunc, pArgs);
449489 Py_DECREF (pArgs);
450490
491+ // 2.5. Check if Python raised an exception (including from gspy.error())
492+ if (pResultTuple == nullptr ) {
493+ // Python exception occurred
494+ if (g_python_error_message != nullptr && !g_python_error_message->empty ()) {
495+ // gspy.error() was called - use the stored message
496+ errorMessage = " GSPy Error: " + *g_python_error_message;
497+ LogDebug (" Python signaled fatal error via gspy.error(): " + *g_python_error_message);
498+ g_python_error_message->clear ();
499+ } else {
500+ // Some other Python exception - get the error details
501+ PyErr_Print ();
502+ errorMessage = " Python exception occurred (see log for details)" ;
503+ LogError (errorMessage);
504+ }
505+ return ;
506+ }
507+
508+ // 2.6. Check if gspy.error() was called but Python still returned successfully
509+ // (This shouldn't happen with the new implementation, but keep as safety check)
510+ if (g_python_error_message != nullptr && !g_python_error_message->empty ()) {
511+ errorMessage = " GSPy Error: " + *g_python_error_message;
512+ LogDebug (" Python signaled fatal error: " + errorMessage);
513+ g_python_error_message->clear ();
514+ Py_DECREF (pResultTuple);
515+ return ;
516+ }
517+
451518 // 3. Delegate result processing
452519 if (!MarshalOutputsToCpp (pResultTuple, config[" outputs" ], outargs, errorMessage)) {
453520 // MarshalOutputsToCpp handles its own error logging and Py_DECREF
0 commit comments