Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add win32 support #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions adPythonApp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *db*))
DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *Db*))
DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard *opi*))
DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard protocol))
DIRS := $(DIRS) $(filter-out $(DIRS), $(wildcard scripts))
include $(TOP)/configure/RULES_DIRS

26 changes: 26 additions & 0 deletions adPythonApp/scripts/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
TOP=../..
include $(TOP)/configure/CONFIG
#----------------------------------------
# ADD MACRO DEFINITIONS AFTER THIS LINE

#----------------------------------------------------
# Install scripts
PLUGINSCRIPTS += adPythonBarCode.py
PLUGINSCRIPTS += adPythonCircle.py
PLUGINSCRIPTS += adPythonCrystalDroplet.py
PLUGINSCRIPTS += adPythonCrystalFeatureMatch.py
PLUGINSCRIPTS += adPythonDataMatrix.py
PLUGINSCRIPTS += adPythonFocus.py
PLUGINSCRIPTS += adPythonGaussian2DFitter.py
PLUGINSCRIPTS += adPythonMitegen.py
PLUGINSCRIPTS += adPythonMorph.py
PLUGINSCRIPTS += adPythonMxSampleDetect.py
PLUGINSCRIPTS += adPythonPowerMean.py
PLUGINSCRIPTS += adPythonRotate.py
PLUGINSCRIPTS += adPythonTemplate.py
PLUGINSCRIPTS += adPythonTransfer.py
PLUGINSCRIPTS += transferclient.py

include $(TOP)/configure/RULES
#----------------------------------------
# ADD RULES AFTER THIS LINE
44 changes: 24 additions & 20 deletions adPythonApp/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ include $(TOP)/configure/CONFIG

# This tells the compiler to ignore errors generated by EPICS includes. We need
# this because the EPICS headers have non strict prototypes in places.
ifdef OS_CLASS
ifneq ($(OS_CLASS), WIN32)
USR_CPPFLAGS += -isystem $(EPICS_BASE)/include
endif
endif

# Nice and strict...
USR_CXXFLAGS += -Wall
ifdef OS_CLASS
ifneq ($(OS_CLASS), WIN32)
USR_CXXFLAGS += -Werror
USR_CXXFLAGS += -Wall -Wextra
USR_CXXFLAGS += -Wextra
USR_CXXFLAGS += -Wno-unused-parameter
USR_CXXFLAGS += -Wno-missing-field-initializers
USR_CXXFLAGS += -Wundef
Expand All @@ -21,6 +28,8 @@ USR_CXXFLAGS += -Wcast-align
USR_CXXFLAGS += -Wwrite-strings
USR_CXXFLAGS += -Wredundant-decls
USR_CXXFLAGS += -Wmissing-declarations
endif
endif

LIBRARY_IOC += adPython

Expand All @@ -30,31 +39,26 @@ DBD += adPythonPlugin.dbd
# The following are compiled and added to the support library
adPython_SRCS += adPythonPlugin.cpp

# Guess the python executable
ifneq ($(PYTHON_PREFIX),)
PYTHON = $(PYTHON_PREFIX)/bin/python
else
PYTHON = python
endif

# Get the version string of python
PYTHON_VERSION = $(shell $(PYTHON) -c 'from distutils import sysconfig; \
print sysconfig.get_config_var("VERSION")')

# link against python
ifneq ($(PYTHON_PREFIX),)
# user defined prefix, add it explicitly
USR_INCLUDES += -I$(PYTHON_PREFIX)/include/python$(PYTHON_VERSION)
python$(PYTHON_VERSION)_DIR = $(PYTHON_PREFIX)/lib
adPython_LIBS += python$(PYTHON_VERSION)
# add the numpy include path too, which is printed out when we run
# adPythonPlugin.py from the command line
USR_INCLUDES += -I$(shell $(PYTHON) ../adPythonPlugin.py)
USR_INCLUDES += -I$(PYTHON_INCLUDE)
USR_INCLUDES += -I$(NUMPY_INCLUDE)
python$(PYTHON_VERSION)_DIR = $(PYTHON_LIB)
LIB_LIBS += python$(PYTHON_VERSION)
else
# it's in a standard place
adPython_SYS_LIBS += python$(PYTHON_VERSION)
LIB_SYS_LIBS += python$(PYTHON_VERSION)
endif

USR_CXXFLAGS += -DDATADIRS=\"$(shell cd ..; pwd):$(shell cd ../../scripts; pwd)\"
# add more libs
LIB_LIBS += NDPlugin

# location of plugin scripts
USR_CXXFLAGS += -DDATADIRS=\"$(ADPYTHON_MODULE_PATH)\"

# install plugin scripts
PLUGINSCRIPTS += adPythonOffline.py
PLUGINSCRIPTS += adPythonPlugin.py

include $(TOP)/configure/RULES
85 changes: 48 additions & 37 deletions adPythonApp/src/adPythonPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#define NPY_INTERNAL_BUILD 0
#include "numpy/ndarrayobject.h"
#include <stdio.h>
#include <libgen.h>
#include <epicsTime.h>
#include "NDArray.h"
#include "adPythonPlugin.h"
Expand All @@ -37,14 +36,16 @@
#define Bad(errString) NoGood(errString, BAD)
#define Ugly(errString) NoGood(errString, UGLY)

// Used in setting the python module search path
#ifdef _WIN32
#define PATH_LIST_SEPARATOR ";"
#else
#define PATH_LIST_SEPARATOR ":"
#endif

// Used in error printing
const char *driverName = "adPythonPlugin";

// This holds the threadState of the thread that initialised python
// We need it to get a handle on the interpreter so create a threadState
// object for each port
static PyThreadState *mainThreadState = NULL;

adPythonPlugin::adPythonPlugin(const char *portNameArg, const char *filename,
const char *classname, int queueSize, int blockingCallbacks,
const char *NDArrayPort, int NDArrayAddr, int maxBuffers,
Expand Down Expand Up @@ -84,24 +85,24 @@ adPythonPlugin::adPythonPlugin(const char *portNameArg, const char *filename,
*/
void adPythonPlugin::initThreads()
{
// First we tell python where to find adPythonPlugin.py and other scripts
char buffer[BIGBUFFER];
snprintf(buffer, sizeof(buffer), "PYTHONPATH=%s", DATADIRS);
putenv(buffer);

// Now we initialise python
PyGILState_STATE gstate;

// Initialise python
if (!Py_IsInitialized()) {
PyEval_InitThreads();
Py_Initialize();

// Be sure to save thread state to release the GIL and give a handle
// on the interpreter to this and other ports
mainThreadState = PyEval_SaveThread();
// Release the GIL
PyEval_SaveThread();
}

// Create a thread state just for us
this->threadState = PyThreadState_New(mainThreadState->interp);
PyEval_RestoreThread(this->threadState);
// Acquire the GIL and set up non-python-created thread access
gstate = PyGILState_Ensure();

// Tell python where to find adPythonPlugin.py and other scripts
char buffer[BIGBUFFER];
snprintf(buffer, sizeof(buffer), "%s%s%s", Py_GetPath(),
PATH_LIST_SEPARATOR, DATADIRS);
PySys_SetPath(buffer);

// Import our supporting library
this->importAdPythonModule();
Expand All @@ -120,8 +121,8 @@ void adPythonPlugin::initThreads()
this->updateParamList(1);
}

// Release the GIL and finish
this->threadState = PyEval_SaveThread();
// Release the GIL and tear down non-python-created thread access
PyGILState_Release(gstate);
}

/** Callback function that is called by the NDArray driver with new NDArray data
Expand All @@ -133,6 +134,8 @@ void adPythonPlugin::initThreads()
* Called with this->lock taken
*/
void adPythonPlugin::processCallbacks(NDArray *pArray) {
PyGILState_STATE gstate;

// First call the base class method
NDPluginDriver::beginProcessCallbacks(pArray);

Expand All @@ -146,8 +149,8 @@ void adPythonPlugin::processCallbacks(NDArray *pArray) {
this->unlock();
epicsMutexLock(this->dictMutex);

// Make sure we're allowed to use the python API
PyEval_RestoreThread(this->threadState);
// Acquire the GIL and set up non-python-created thread access
gstate = PyGILState_Ensure();
this->lock();

// Store the time at the beginning of processing for profiling
Expand Down Expand Up @@ -184,9 +187,11 @@ void adPythonPlugin::processCallbacks(NDArray *pArray) {
setDoubleParam(adPythonTime, epicsTimeDiffInSeconds(&ts_end, &ts_start)*1000);
callParamCallbacks();

// release GIL and dict Mutex
// Unlock
this->unlock();
this->threadState = PyEval_SaveThread();
// Release the GIL and tear down non-python-created thread access
PyGILState_Release(gstate);
// Release dict mutex
epicsMutexUnlock(this->dictMutex);

// Spit out the array
Expand All @@ -212,14 +217,15 @@ asynStatus adPythonPlugin::writeInt32(asynUser *pasynUser, epicsInt32 value) {
int param = pasynUser->reason;
if (param == adPythonLoad ||
(this->nextParam && param >= adPythonUserParams[0])) {
PyGILState_STATE gstate;
// We have to modify our python dict to match our param list
// Note: to avoid deadlocks we should always take locks in order:
// dictMutex, then GIL, then this->lock
// so unlock here to preserve this order
this->unlock();
epicsMutexLock(this->dictMutex);
// Make sure we're allowed to use the python API
PyEval_RestoreThread(this->threadState);
// Acquire the GIL and set up non-python-created thread access
gstate = PyGILState_Ensure();
// Now call the bast class to write the value to the param list
this->lock();
status |= NDPluginDriver::writeInt32(pasynUser, value);
Expand All @@ -233,8 +239,9 @@ asynStatus adPythonPlugin::writeInt32(asynUser *pasynUser, epicsInt32 value) {
// our param lib has changed, so update the dict and reprocess
status |= this->updateParamDict();
}
// release GIL and dict Mutex
this->threadState = PyEval_SaveThread();
// Release the GIL and tear down non-python-created thread access
PyGILState_Release(gstate);
// Release dict mutex
epicsMutexUnlock(this->dictMutex);
} else {
status |= NDPluginDriver::writeInt32(pasynUser, value);
Expand All @@ -255,21 +262,23 @@ asynStatus adPythonPlugin::writeFloat64(asynUser *pasynUser,
int status = asynSuccess;
int param = pasynUser->reason;
if (this->nextParam && param >= adPythonUserParams[0]) {
PyGILState_STATE gstate;
// We have to modify our python dict to match our param list
// Note: to avoid deadlocks we should always take locks in order:
// dictMutex, then GIL, then this->lock
// so unlock here to preserve this order
this->unlock();
epicsMutexLock(this->dictMutex);
// Make sure we're allowed to use the python API
PyEval_RestoreThread(this->threadState);
// Acquire the GIL and set up non-python-created thread access
gstate = PyGILState_Ensure();
// Now call the bast class to write the value to the param list
this->lock();
status |= NDPluginDriver::writeFloat64(pasynUser, value);
// our param lib has changed, so update the dict and reprocess
status |= this->updateParamDict();
// release GIL and dict Mutex
this->threadState = PyEval_SaveThread();
// Release the GIL and tear down non-python-created thread access
PyGILState_Release(gstate);
// Release dict mutex
epicsMutexUnlock(this->dictMutex);
} else {
status = NDPluginDriver::writeFloat64(pasynUser, value);
Expand All @@ -292,21 +301,23 @@ asynStatus adPythonPlugin::writeOctet(asynUser *pasynUser, const char *value,
int status = asynSuccess;
int param = pasynUser->reason;
if (this->nextParam && param >= adPythonUserParams[0]) {
PyGILState_STATE gstate;
// We have to modify our python dict to match our param list
// Note: to avoid deadlocks we should always take locks in order:
// dictMutex, then GIL, then this->lock
// so unlock here to preserve this order
this->unlock();
epicsMutexLock(this->dictMutex);
// Make sure we're allowed to use the python API
PyEval_RestoreThread(this->threadState);
// Acquire the GIL and set up non-python-created thread access
gstate = PyGILState_Ensure();
// Now call the bast class to write the value to the param list
this->lock();
status |= NDPluginDriver::writeOctet(pasynUser, value, maxChars, nActual);
// our param lib has changed, so update the dict and reprocess
status |= this->updateParamDict();
// release GIL and dict Mutex
this->threadState = PyEval_SaveThread();
// Release the GIL and tear down non-python-created thread access
PyGILState_Release(gstate);
// Release dict mutex
epicsMutexUnlock(this->dictMutex);
} else {
status |= NDPluginDriver::writeOctet(pasynUser, value, maxChars, nActual);
Expand Down
5 changes: 4 additions & 1 deletion adPythonApp/src/adPythonPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <iocsh.h>
#include <epicsExport.h>

#if defined(_MSC_VER) && !defined(__func__)
#define __func__ __FUNCTION__
#endif

// Max number of user parameters in a subclass
#define NUSERPARAMS 100

Expand Down Expand Up @@ -58,7 +62,6 @@ class adPythonPlugin : public NDPluginDriver {
NDAttributeList *pFileAttributes;
int nextParam, pluginState;
epicsMutexId dictMutex;
PyThreadState *threadState;
};

#endif
2 changes: 2 additions & 0 deletions configure/CONFIG_PLUGINSCRIPTS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FILE_TYPE += PLUGINSCRIPTS
INSTALL_PLUGINSCRIPTS = $(INSTALL_LOCATION)/plugin-scripts
Loading