import{_ as a,c as l,o as s,a4 as i}from"./chunks/framework.Cy9JvDAg.js";const _=JSON.parse('{"title":"4.5 - 在C代码中处理让出","description":"","frontmatter":{"title":"4.5 - 在C代码中处理让出","footer":true,"lastUpdated":true,"outline":false},"headers":[],"relativePath":"4.5.md","filePath":"4.5.md"}'),n={name:"4.5.md"},h=i(`<h2 id="_4-5-在c代码中处理让出" tabindex="-1">4.5 - 在C代码中处理让出 <a class="header-anchor" href="#_4-5-在c代码中处理让出" aria-label="Permalink to &quot;4.5 - 在C代码中处理让出&quot;">​</a></h2><p>在内部，Lua使用C库函数longjump来让出协程。因此，如果有一个C函数foo调用了一个API函数且这个API函数让出了（直接或间接地由其他函数发起的让出），那么Lua不会再从foo中返回了，因为longjmp在C栈上移除了其栈帧。</p><p>为了避免此类问题，每当尝试通过API调用来让出时都会抛出错误，除非是这三个函数：<a href="/lua5.4-manual-zh/4.6.html#lua_yieldk">lua_yieldk</a>、<a href="/lua5.4-manual-zh/4.6.html#lua_callk">lua_callk</a>和<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>。这些函数会接收一个<em>延续函数 continuation function</em>（作为参数名k）以在让出后继续执行。</p><p>我们需要说到一些术语以解释延续。我们把从Lua调用的C函数称为<em>源函数 original function</em>。这个源函数中调用的上述这三种C API中的函数，我们称为<em>被调用方函数 callee function</em>，它们会让出当前Lua线程。这种情况会在callee函数为<a href="/lua5.4-manual-zh/4.6.html#lua_yieldk">lua_yieldk</a>时发生，或者callee函数是<a href="/lua5.4-manual-zh/4.6.html#lua_callk">lua_callk</a>或<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>且它们发生了让出时。</p><p>假设运行的Lua线程执行callee函数时让出了，那么在Lua线程重入后，其最终<em>源函数</em>应当完成运行。然而callee函数并不能返回源函数，因为其C堆栈上的栈帧已经因为让出而被销毁了。作为代替，Lua会调用<em>延续函数</em>，其通过callee函数的参数来给出。顾名思义，延续函数应当继续完成源函数的工作。</p><p>作为示例，请考量以下函数：</p><div class="language-C vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">C</span><pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> original_function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (lua_State </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">L</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">  /*... code 1 */</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">  status </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> lua_pcall</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(L, n, m, h);</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">  /* calls Lua */</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">  /*... code 2 */</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>现在我们想让Lua代码可以在<a href="/lua5.4-manual-zh/4.6.html#lua_pcall">lua_pcall</a>中运行时让出。首先我们可以重写我们的函数，像这样：</p><div class="language-C vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">C</span><pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> k</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (lua_State </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">L</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> status</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, lua_KContext </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">ctx</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">  /*... code 2 */</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> original_function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (lua_State </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">L</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">  /*... code 1 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">  return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> k</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(L, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">lua_pcall</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(L, n, m, h), ctx);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>在上边的代码中，新函数k为<em>延续函数</em>（类型为<a href="/lua5.4-manual-zh/4.6.html#lua_KFunction">lua_KFunction</a>），它会完成源函数在调用<a href="/lua5.4-manual-zh/4.6.html#lua_pcall">lua_pcall</a>后的所有工作。现在我们必须告知Lua代码被某些方式中断执行（错误或者让出）后应该调用k，所以我们需要重写代码，用<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>替换<a href="/lua5.4-manual-zh/4.6.html#lua_pcall">lua_pcall</a>，像这样：</p><div class="language-C vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">C</span><pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">int</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> original_function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (lua_State </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">*</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">L</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">  /* ... code 1 */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">  return</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> k</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(L, </span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">lua_pcallk</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(L, n, m, h, ctx2, k), ctx1);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>注意外层显式调用的延续函数k：Lua只会在需要的情况下调用延续函数，即因为错误或让出后的时候。如果被调用的函数并没有让出而是正常返回，<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>（以及<a href="/lua5.4-manual-zh/4.6.html#lua_callk">lua_callk</a>）也将会正常返回。（当然，这种情况下与其调用延续函数k，还不如把等效的工作直接放到源函数中完成。）</p><p>除了Lua状态机，延续函数还有两个其他参数：调用的最终状态码和上下文（ctx），其由原来的<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>传递而来。Lua并不使用这个上下文，它只是从源函数中传递给延续函数。对于<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>，状态应当和<a href="/lua5.4-manual-zh/4.6.html#lua_pcallk">lua_pcallk</a>本身的返回相同，除了执行让出后返回<a href="/lua5.4-manual-zh/4.4.1.html">LUA_YIELD</a>（而不是<a href="/lua5.4-manual-zh/4.4.1.html">LUA_OK</a>）。对于<a href="/lua5.4-manual-zh/4.6.html#lua_yieldk">lua_yieldk</a>和<a href="/lua5.4-manual-zh/4.6.html#lua_callk">lua_callk</a>，当Lua调用延续函数时它们的状态码永远是<a href="/lua5.4-manual-zh/4.4.1.html">LUA_YIELD</a>。（对于这两个函数，Lua将不会因为错误而调用延续函数，因为它们根本就不会处理错误。）同样，当使用<a href="/lua5.4-manual-zh/4.6.html#lua_callk">lua_callk</a>时，你应当将<a href="/lua5.4-manual-zh/4.4.1.html">LUA_OK</a>当作状态码来调用延续函数。（对于<a href="/lua5.4-manual-zh/4.6.html#lua_yieldk">lua_yieldk</a>，这里直接显式调用延续函数并没有太多意义，因为<a href="/lua5.4-manual-zh/4.6.html#lua_yieldk">lua_yieldk</a>通常不返回。）</p><p>Lua将延续函数和源函数作相同看待。延续函数同样接收来自源函数的Lua栈，如果callee函数已经返回了，那么还是在相同的Lua状态机内。（例如，在<a href="/lua5.4-manual-zh/4.6.html#lua_callk">lua_callk</a>之后，函数和其参数都从栈上移除了，取而代之的是调用结果。）其拥有同样的上值。无论如何，此返回都会被Lua作和源函数返回一样的处理。</p>`,14),t=[h];function e(p,k,u,c,r,d){return s(),l("div",null,t)}const m=a(n,[["render",e]]);export{_ as __pageData,m as default};
