모듈은 import하는데 함수를 못 읽어들이는 상황이 발생
한동안 삽질하다가 오늘 그냥 지금까지 한거 싹 날려버리고 처음부터 차근차근 하니까 성공..
아무래도 python 코드 또는 c 코드에서 잘못된것 같은데..
다음부터 python 코드를 짜면 먼저 작동하는지부터 확인해야겠네요
우선 전체 코드는 다음과 같습니다
#include <Python.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <Windows.h>
#include <process.h>
#define sleep(x) Sleep(1000 * x)
void ThreadProc(void*);
void ThreadProc2(void*);
#define NUM_ARGUMENTS 5
typedef struct
{
int argc;
char *argv[NUM_ARGUMENTS];
} CMD_LINE_STRUCT;
int main(int argc, char *argv[])
{
int i;
CMD_LINE_STRUCT cmd;
PyThreadState *mainThreadState;
PyGILState_STATE gilState;
HANDLE hThread;
DWORD dwThreadID;
cmd.argc = argc;
for( i = 0; i < NUM_ARGUMENTS; i++ )
{
cmd.argv[i] = argv[i];
}
if (argc < 3)
{
fprintf(stderr, "Usage: call python_filename function_name [args]\n");
return 1;
}
// init python interpreter
Py_Initialize();
// init thread support
PyEval_InitThreads();
// Save a pointer to the main PyThreadState Object
mainThreadState = PyEval_SaveThread();
gilState = PyGILState_Ensure();
hThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int(_stdcall*)(void*))ThreadProc, &cmd, 0, (unsigned*)&dwThreadID);
PyGILState_Release(gilState);
for(i = 0; i < 10; i++)
{
printf("this is main thread...\n");
sleep(1);
}
printf("Main Thread waiting for My Thread to complete...\n");
WaitForSingleObject(hThread,INFINITE);
printf("Main thread finished gracefully.\n");
PyEval_RestoreThread(mainThreadState);
Py_Finalize();
return 0;
}
void ThreadProc(void *data)
{
PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pSysModules;
PyGILState_STATE gilState;
CMD_LINE_STRUCT* arg = (CMD_LINE_STRUCT*)data;
for (int i = 0; i < 15; i++)
{
printf("is threadfunc1...\n");
sleep(1);
}
gilState = PyGILState_Ensure();
pName = PyString_FromString(arg->argv[1]);
pModule = PyImport_Import(pName);
Py_DECREF(pName);
pDict = PyModule_GetDict(pModule);
pFunc = PyDict_GetItemString(pDict, arg->argv[2]);
if(PyCallable_Check(pFunc))
{
pValue = PyObject_CallObject(pFunc, NULL);
}
else
{
PyErr_Print();
}
pSysModules = PySys_GetObject("modules");
if(NULL != pSysModules)
{
PyObject *pModuleName = PyString_FromString(arg->argv[1]);
int ret = PyDict_DelItem(pSysModules, pModuleName);
if(-1 == ret)
{
PyErr_Print();
}
}
Py_DECREF(pModule);
PyGILState_Release(gilState);
}
테스트용 코드라 변수하고 함수 구분도 그냥 숫자로..ㅋ
설명을 간단하게 하자면
PyThreadState *mainThreadState;
PyGILState_STATE gilState;
이 부분에서 PyThreadState와 PyGILState_STATE변수를 정해줍니다.
PyThreadState는 메인스레드의 포인터를 저장할 용도로 정의되고
PyGILState_STATE는 global interpreter lock(이하 GIL)을 잡고, 풀어주는 용도입니다.
Py_Initialize();
PyEval_InitThreads();
mainThreadState = PyEval_SaveThread();
gilState = PyGILState_Ensure();
python api들의 사용을 위해 Py_Initialize()를 호출하고
PyEval_InitThreads()로 멀티스레드 환경을 가능하게 합니다
PyEval_SaveThread()를 사용하여 메인스레드의 포인터를 변수에 저장하고
PyGILState_Ensure()를 이용해서 GIL를 잡아줍니다.
PyGILState_Ensure()는 python 2.3 이후 버전부터 사용 가능한 api인데요
2.3 이전 버전에서는
PyEval_AcquireLock()을 이용해 GIL를 잡고,
PyThreadState_Swap(PyThreadState*)으로 PyThreadState 객체를 로드한 뒤
파이썬 코드를 실행하고
PyThreadState_Swap(NULL)을 이용, PyThreadState 객체를 언로드하고
PyEval_ReleaseLock()으로 GIL를 풀어주는 작업을 진행했어야 하는데
2.3이후 버전부터는
PyGILState_Ensure() 이 함수 하나만으로
PyEval_AcquireLock()과 PyThreadState_Swap(PyThreadState*)
이 두개의 함수를 사용한 결과와 똑같이 됩니다.
자, GIL까지 잡고.. 그 다음은 c에서 스레드를 만들어서 동작시키고 GIL를 풀어줍니다
PyGILState_Release(gilState);
PyGILState_STATE 가 저장된 변수인 gilState를 인자로하여 PyGILState_Release()를 호출, GIL을 풀어줍니다.
이 역시 위에서 설명한 PyThreadState_Swap(NULL), PyEval_ReleaseLock() 두개의 함수와 동일한 역할을 합니다.
나머진 c 코드니까 넘어가구요,
PyEval_RestoreThread(mainThreadState);
Py_Finalize();
이제 프로그램이 끝나니 저장해두었던 mainThreadState가 필요 없어졌으므로 PyEval_RestoreThread()로 저장한것을 없애구요,
더 이상 파이썬 인터프리터를 사용하지 않을것이므로 Py_Finalize()를 호출하여 파이썬 인터프리터를 종료합니다.
이제 스레드 부분을 보시면
PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pSysModules;
PyGILState_STATE gilState;
필요한 PyObject 변수들을 선언하고, 스레드에서도 GIL을 잡고 풀어줘야 하기 때문에 PyGILState_STATE 변수를 선언합니다.
gilState = PyGILState_Ensure();
pName = PyString_FromString(arg->argv[1]);
pModule = PyImport_Import(pName);
Py_DECREF(pName);
pDict = PyModule_GetDict(pModule);
pFunc = PyDict_GetItemString(pDict, arg->argv[2]);
메인 스레드에서와 마찬가지로 PyGILState_Ensure()를 이용, GIL을 잡아줍니다.
그 밑까지는 모듈을 import하고 모듈의 함수를 불러오기까지의 루틴입니다.
모듈을 불러온 뒤 pName은 필요 없어졌으므로 레퍼런스 카운트를 감소시켜 줍니다
if(PyCallable_Check(pFunc))
{
pValue = PyObject_CallObject(pFunc, NULL);
}
else
{
PyErr_Print();
}
pSysModules = PySys_GetObject("modules");
if(NULL != pSysModules)
{
PyObject *pModuleName = PyString_FromString(arg->argv[3]);
int ret = PyDict_DelItem(pSysModules, pModuleName);
if(-1 == ret)
{
PyErr_Print();
}
}
Py_DECREF(pModule);
PyGILState_Release(gilState);
이 부분은 함수가 정확하게 로드되었는지 확인 후 로드하고
캐싱된 모듈을 지워주는 부분입니다.
역시 마지막엔 레퍼런스 카운트를 감소시키구요,
GIL을 잡았었으니 PyGILState_Release(gilState)를 이용해 풀어줍니다.