LoginSignup
18
23

More than 5 years have passed since last update.

Pythonから他のプログラムの制御を行う(Python⇔exe間の通信)

Last updated at Posted at 2018-05-19

1. 目的

Pythonで他のプログラムを動かすことが目的です.具体的にはC言語で公開されていたSDKをPythonと上手く組み合わせて使えないか思ったのがきっかけでした.

  • Pythonのスクリプトから.exeファイルを起動
  • 立ち上がったexeへPythonからコマンド送信
  • exeからの出力(stdout)をPythonで受け取る

2. トライ&エラー

2.1 Pythonのsubprocessでそのまま制御できる場合

Pythonのモジュールのsubprocessを用いると,他の実行可能なプログラムをPythonから動かすことができるとのことで,これを採用しました.他のプロセスとの通信にはPIPEと呼ばれるプロセス間通信手法が入っており,サクッといけそうな感じがします.実際に以下のPython(制御)とC(動かすプログラム)の組み合わせではうまく動作します.

OK.cpp
#include <stdio.h>

#define MAX_BUFFER 256

int main()
{
    char buf[MAX_BUFFER];

    fgets(buf, MAX_BUFFER, stdin);
    printf("your input is :%s", buf);

    return 0;
}
control.py
import subprocess as sp

if __name__ == "__main__":

    # exeファイルをプロセスとして立ち上げる
    # stdinとstdoutをPIPEとして,このPythonとつなぐ
    cmd = ".\\OK.exe"
    p = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE)

    # Popenのインスタンスにcommunicateで命令を送る
    # 戻り値は(stdout, stderr)のタプルのため,それぞれ保管
    # 文字列の前のbはbyteに変換するため
    out, err = p.communicate(input=b"Hello World")

    print(out)
    exit()

(OK.cppをコンパイルし,OK.exeとして以下のcontrol.pyと同じフォルダに入れています)

2.2 Pythonのsubprocessでそのまま制御できない場合(私はハマった)

しかし,私が扱いたいSDKはstdinの入力を通じて状態遷移し,何度も入力が必要なものでした.この場合,先と同じcontrol.pyを用いてもcommunicateの部分で動作が止まります.例えばこのようなCプログラムはNGです.

NG.cpp
#include <stdio.h>
#include <string.h>

#define MAX_BUFFER 256

int main()
{
    char buf[MAX_BUFFER], *ret;
    //入力文字列にendの文字があるまで入力を受け続ける
    while(1)
    {
        fgets(buf, MAX_BUFFER, stdin);
        printf("your input is :%s", buf);
        //入力文字列にendがあるかを判定
        ret = strstr(buf, "end");
        //ない場合はretにNULLが返っているため,while文を抜ける
        if(ret!=NULL)
        {
            break;
        }
    }
    return 0;
}

これを解決するPython側の実装を私は見つけることができませんでした…ので,今回はC側の実装で解決してしまいました.以下にC側での実装例を示しますが,Python側でもこうすればできる!という方法を知っている方がいましたら,教えていただけると有難いです.
(私が調べた感じではpyexpectがかなり良い線いってそうで実際試してみたのですが,Linuxでないとフルの機能が使えず断念しました.Linuxで行う場合はもう少し苦労せずに済むかもしれません)

3. 採用した方法:ソケット通信

今回採用したのはTCPのサーバ・クライアント通信でコマンドを受け取るようにし,クライアントのプロセスをPython側で都度立ち上げるというものです.以下に例を示します.

  • サーバプログラム
main.cpp
#include "stdafx.h"

char *getCharTCP(char*);

int main()
{
    char buf[DEFAULT_BUFLEN];
    char *ret;
    //入力文字列にendの文字があるまで入力を受け続ける
    while(1)
    {
        //fgets(buf, DEFAULT_BUFLEN, stdin);
        printf("waiting new input :\n");
        ret = getCharTCP(buf);
        printf("your input is :%s", buf);
        //入力文字列にendがあるかを判定
        ret = strstr(buf, "end");
        //ない場合はretにNULLが返っているため,while文を抜ける
        if(ret!=NULL)
        {
            break;
        }
    }
    return 0;
}
getCharTCP.cpp
#include "stdafx.h"

char *getCharTCP(char *out)
{
    WSADATA wsaData;
    int iResult, i;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iSendResult;
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;
    char errorcode[DEFAULT_BUFLEN];
    strcpy(errorcode, "error");

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return errorcode;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return errorcode;
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return errorcode;
    }

    // Setup the TCP listening socket
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return errorcode;
    }

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return errorcode;
    }

    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return errorcode;
    }

    // No longer need server socket
    closesocket(ListenSocket);

    // Receive until the peer shuts down the connection
    do {

        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

            // Echo the buffer back to the sender
            iSendResult = send(ClientSocket, recvbuf, iResult, 0);
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return errorcode;
            }
            printf("Bytes sent: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Connection closing...\n");
        else {
            printf("recv failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return errorcode;
        }

    } while (iResult > 0);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return errorcode;
    }

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();
    for (i = strlen(recvbuf) - 1; i >= 0; i--)
    {
        strncpy(out, recvbuf, i + 1);
        out[i + 1] = '\0';
        break;
    }
    printf("receive end\n");
    return out;
}
stdafx.cpp
#include "stdafx.h"
stdafx.h
#pragma once
// VisualStudioの警告を切る
#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <string.h>



// getCharTCP向けのインクルード
#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

  • クライアントプログラム
main.cpp
#include "stdafx.h"


int main(int argc, char **argv)
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL,
        *ptr = NULL,
        hints;
    char sendbuf[DEFAULT_BUFLEN];
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    printf("please write letters to control program :\n");
    fgets(sendbuf, DEFAULT_BUFLEN, stdin);

    // Validate the parameters
    if (argc != 2) {
        printf("usage: %s server-name\n", argv[0]);
        return 1;
    }

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Attempt to connect to an address until one succeeds
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    // Send an initial buffer
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    printf("Bytes Sent: %ld\n", iResult);

    // shutdown the connection since no more data will be sent
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // Receive until the peer closes the connection
    do {

        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("Bytes received: %d\n", iResult);
        else if (iResult == 0)
            printf("Connection closed\n");
        else
            printf("recv failed with error: %d\n", WSAGetLastError());

    } while (iResult > 0);

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}
stdafx.h
#pragma once

#include <stdio.h>

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>


// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
  • Pythonでの実行

(Pythonと同じフォルダにコンパイル済みのサーバのプログラムとクライアントのプログラムを置いています)

control.py
import subprocess as sp

if __name__ == "__main__":

    # サーバ側のexeファイルを立ち上げる
    # このファイルがSDKにあたる
    cmd = ".\\server.exe"
    pserve = sp.Popen(cmd)

    # クライアント側のプログラム
    cmd = ".\\client.exe localhost"
    pclient = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE)
    out, err = pclient.communicate(input=b"Hello World 1st\n")
    pclient.kill()
    # 命令を送るたびにインスタンス立ち上げが必要
    # もっと良い実装はある気はする
    cmd = ".\\client.exe localhost"
    pclient = sp.Popen(cmd, stdin=sp.PIPE, stdout=sp.PIPE)
    out, err = pclient.communicate(input=b"Hello World 2nd\n")
    pclient.kill()
    exit()

4. 雑感

Pythonのモジュールですぐ対応できるだろうと思って始めたのですが,思いのほかハマってしまった事例だったので書き起こしてみました.もっと良い解決法がある気もしているので(PIPEでのプロセス間通信にするetc.),機会を見てもっと調べるかもしれません.
しかしTCP通信の実装を行ったので,他のPCからも実行できるようになったのが副産物としてよかった部分かなと思います.Pythonのsocketモジュールを使えばまた良い制御方法があるかもしれません.

a1. 環境

OS:Win10
IDE:Visual Studio 2017 (C, PythonともVS2017で作成しました)
Python : Anaconda 5.0.1

a2. 参考サイトのURL

Python公式:subprocess
関数で文字列を返したい場合の注意点
Winsock2実装例(TCP通信):Getting Started with Winsock2

18
23
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
23