24

I want to call a Python script from C, passing some arguments that are needed in the script.

The script I want to use is mrsync, or multicast remote sync. I got this working from command line, by calling:

python mrsync.py -m /tmp/targets.list -s /tmp/sourcedata -t /tmp/targetdata

-m is the list containing the target ip-addresses. -s is the directory that contains the files to be synced. -t is the directory on the target machines where the files will be put.

So far I managed to run a Python script without parameters, by using the following C program:

Py_Initialize();
FILE* file = fopen("/tmp/myfile.py", "r");
PyRun_SimpleFile(file, "/tmp/myfile.py");
Py_Finalize();

This works fine. However, I can't find how I can pass these argument to the PyRun_SimpleFile(..) method.

Ben
  • 51,770
  • 36
  • 127
  • 149
Bram W.
  • 1,587
  • 4
  • 16
  • 39

2 Answers2

42

Seems like you're looking for an answer using the python development APIs from Python.h. Here's an example for you that should work:

#My python script called mypy.py
import sys

if len(sys.argv) != 2:
  sys.exit("Not enough args")
ca_one = str(sys.argv[1])
ca_two = str(sys.argv[2])

print "My command line args are " + ca_one + " and " + ca_two

And then the C code to pass these args:

//My code file
#include <stdio.h>
#include <python2.7/Python.h>

void main()
{
    FILE* file;
    int argc;
    char * argv[3];

    argc = 3;
    argv[0] = "mypy.py";
    argv[1] = "-m";
    argv[2] = "/tmp/targets.list";

    Py_SetProgramName(argv[0]);
    Py_Initialize();
    PySys_SetArgv(argc, argv);
    file = fopen("mypy.py","r");
    PyRun_SimpleFile(file, "mypy.py");
    Py_Finalize();

    return;
}

If you can pass the arguments into your C function this task becomes even easier:

void main(int argc, char *argv[])
{
    FILE* file;

    Py_SetProgramName(argv[0]);
    Py_Initialize();
    PySys_SetArgv(argc, argv);
    file = fopen("mypy.py","r");
    PyRun_SimpleFile(file, "mypy.py");
    Py_Finalize();

    return;
}

You can just pass those straight through. Now my solutions only used 2 command line args for the sake of time, but you can use the same concept for all 6 that you need to pass... and of course there's cleaner ways to capture the args on the python side too, but that's just the basic idea.

Hope it helps!

Mike
  • 47,263
  • 29
  • 113
  • 177
  • Note that in Python 3 you'll have to first convert to wide chars (wchar_t). Try mbstowcs_s (#include ) for this. – gkimsey Jul 11 '13 at 15:12
  • Hi @Mike, could you please have a look at this situation, where I need to pass the arguments to a python package app (not just a py file): http://stackoverflow.com/questions/21036106/pass-command-line-arguments-to-python-2-7-6-package-application-using-c-api – Jay Zhao Jan 12 '14 at 09:41
  • @JayZ - Sorry, just got back on today, looks like you got your answer at the other link, but if you need more help let me know. – Mike Jan 13 '14 at 17:22
  • I revisited this example - while CPython seems to use `argc` as well, I'd still use a `NULL` pointer at the end of self-constructed `argv`, as is in the standard C `argv`. – Antti Haapala -- Слава Україні Apr 20 '16 at 06:06
  • @Mike where to put the '-m' option ? how to set flags in Python/C api ? – Shoyeb Sheikh Apr 08 '19 at 12:58
9

You have two options.

  1. Call

    system("python mrsync.py -m /tmp/targets.list -s /tmp/sourcedata -t /tmp/targetdata")
    

    in your C code.

  2. Actually use the API that mrsync (hopefully) defines. This is more flexible, but much more complicated. The first step would be to work out how you would perform the above operation as a Python function call. If mrsync has been written nicely, there will be a function mrsync.sync (say) that you call as

    mrsync.sync("/tmp/targets.list", "/tmp/sourcedata", "/tmp/targetdata")
    

    Once you've worked out how to do that, you can call the function directly from the C code using the Python API.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • +1 Your first method is working indeed, thank you for that. However, I'm still curious to know if it can't be achieved with the Python Interpreter that I described in my first post. Your second method will unfortunately not work in this situation, because mrsync doesn't provide an API. – Bram W. Aug 27 '12 at 14:36
  • Actually, it might be possible to initialise the interpreter with argv and then have it exec a file. You'd have to try it and see, though, I don't know. – Katriel Aug 27 '12 at 14:53
  • Option 1 only working with the assumption that "python" is guaranteed to be in the system path – Mike Aug 29 '12 at 19:55
  • Using the system function gets always a downvote from me. The string provided to system is subject to shell interpretation and thus the function is quite dangerous. The proper way to execute commands from C in POSIX systems are the exec* functions. Thus: execlp("python", "python", "mrsync.py", "-m", ..., "/tmp/targetdata", NULL); If you want to link against a specific python interpreter, you can use Mike's answer. Please note however that if you update the python your application will break much easier, which necessarily is not the case when using just exec. – Antti Haapala -- Слава Україні Sep 04 '12 at 09:46