以产量为例外的双向交互作用 我编写C++ DLL,它是元交易者4应用程序(MT4)和Python脚本(嵌入Python到MT4)之间的门。MT4将请求发送到此DLL并等待指令执行(例如,字符串命令数组)。DLL将MT4请求的解析传递给Python脚本。Python脚本需要从MT4获得一些信息来解析每个请求。因此,MT4和Python具有双向通信

以产量为例外的双向交互作用 我编写C++ DLL,它是元交易者4应用程序(MT4)和Python脚本(嵌入Python到MT4)之间的门。MT4将请求发送到此DLL并等待指令执行(例如,字符串命令数组)。DLL将MT4请求的解析传递给Python脚本。Python脚本需要从MT4获得一些信息来解析每个请求。因此,MT4和Python具有双向通信,python,dll,python-3.3,Python,Dll,Python 3.3,但MT4不支持双向通信,它只能解析自己的DLL请求的结果,并用新的参数对DLL进行新的调用。因此,我需要中断Python控制流,以便从Python向DLL(和MT4)临时返回部分结果,并等待来自MT4的新请求 如何以Python风格创建这个(丑陋的)双向交互?我需要一些功能性的、但仅在函数范围内的yield工作,当我需要类似yield的异常时:从调用堆栈的底部到顶部,并且能够通过调用顶部Python脚本中的main.next()将控制流返回到上一个屈服点 MT4伪代码: new_args = .

但MT4不支持双向通信,它只能解析自己的DLL请求的结果,并用新的参数对DLL进行新的调用。因此,我需要中断Python控制流,以便从Python向DLL(和MT4)临时返回部分结果,并等待来自MT4的新请求

如何以Python风格创建这个(丑陋的)双向交互?我需要一些功能性的、但仅在函数范围内的yield工作,当我需要类似yield的异常时:从调用堆栈的底部到顶部,并且能够通过调用顶部Python脚本中的main.next()将控制流返回到上一个屈服点

MT4伪代码:

new_args = ...
while (true) {
  cmds = DLL_GetCommands(new_args);
  if (! cmd) { // No commands from Python
    break;
  }
  new_args = _parseCommand(cmds);
}
char* __declspec(dllexport) DLL_GetCommands(char* args) {
    // Python already initialized.
    // Some python script already executed
    // and I have local scope of this execution.
    PyObject var = ...search some object variable in Python...
    // var is instance of Advert class
    return PyObject_CallMethodObjArgs(var, "parse_tick", args, NULL);
}
DLL伪代码:

new_args = ...
while (true) {
  cmds = DLL_GetCommands(new_args);
  if (! cmd) { // No commands from Python
    break;
  }
  new_args = _parseCommand(cmds);
}
char* __declspec(dllexport) DLL_GetCommands(char* args) {
    // Python already initialized.
    // Some python script already executed
    // and I have local scope of this execution.
    PyObject var = ...search some object variable in Python...
    // var is instance of Advert class
    return PyObject_CallMethodObjArgs(var, "parse_tick", args, NULL);
}
Python代码:

class Handler():
    def some_cpp_request(self, a, b):
        yield 'some_cpp_request'
        # After second call to Advert.parse_tick() control flow should return here.

class Advert():
    def __init__(self):
        self.h = Handler()

    # This method and all what it call should work as single generator
    def parse_tick(self):
        for i in range(2):
            self._some_method(i, i)

    def _some_method(self, a, b):
        self.h.some_cpp_request(a, b)

[更新]在收到abarnert的建议后,我有了工作解决方案:

class Handler():
    def __init__(self):
        self.cmds = []
        self.cmds_results = []

    def some_cpp_request(self, a, b):
        self.cmds.append(("SOME_CPP_REQUEST", a, b))
        yield
        # Here self.cmds_results contains MT4 response.

class Advert():
    def __init__(self):
        self.h = Handler()

    def parse_tick(self):
        for i in range(2):
            yield from self._some_method(i, i)

        return 'xxx'

    def _some_method(self, a, b):
        yield from self.h.some_cpp_request(a, b)

parser = Advert()
gen = parser.parse_tick()

# This loop should be written in DLL layer.
while True:
    next(gen)

    parser.h.cmds_results.clear()
    for cmd in parser.h.cmds:
        # Adding some results
        parser.h.cmds_results.append((cmd, 'SOME RESULT'))
    parser.h.cmds.clear()
p.S.更舒适的解决方案是创建并行线程(或进程),而不是调用和捕获收益率:这不需要替换所有收益率。两个线程可以通过两个阻塞队列进行通信。队列:

  • 子线程:
  • 将请求放入请求队列
  • 在响应队列中阻止等待新元素
  • 主线程:
  • 无限阻塞等待请求队列中的新元素
  • 将响应放入请求队列

如果请求为None,则为finishruntime:child和main threads中断无限循环。

如果要在Python代码中使用
yield
,只需执行以下操作:

def parse_tick(self):
    for i in range(2):
        yield self._some_method(i, i)
无论是从Python还是从C语言调用Python生成器函数,都会得到一个迭代器。每次从迭代器获得
next
值时,它都会在最后一个
yield
点之后重新激活生成器函数

而且很容易。下面是一些伪代码(如您现有的伪代码、跳过错误处理、重新计数等):


例外情况如何?没问题。如果从Python中迭代的生成器函数引发异常,
next
会向调用方引发该异常。如果用C进行迭代,那么
PyIter\u Next
返回与完成迭代器相同的
NULL
,那么如何区分它们呢?通过检查


或者,您可以将回调函数传递到Python代码中,Python代码使用每个值调用该回调,而不是
yield
ing每个值。这就是嵌入Python的应用程序的传统做法。但是,如果你已经在用发电机思考问题,你就不需要回到过去的方式


首先,听起来您想重新调整异常的用途,以便
raise
ing一个异常自动地像
yield
一样,可以从中恢复。那不行。非生成器函数的函数无法恢复,句号。即使是发电机功能,也只能在
收益
后恢复,而不能在
提高
(或
返回
)后恢复。这是没有办法的;它将从根本上改变
raise
(和
return
)的语义

所以,你必须把你想要驱动的低级函数,像发电机一样,转换成实际的发电机。(或者将它们分解为更小的函数并按顺序调用,或者将它们转换为保持显式状态并继续执行
\uuuuu call\uuuuu
的对象,或者其他比惯用方式更费事的操作。)


接下来,听起来您希望隐式嵌套生成器。Python生成器不是这样工作的。如果调用生成器函数,则返回迭代器。即使你自己也是一个生成函数,它也不能代表你产生值。如果要嵌套,必须使其显式,如下所示:

def _some_method(self, a, b):
    yield a
    yield b

def parse_tick(self):
    for i in range(2):
        yield from self._some_method(i, i)
请注意,调用方(无论是C还是Python)不必知道
parse\u tick
实际上是委托给其他生成器来完成其工作,或者
\u某些方法
挂起在某个地方。它只要求
parse_tick
查找下一个值,它看到的只是返回
0
,然后下一次
0
,然后
1
,然后完成

和<代码> PARSEGITICK 不必记住,它有一个挂起的< < >代码>方法> <代码>,因为它被悬挂在<代码>的中间。下次恢复时,它将自动恢复

\u some\u method
调用,或者,如果调用已用尽,则继续执行下一行代码

试图解释这种设计背后的基本原理,以及如何使用它,如果我没有说清楚的话。格雷格·尤因还使用了来自的
收益率。即使我的解释能力只有他一半,我也不可能像他那样在一个如此简单的答案中解释得那么多


或者,您可以在生成器上使用
send
方法。如果仔细观察,
yield
yield from
是表达式,带有值,而不是语句。如果通过调用
next
来驱动生成器,则表达式的值仅为
None
,这不是很有用。但是,通过在生成器上调用
send
方法,您不仅可以要求它恢复,还可以给它一个值以恢复。(如果需要,您还可以调用
throw
向生成器中引发异常。)这可以让您根据自顶向下的协程编写控制流,我认为