You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell延迟函数经FFI多次调用时状态未保留的问题

Haskell FFI 延迟函数跨调用状态保留修复

问题根源

你当前的delay函数每次被调用时,都会通过unsafePerformIO重新初始化环形缓冲区和索引的IORef——也就是说,StablePtr保存的delay 2只是一个"工厂函数",每次调用它都会生成一套全新的状态,而非复用之前的缓冲数据。这就是第二次调用开头输出仍为0的核心原因。

正确的做法是:让延迟器先完成状态初始化,然后返回一个携带持久状态的闭包,将这个闭包存入StablePtr,这样跨FFI调用时就能复用同一个缓冲区。

修正后的代码

Haskell 核心逻辑与 FFI 导出

import Data.IORef
import qualified Data.StorableVector as V
import qualified Data.Vector.Storable.Mutable as MV
import Foreign.StablePtr
import Foreign.Ptr

-- 定义带状态的处理函数类型:输入向量 -> 输出向量
type Process = V.Vector Float -> V.Vector Float

-- 初始化延迟器,返回带状态的闭包
mkDelay :: Int -> IO Process
mkDelay d = do
    buf <- MV.replicate d 0.0  -- 初始化环形缓冲区
    inx <- newIORef 0          -- 初始化索引指针
    return $ \inp -> V.map (processSample buf inx) inp
  where
    processSample :: MV.IOVector Float -> IORef Int -> Float -> Float
    processSample buf inx v = unsafePerformIO $ do
        i <- readIORef inx
        r <- MV.unsafeExchange buf i v  -- 交换操作:取出旧值,存入新输入值
        writeIORef inx ((i + 1) `mod` d)
        return r

-- FFI 导出:创建带状态的延迟器实例
foreign export ccall 
startCtx :: IO (StablePtr Process)
startCtx = newStablePtr =<< mkDelay 2

-- FFI 导出:释放资源
foreign export ccall 
freeCtx :: StablePtr Process -> IO ()
freeCtx = freeStablePtr

-- FFI 导出:处理向量的入口
foreign export ccall 
hs_process :: StablePtr Process -> Int -> Ptr Float -> Ptr Float -> IO ()
hs_process pf ns i o = do
    processFn <- deRefStablePtr pf
    inputVec <- V.peek ns i
    let outputVec = processFn inputVec
    V.poke o outputVec

C 端代码(无需修改)

#include "Process_stub.h"
#include <vector>
#include <cstdio>

using namespace std;

extern "C" {
    void HsStart();
    void HsEnd();
}

vector<float> input1 = {1.0, 2.0, 3.0, 4.0, 5.0},
              input2 = {6.0, 7.0, 8.0, 9.0, 10.0},
              output(input1.size(), 0.0);

int main(int argc, char *argv[])
{   
    HsStart();

    auto pf = startCtx();

    hs_process(pf, input1.size(), input1.data(), output.data());
    printf("First call of hs_process:\n");
    for(int i = 0; i < input1.size(); i++)
        printf("[%d] input = %f | output = %f\n", i, input1[i], output[i]);

    hs_process(pf, input2.size(), input2.data(), output.data());
    printf("\nSecond call of hs_process:\n");
    for(int i = 0; i < input2.size(); i++)
        printf("[%d] input = %f | output = %f\n", i, input2[i], output[i]);

    freeCtx(pf);
    
    HsEnd();

    return 0;
}

关键改动说明

  1. 状态初始化与闭包分离:把原来delay函数里的状态初始化(缓冲区、索引)移到mkDelay的IO动作中,只执行一次;返回的闭包会捕获buf和inx,确保每次调用处理函数都复用同一个状态。
  2. StablePtr 保存闭包实例:startCtx现在调用mkDelay 2得到带状态的闭包,再把这个闭包存入StablePtr,而非保存delay 2这个工厂函数。
  3. 移除冗余的IORef包裹缓冲区:原来的代码用IORef包裹可变向量,这是多余的——可变向量本身就是带状态的,直接捕获即可。

验证预期输出

修正后运行代码,会得到预期的结果:

First call of hs_process:
[0] input = 1.000000 | output = 0.000000
[1] input = 2.000000 | output = 0.000000
[2] input = 3.000000 | output = 1.000000
[3] input = 4.000000 | output = 2.000000
[4] input = 5.000000 | output = 3.000000

Second call of hs_process:
[0] input = 6.000000 | output = 4.000000
[1] input = 7.000000 | output = 5.000000
[2] input = 8.000000 | output = 6.000000
[3] input = 9.000000 | output = 7.000000
[4] input = 10.000000 | output = 8.000000

内容的提问来源于stack exchange,提问作者murf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 10:23:20