Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to deploy Qt application with external libraries



  • Hello, So ive built my Qt application and it uses the Python C API, which I statically link. In my .pro file. I Include it in my project as so:

    LIBS += -L"C:/python/libs" -lpython38
    
    INCLUDEPATH += C:/python/include/
    
    DEPENDPATH += C:/python/include/
    

    linking to a local directory on my computer. I then #include "Python.h" in one of my header files as usual - no problems.

    This works fine, but when it comes to deploying the application, I receive the error from windows:

    The code execution cannot proceed because python38.dll was not found. Reinstalling the program may fix this problem.
    

    Now, I dont understand why I got this error because I thought all the required Python libraries would be compiled into the .exe file since it was statically linked.

    Anyway, I add the python38.dll to the directory where my .exe is located, As required, and the program (.exe file) starts fine. However, when it gets to the part where it actually has to call the python code, the application just crashes.

    Any suggestion towards how I may fix this? Thank you.


  • Lifetime Qt Champion

    Hi,

    What do you do in that script ?



  • @SGaist In the python script you mean? It has a couple functions which sends request to a raspberry pi server. Here is how the python code is called within my Qt Application:

            Py_Initialize();
    
            PyObject* sys = PyImport_ImportModule( "sys" );
            PyObject* sys_path = PyObject_GetAttrString( sys, "path" );
            PyObject* folder_path = PyUnicode_FromString( "." );
            PyList_Append( sys_path, folder_path );
    
            PyObject *pName = PyUnicode_FromString("client");
            PyObject *pModule = PyImport_Import(pName);
    
            Py_DECREF(pName);
    
            PyObject* pFunc_send = PyObject_GetAttrString(pModule, (char*)"send_string");
            PyObject* pFunc_connect = PyObject_GetAttrString(pModule, (char*)"connect_to_server");
    
            if ((pFunc_send && PyCallable_Check(pFunc_send)) && (pFunc_connect && PyCallable_Check(pFunc_send)))
                {
                    PyObject* pResult_connect = PyObject_CallObject(pFunc_connect, NULL);
    
                    const int connection_result = PyLong_AsLong(pResult_connect);
    
                    if(connection_result)
                    {
                        for(auto question : cachedQuestions)
                        {
                            const char* args[2] = {question.first.c_str(), question.second.c_str()};
    
                            PyObject* pArgs_send = PyTuple_New(2);
    
                            for(int i = 0; i < 2; ++i)
                            {
                                PyObject* pValue = PyUnicode_FromString(args[i]);
                                if (!pValue) {
                                    Py_DECREF(pArgs_send);
                                    Py_DECREF(pModule);
                                    fprintf(stderr, "Cannot convert argument\n");
                                }
                                PyTuple_SetItem(pArgs_send, i, pValue);
                            }
    
                            //send the question and capture the server response
                            PyObject* pResult = PyObject_CallObject(pFunc_send, pArgs_send);
    
                            const std::string result = _PyUnicode_AsString(pResult);
    
                            Py_DECREF(pArgs_send);
                            Py_DECREF(pResult);
    
                            if(result == "data_error" && (POTWParams["new_playlist"] == "False"))
                            {
                                generateNewPopup("ERROR 1\n\n"
                                                 "there was an error in trasmitting your data to the    server\n"
                                                 "make sure your spotify playlist is of the form: https://open.spotify.com/playlist/________\n"
                                                 "please try again");
    
                                bool result = disconnect_server(pFunc_send);
    
                                if(!result)
                                {
                                    generateNewPopup("ERROR 2\n\n"
                                                     "there was an error when attempting to disconnect from the server\n"
                                                     "trying again ...");
    
                                    disconnect_server(pFunc_send);
    
                                    Py_DECREF(pFunc_send);
                                    Py_DECREF(pFunc_connect);
                                    Py_DECREF(pModule);
                                }
    
                                return;
                            }
                        }
                    }
                    bool disconnect_result = disconnect_server(pFunc_send);
    
                    if(disconnect_result)
                        generateNewPopup("SUCCESS\n\n"
                                         "A new POTW is now underway :)\n"
                                         "You may now quit the program");
                }
                else
                {
                    generateNewPopup("ERROR\n\n"
                                     "Could not call send function"
                                     "A file may have been moved or destroyed")
    ;            }
    
                Py_DECREF(pFunc_send);
                Py_DECREF(pFunc_connect);
                Py_DECREF(pModule);
    
           // Finish the Python Interpreter
            Py_Finalize();
        }
        else{
            generateNewPopup("ERROR\n\n"
                             "you did not fill in all the necessary fields.\n "
                              "Your request was not transmitted to the server. \n"
                              "Please try again \n");
        }
    

    you dont have to read it all, but just so you understand how im using the API I gave it for reference.


  • Lifetime Qt Champion

    Why not use QNetworkAccessManager rather than embedding python ?



  • @SGaist I could, but that would probably require me to install Qt on my target machine (im not even sure it has enough storage for that), and, the server needs to interact with a host of web APIs so it would have to switch from c++ to python at some point, since C++ is often not supported in those use cases. I do take your point though, and will probably resort to this if i cant find a solution for my current problems.


  • Lifetime Qt Champion

    Why would you need to do that ?
    QNetworkAccessManager just does standard http requests. The backend can be whatever you want.



  • I think you're right, it might be easier just to switch to an all Qt implementation. If I just need strings encoded in utf-8 format to a specific Port at a specific IP address, is QNetworkAccessManager the best tool for the job?


  • Lifetime Qt Champion

    What should your request look like ?



  • essentially the message being sent is built up as:

    [length of opcode (in bytes)] - [length of message (in byes)] - [opcode] - [message]

    where each section above is a utf-8 encoded strings.

    My original python code was:

    HEADER = 256 #header length(bytes)
    OPCODE = 4
    PORT = #####REDACTED_FOR_PRIVACY#####
    FORMAT = 'utf-8'
    DISCONNECT_MESSAGE = "!DISCONNECT"
    SERVER = #####REDACTED_FOR_PRIVACY#####
    ADDR = (SERVER, PORT)
    
    class Opcode (str, Enum):
        WEEK:                 str = "1"
        EMAIL:                str = "2"
        COMPETITION_DURATION: str = "3"
        SPOTIFY:              str = "5"
        SUBMISSION_DURATION:  str = "6"
        RANKING_DURATION:     str = "7"
        DISCONNECT:           str = "8"
        NEW_PLAYLIST:         str = "9"
        NUM_PARTICIPANTS:     str = "10"
        NAME_QUESTION:        str = "11"
        BONUS_QUESTION:       str = "12"
    
    def connect_to_server():
        global client
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.connect(ADDR)
        
    def send(opcode, msg):
            
        message= msg.encode(FORMAT) #convert the message to byte format
        op = opcode.encode(FORMAT)
    
        msg_length = len(message) #length of message 
        msg_send_length = str(msg_length).encode(FORMAT) #convert the length of the message to byte format
        
        op_length = len(op)
        op_send_length = str(op_length).encode(FORMAT)
        
        # Pad out the length to 64 bytes and the opcode to 4 bytes
        msg_send_length += b' ' * (HEADER - len(msg_send_length)) 
        op_send_length  += b' ' * (OPCODE - len(op_send_length)) 
        
        client.send(msg_send_length)
        client.send(op_send_length)
        client.send(op)
        client.send(message)
    
        return client.recv(2048).decode(FORMAT)
    
    
    send(Opcode.EMAIL, "example@gmail.com")
    send(Opcode.WEEK, "12")
    send(Opcode.COMPETITION_DURATION, "7")
    send(Opcode.SPOTIFY, "https://open.spotify.com/playlist/cyt67ryic657bi")
    

Log in to reply