【问题标题】:How to pass (rapidly updated) variable from ctypes C code to Python? Pass by reference?如何将(快速更新的)变量从 ctypes C 代码传递到 Python?通过引用传递?
【发布时间】:2019-05-18 09:14:53
【问题描述】:

我正在尝试开发将编译为 DLL(或 .SO 用于 linux)的 C 代码,目的是高速处理一些外部输入并将结果返回给 Python 以进行后续处理。

我的问题是:从 C 函数中定期返回值(每秒超过 1000 秒)以在 Python 中使用的最佳方法是什么?

我创建了一个测试用例如下:

//dummy_function.c
#include <stdio.h>
#include <Windows.h>

__declspec(dllexport) int runme() {
    int i;
    for (i=1; i<= 500; i++) {
        printf("int = %d \n", i);
        Sleep(100);  // Add some arbitrary delay for now
    }
}

注意,我导入 Windows.h 以使用虚拟时间延迟(模拟我的实际问题)。此代码可以使用 unistd.h 在 unix 上运行(根据:What is the proper #include for the function 'sleep' in C?

然后这段代码被编译成.dll

gcc -shared -o dummy_function.dll dummy_function.c

并通过以下方式在 Python 中导入:

import ctypes
libc = ctypes.CDLL('dummy_function.dll')
libc.runme()  # Start running

当执行这个 Python 代码时,它会打印出递增的整数。但是,使用 Python 捕获 C 的打印输出然后对其进行处理似乎不是一个好方法(/不能为高速扩展)。

相反,我想知道是否有一种方法可以更轻松地将变量从 DLL 传递给 Python。我想我不想在 C 中使用 return 函数,因为它会退出函数。

我读过 C 中的引用传递,所以想知道这是否与 ctypes 兼容。我可以定义一些 C 代码写入的内存位置,然后 Python 轮询这个吗?或者更好的是,某种形式的队列/缓冲区以避免丢失事件?

任何建议将不胜感激,谢谢!

【问题讨论】:

    标签: python c dll ctypes


    【解决方案1】:

    尽管您熟悉这些概念,但这里是[Python 3.Docs]: ctypes - A foreign function library for Python

    Python 代码在 C 代码运行时轮询内存区域,这意味着多个线程。
    这是一个例子。

    dll.c

    #include <stdio.h>
    #include <Windows.h>
    
    #if defined(_WIN32)
    #  define DLL_EXPORT __declspec(dllexport)
    #else
    #  define DLL_EXPORT
    #endif
    
    #define PRINT_MSG_2XI(ARG0, ARG1) printf("From C - [%s] (%d) - [%s]:  ARG0: 0x%016p, ARG1: %d\n", __FILE__, __LINE__, __FUNCTION__, ARG0, ARG1)
    
    
    typedef struct Srtruct_ {
        int value;
        int sentinel;
    } Struct;
    
    
    DLL_EXPORT int func0(Struct *ptr) {
        int counter = 0;
        if (!ptr) {
            return -1;
        }
        while (ptr->sentinel) {
            ptr->value++;
            counter++;
            PRINT_MSG_2XI(ptr, ptr->value);
            Sleep(200);
        }
        return counter;
    }
    

    code.py

    #!/usr/bin/env python3
    
    import sys
    import ctypes
    import time
    import threading
    
    
    DLL_NAME = "./dll.dll"
    
    
    class Struct(ctypes.Structure):
        _fields_ = [
            ("value", ctypes.c_int),
            ("sentinel", ctypes.c_int),
        ]
    
    
    def thread_func(func, arg0):
        ret = func(arg0)
        print("\nFrom Python - Function returned {:d}".format(ret))
    
    
    def main():
        dll = ctypes.CDLL(DLL_NAME)
        func0 = dll.func0
        func0.argtypes = [ctypes.POINTER(Struct)]
        func0.restype = ctypes.c_int
    
        data = Struct(30, 1)
        t = threading.Thread(target=thread_func, args=(func0, data,))
        t.start()
        time.sleep(1)
        print("From Python - Monitored value: {:d}".format(data.value))
        time.sleep(1)
        print("From Python - Monitored value: {:d}".format(data.value))
        data.sentinel = 0
        time.sleep(0.5)
    
    
    if __name__ == "__main__":
        print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
        main()
        print("\nDone.")
    

    输出

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q056197585]> sopr.bat
    *** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
    
    [prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
    **********************************************************************
    ** Visual Studio 2017 Developer Command Prompt v15.9.11
    ** Copyright (c) 2017 Microsoft Corporation
    **********************************************************************
    [vcvarsall.bat] Environment initialized for: 'x64'
    
    [prompt]> dir /b
    code.py
    dll.c
    
    [prompt]> cl /nologo /DDLL dll.c  /link /NOLOGO /DLL /OUT:dll.dll
    dll.c
       Creating library dll.lib and object dll.exp
    
    [prompt]> dir /b
    code.py
    dll.c
    dll.dll
    dll.exp
    dll.lib
    dll.obj
    
    [prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.py
    Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32
    
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 31
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 32
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 33
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 34
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 35
    From Python - Monitored value: 35
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 36
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 37
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 38
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 39
    From C - [dll.c] (27) - [func0]:  ARG0: 0x00000152EB84CE90, ARG1: 40
    From Python - Monitored value: 40
    
    From Python - Function returned 10
    
    Done.
    

    【讨论】:

    • 感谢这个很好的例子 - 它确实有效并且似乎可以满足我的要求。但是,我想知道您是否可以说出为什么需要线程,因为我不清楚?另外,有没有办法缓冲 C 输出,所以 Python 会捕获所有增量(即,而不是上面代码中的每 5 个)?我想将 Python 睡眠时间设置为较低的值会起作用,但有没有更优雅的解决方案?谢谢!
    • 不客气!正如我所说,如果您想在 C 函数运行时从 Python 获取数据,则不能在单个线程中执行此操作(因为两者都将在同时),否则你可以只返回值。其次,我根据问题提出了我的例子:通过内存区域“通信”的某种方式(结构的int value;,但你可以把任何你想要的东西放在那里) . C 中的 printf 语句仅用于演示目的,以明确 PythonC 中的数据是否同步(这根本不是关于将数据传输到 Python)。
    • 有道理,谢谢。我已经接受了答案。
    猜你喜欢
    • 2013-04-14
    • 1970-01-01
    • 1970-01-01
    • 2019-04-07
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    • 2010-11-02
    相关资源
    最近更新 更多