2014-11-22 21 views
3

我正在Rcpp中開發一個構建霍夫曼樹的簡單庫。它有一個我可以從其他包調用的工作R接口,但我也想直接從我正在開發的其他基於Rcpp的包中的C++代碼調用C++函數。如何在R包之間共享基於Rcpp的庫中的C++函數?

我已經想出瞭如何將第一個包的標頭放在inst/include目錄中,以便它在第二個包中可用。但是,如果在第二個包的NAMESPACE文件中調用useDynLib以加載其調用第一個包中的函數的C++代碼,則會爲我嘗試使用的函數收到未定義的符號錯誤。我在Import,DependsLinkingTo的第二個文件包的DESCRIPTION文件中列出了第一個軟件包。

這是我第一次嘗試做任何非基於R的軟件包,我通過Rstudio的「Build & Reload」命令完成了我的所有開發,並在創建軟件包時使用了「Package w/Rcpp」選項生成初始目錄結構。

+2

這裏有一些信息在http://r-pkgs.had.co.nz/src.html – hadley 2014-11-24 21:39:50

回答

2

是的,鏈接步驟更難但仍然可行。

看起來例如,瞭解RcppXts軟件包如何導入xts軟件包導出的符號。這是非常乏味的。

我認爲凱文在他的Kmisc軟件包中有一些必要的註冊步驟的幫助。我一直有意要閱讀那些,但並不需要他們/還有時間。

+0

感謝您的指點;我將整理出一個簡短的例子。另外,感謝Rcpp。 :D – bskaggs 2014-11-22 19:11:08

+0

在這種情況下,'Kmisc'只是自動註冊'本地例程',例如[這裏](http://cran.r-project.org/doc/manuals/r-release/R-exts.html#Registering-native-routines)。但是我最近發現Hadley只是在'NAMESPACE'中寫'useDynLib(,,)'實際上等價於... – 2014-11-22 19:52:56

+0

Portably?這將是一個_huge_幫助。 – 2014-11-22 20:31:08

4

在R中執行此操作的一般機制是通過R_RegisterCCallableR_GetCCallable使函數指針可用。一個例子見R-exts

這意味着符號是根據需要動態解析的 - 您實際上並不需要「鏈接」到其他程序包本身;您只需要標題,以便在代碼執行後可以在日後正確解析符號。請注意,LinkingTo:字段實際上是一個用詞不當 - 它只是給你的標題,它實際上並不是鏈接你到(爲該庫生成的)包。

幸運的是,這可以與Rcpp::interfaces屬性,在RcppExports.cpp基本上自動生成R_RegisterCCallable入口點,並提供所產生的報頭文件中使用R_GetCCallable包裝函數實現自動化。

例如,假設我有一個傻包稱爲RcppInterfaces,含有這種在src/test.cpp(用Includes:LinkingTo:具有RcppDESCRIPTION)。請注意0​​註釋,這表示Rcpp該文件應該同時獲得R導出和C++頭導出。

// [[Rcpp::interfaces(r, cpp)]] 

#include <Rcpp.h> 

// [[Rcpp::export]] 
void hello() { 
    Rcpp::Rcout << "Hello!\n"; 
} 

如果我打電話Rcpp::compileAttributes(),你會看到寫到RcppExports.cpp以下「東西」:

// This file was generated by Rcpp::compileAttributes 
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 

#include <Rcpp.h> 
#include <string> 
#include <set> 

using namespace Rcpp; 

// hello 
void hello(); 
static SEXP RcppInterfaces_hello_try() { 
BEGIN_RCPP 
    { 
     hello(); 
    } 
    return R_NilValue; 
END_RCPP_RETURN_ERROR 
} 
RcppExport SEXP RcppInterfaces_hello() { 
    SEXP __result; 
    { 
     Rcpp::RNGScope __rngScope; 
     __result = PROTECT(RcppInterfaces_hello_try()); 
    } 
    Rboolean __isInterrupt = Rf_inherits(__result, "interrupted-error"); 
    if (__isInterrupt) { 
     UNPROTECT(1); 
     Rf_onintr(); 
    } 
    Rboolean __isError = Rf_inherits(__result, "try-error"); 
    if (__isError) { 
     SEXP __msgSEXP = Rf_asChar(__result); 
     UNPROTECT(1); 
     Rf_error(CHAR(__msgSEXP)); 
    } 
    UNPROTECT(1); 
    return __result; 
} 

// validate (ensure exported C++ functions exist before calling them) 
static int RcppInterfaces_RcppExport_validate(const char* sig) { 
    static std::set<std::string> signatures; 
    if (signatures.empty()) { 
     signatures.insert("void(*hello)()"); 
    } 
    return signatures.find(sig) != signatures.end(); 
} 

// registerCCallable (register entry points for exported C++ functions) 
RcppExport SEXP RcppInterfaces_RcppExport_registerCCallable() { 
    R_RegisterCCallable("RcppInterfaces", "RcppInterfaces_hello", (DL_FUNC)RcppInterfaces_hello_try); 
    R_RegisterCCallable("RcppInterfaces", "RcppInterfaces_RcppExport_validate", (DL_FUNC)RcppInterfaces_RcppExport_validate); 
    return R_NilValue; 
} 

注意,最早期的東西是樣板,確保的異常安全版本該函數被調用;最後你基本上有了爲其他軟件包註冊可調用函數的機制。在inst/include/RcppInterfaces_RcppExports.h,我們有:

// This file was generated by Rcpp::compileAttributes 
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 

#ifndef __RcppInterfaces_RcppExports_h__ 
#define __RcppInterfaces_RcppExports_h__ 

#include <Rcpp.h> 

namespace RcppInterfaces { 

    using namespace Rcpp; 

    namespace { 
     void validateSignature(const char* sig) { 
      Rcpp::Function require = Rcpp::Environment::base_env()["require"]; 
      require("RcppInterfaces", Rcpp::Named("quietly") = true); 
      typedef int(*Ptr_validate)(const char*); 
      static Ptr_validate p_validate = (Ptr_validate) 
       R_GetCCallable("RcppInterfaces", "RcppInterfaces_RcppExport_validate"); 
      if (!p_validate(sig)) { 
       throw Rcpp::function_not_exported(
        "C++ function with signature '" + std::string(sig) + "' not found in RcppInterfaces"); 
      } 
     } 
    } 

    inline void hello() { 
     typedef SEXP(*Ptr_hello)(); 
     static Ptr_hello p_hello = NULL; 
     if (p_hello == NULL) { 
      validateSignature("void(*hello)()"); 
      p_hello = (Ptr_hello)R_GetCCallable("RcppInterfaces", "RcppInterfaces_hello"); 
     } 
     RObject __result; 
     { 
      RNGScope __rngScope; 
      __result = p_hello(); 
     } 
     if (__result.inherits("interrupted-error")) 
      throw Rcpp::internal::InterruptedException(); 
     if (__result.inherits("try-error")) 
      throw Rcpp::exception(as<std::string>(__result).c_str()); 
     return Rcpp::as<void >(__result); 
    } 

} 

#endif // __RcppInterfaces_RcppExports_h__ 

這是一些異常安全的樣板,但有趣的部分是在R_GetCCallable呼叫,允許其他包的作者爲「只使用」該功能,與R_GetCCallable東西,內聯和直接在函數調用中進行管理(使用靜態指針,必要時可以填充一次)。

所以,就因爲這RcppInterfaces包的用戶而言,他們可以叫

RcppInterfaces::hello() 

在他們的代碼,我們只是自動確保函數指針擡起頭來,用(安全!)在運行時,使用R自己的機制。

1

來自Kevin的關於R_RegisterCCallableR_GetCCallable的很好的描述。就我個人而言,我認爲所有的代碼都可以被包或頭文件中的其他包使用。這是國際海事組織不那麼脆弱。

相關問題