草庐IT

c++ - Cython 代码可以编译成 dll 以便 C++ 应用程序可以调用它吗?

coder 2024-02-12 原文

我有一个 C++ 程序,它有一种插件结构:当程序启动时,它会在插件文件夹中寻找具有某些导出函数签名的 dll,例如:

void InitPlugin(FuncTable* funcTable);

然后程序会调用dll中的函数进行初始化,并将函数指针传递给dll。从那时起,dll 就可以与程序对话了。

我知道 Cython 允许您在 Python 中调用 C 函数,但我不确定我是否可以编写 Cython 代码并将其编译为 dll,以便我的 C++ 程序可以使用它进行初始化。示例代码会很棒。

最佳答案

在 dll 中使用 cython-module 与 using a cython-module in an embeded python interpreter 没有什么不同.

第一步是标记cdef - 应该从外部 C 代码使用的函数 public ,例如:

#cyfun.pyx:

#doesn't need python interpreter
cdef public int double_me(int me):
    return 2*me;
        
#needs initialized python interpreter
cdef public void print_me(int me):
    print("I'm", me);

cyfun.ccyfun.h可以生成

cython -3 cyfun.pyx

这些文件将用于构建 dll。

dll 需要一个函数来初始化 python 解释器,另一个函数来完成它,这应该在 double_me 之前只调用一次。和 print_me可以使用(好的,double_me 也可以在没有解释器的情况下工作,但这是一个实现细节)。注意:初始化/清理也可以放在 DllMain 中- 在下面进一步查看这样的版本。

dll 的头文件如下所示:

//cyfun_dll.h
#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

//return 0 if everything ok
DLL_PUBLIC int cyfun_init();
DLL_PUBLIC void cyfun_finalize();

DLL_PUBLIC int cyfun_double_me(int me);
DLL_PUBLIC void cyfun_print_me(int me);

所以有必要的 init/finalize 函数,符号通过 DLL_PUBLIC 导出(这需要完成,请参阅 SO-post)以便它可以在 dll 外部使用。

实现如下 cyfun_dll.c -文件:

//cyfun_dll.c
#define BUILDING_DLL
#include "cyfun_dll.h"

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "cyfun.h"

DLL_PUBLIC int cyfun_init(){
  int status=PyImport_AppendInittab("cyfun", PyInit_cyfun);
  if(status==-1){
    return -1;//error
  } 
  Py_Initialize();
  PyObject *module = PyImport_ImportModule("cyfun");

  if(module==NULL){
     Py_Finalize();
     return -1;//error
  }
  return 0;   
}


DLL_PUBLIC void cyfun_finalize(){
   Py_Finalize();
}

DLL_PUBLIC int cyfun_double_me(int me){
    return double_me(me);
}

DLL_PUBLIC void cyfun_print_me(int me){
    print_me(me);
}

值得注意的细节:

  1. 我们定义BUILDING_DLL所以DLL_PUBLIC变成 __declspec(dllexport) .
  2. 我们使用 cyfun.h由 cython 从 cyfun.pyx 生成.
  3. cyfun_init初始化 python 解释器并导入内置模块 cyfun .有点复杂的代码是因为从 Cython-0.29 开始,PEP-489是默认的。更多信息可以在 this SO-post 中找到.如果 Python 解释器未初始化或模块 cyfun未导入,很有可能从 cyfun.h 调用功能将以段错误结束。
  4. cyfun_double_me只是包装 double_me所以它在 dll 之外变得可见。

现在我们可以构建 dll 了!

:: set up tool chain
call "<path_to_vcvarsall>\vcvarsall.bat" x64

:: build cyfun.c generated by cython
cl  /Tccyfun.c /Focyfun.obj /c <other_coptions> -I<path_to_python_include> 

:: build dll-wrapper
cl  /Tccyfun_dll.c /Focyfun_dll.obj /c <other_coptions> -I<path_to_python_include>

:: link both obj-files into a dll
link  cyfun.obj cyfun_dll.obj /OUT:cyfun.dll /IMPLIB:cyfun.lib /DLL <other_loptions> -L<path_to_python_dll>

dll 现已构建,但以下细节值得注意:

  1. <other_coptions><other_loptions> can vary from installation to installation. An easy way is to see them is to run cythonize some_file.pyx` 并检查日志。
  2. 我们不需要传递 python-dll,因为它将是 linked automatically , 但我们需要设置正确的库路径。
  3. 我们对 python-dll 有依赖性,因此稍后必须在可以找到它的地方。

你从这里去取决于你的任务,我们用一个简单的 main 测试我们的 dll:

//test.c
#include "cyfun_dll.h"

int main(){
   if(0!=cyfun_init()){
      return -1;
   }
   cyfun_print_me(cyfun_double_me(2));
   cyfun_finalize();
   return 0;
}

可以通过

构建
...
:: build main-program
cl  /Tctest.c /Focytest.obj /c <other_coptions> -I<path_to_python_include>

:: link the exe
link test.obj cyfun.lib /OUT:test_prog.exe <other_loptions> -L<path_to_python_dll>

现在调用 test_prog.exe导致预期的输出“我是 4”。

根据您的安装,必须考虑以下事项:

  • test_prog.exe取决于 pythonX.Y.dll它应该在路径中的某处以便可以找到(最简单的方法是将其复制到 exe 旁边)
  • 嵌入式 python 解释器需要安装,参见 this和/或 this SO 帖子。

IIRC,初始化,然后完成然后再次初始化 Python 解释器不是一个好主意(这可能适用于某些场景,但不是全部,例如参见 this)- 解释器应该是仅初始化一次并保持事件状态直到程序结束。

因此,将初始化/清理代码放入 DllMain 可能是有意义的(并将 cyfun_init()cyfun_finalize() 设为私有(private)),例如

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved )  // reserved
{
    // Perform actions based on the reason for calling.
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
            return cyfun_init()==0;

        case DLL_PROCESS_DETACH:
            cyfun_finalize();
            break;
        case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
            break;

        case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
            break;   
    }
    return TRUE;
}

如果你的 C/C++ 程序已经有一个初始化的 Python 解释器,那么提供一个只导入模块 cyfun 的函数是有意义的。并且不初始化 python 解释器。在这种情况下,我将定义 CYTHON_PEP489_MULTI_PHASE_INIT=0 ,因为 PyImport_AppendInittab必须在 Py_Initialize 之前调用,加载 dll 时可能已经太晚了。

关于c++ - Cython 代码可以编译成 dll 以便 C++ 应用程序可以调用它吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18515275/

有关c++ - Cython 代码可以编译成 dll 以便 C++ 应用程序可以调用它吗?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  3. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  4. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  5. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  6. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  7. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  8. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  9. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  10. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

随机推荐