FFI 返回一个巨大的整数值而不是 0

Sté*_*ent 4 c++ haskell haskell-ffi

我已将“NumericalIntegration”C++ 库封装在 Haskell 中。是该软件包的最新版本(Hackage 上的版本较旧)。

这是 C++ 代码的主要部分:

class Integrand {
 private:
  std::function<double(double)> f;

 public:
  Integrand(std::function<double(double)>& f_) : f(f_) {}
  double operator()(const double x) const { return f(x); }
};

double integration(double f(double),
                   double lower,
                   double upper,
                   double relError,
                   int subdiv,
                   double* errorEstimate,
                   int* errorCode) {
  // Define the integrand.
  std::function<double(double)> f_ = [&](double x) { return f(x); };
  Integrand integrand(f_);
  // Define the integrator.
  Eigen::Integrator<double> integrator(subdiv);
  // Define a quadrature rule.
  Eigen::Integrator<double>::QuadratureRule rule =
      Eigen::Integrator<double>::GaussKronrod201;
  // Define the desired absolute error.
  double absError = 0.0;
  // Integrate.
  double result = integrator.quadratureAdaptive(integrand, lower, upper,
                                                absError, relError, rule);
  *errorEstimate = integrator.estimatedError();
  *errorCode = integrator.errorCode();
  return result;
}
Run Code Online (Sandbox Code Playgroud)

这是 Haskell 代码的主要部分:

foreign import ccall safe "wrapper" funPtr
    :: (Double -> Double) -> IO (FunPtr (Double -> Double))

foreign import ccall safe "integration" c_integration
    :: FunPtr (Double -> Double) -> Double -> Double -> Double -> Int
    -> Ptr Double -> Ptr Int -> IO Double

-- | Numerical integration.
integration :: (Double -> Double)       -- ^ integrand
            -> Double                   -- ^ lower bound
            -> Double                   -- ^ upper bound
            -> Double                   -- ^ desired relative error
            -> Int                      -- ^ number of subdivisions
            -> IO IntegralResult        -- ^ value, error estimate, error code
integration f lower upper relError subdiv = do
  errorEstimatePtr <- mallocBytes (sizeOf (0 :: Double))
  errorCodePtr <- mallocBytes (sizeOf (0 :: Int))
  fPtr <- funPtr f
  result <-
    c_integration fPtr lower upper relError subdiv errorEstimatePtr errorCodePtr
  errorEstimate <- peek errorEstimatePtr
  errorCode <- peek errorCodePtr
  let out = IntegralResult {_value = result, _error = errorEstimate, _code = errorCode}
  free errorEstimatePtr
  free errorCodePtr
  freeHaskellFunPtr fPtr
  return out
Run Code Online (Sandbox Code Playgroud)

这可行,但存在有关集成错误代码的问题。当集成正常时,错误代码应该是 0。有时它是 0,正如预期的那样。但有时它是一个巨大的整数,毫无意义,尽管积分很好。

您对这个问题有什么想法吗?为什么会出现这个奇怪的错误代码?我的代码中有什么不好的地方吗?我不太精通 C++(也不精通 Haskell)。但除了这个奇怪的错误代码之外,该库似乎运行得很好。

K. *_*uhr 7

在该x86_64平台上,HaskellInt是 64 位,而 Cint是 32 位。(AClong是 64 位。)在您的代码中,您会在高字节中拾取垃圾,并得到一个大得离谱的 64 位整数,毫无疑问,其最低 32 位为零。

无论如何,有一个Foreign.C包含 newtypes CIntCDouble等的模块,旨在匹配目标平台上相应的 C 类型,我认为始终使用这些被认为是良好的做法:

import Foreign
import Foreign.C

foreign import ccall safe "wrapper" funPtr
    :: (CDouble -> CDouble) -> IO (FunPtr (CDouble -> CDouble))

foreign import ccall safe "integration" c_integration
    :: FunPtr (CDouble -> CDouble) -> CDouble -> CDouble -> CDouble -> CInt
    -> Ptr CDouble -> Ptr CInt -> IO CDouble
Run Code Online (Sandbox Code Playgroud)

当然,由于这些是新类型,因此存在包装和展开的麻烦,尽管通常fromIntegral会自动处理这一点,同时也会跨位大小进行转换:

errorCode <- fromIntegral <$> peek errorCodePtr
Run Code Online (Sandbox Code Playgroud)

但是,作为一种不太可移植的替代方案,您可以在声明中坚持使用Double和,前提是您仅针对 C为 32 位的平台。Int32foreign importint

另请注意,如果类型正确,那么malloc是 的类型安全替代方案mallocBytes

errorCodePtr <- malloc
Run Code Online (Sandbox Code Playgroud)

的类型Ptr决定了此处分配的正确字节数。