DLL Compiled By All Language
DLL Compiled By All Language
目录
三种方法
真的是都太麻烦了
参考:https://www.zhihu.com/question/604029921
将Python文件封装成dll文件的方法有多种,其中常用的有以下三种:
使用Cython将Python文件转换为C文件,再使用C编译器生成dll文件
这种方法需要安装Cython并学习其使用方法。Cython是一个将Python代码转换为C语言代码的工具,可以将Python函数转换为C函数,并生成包含函数定义和函数调用的C文件。然后使用C编译器将C文件编译为dll文件。
具体步骤可以参考 blog.csdn.net 和 blog.csdn.net
使用CPython API编写C/C++代码,调用Python函数并生成dll文件
这种方法需要熟悉C/C++和Python语言,并学习CPython API的使用方法。CPython是Python的一种实现,它提供了一些C/C++的API,可以在C/C++代码中调用Python函数和对象。
具体步骤可以参考 blog.csdn.net 和 blog.csdn.net
使用Cython和setup.py文件将Python文件打包成dll文件
这种方法需要安装Cython和setuptools,并学习如何编写setup.py文件。setuptools是Python的一个包管理工具,可以用来打包Python代码。
具体步骤可以参考 blog.csdn.net 和 zhihu.com
ctypes
使用Python的ctypes库可以将Python代码封装为DLL。您可以使用ctypes.CDLL或ctypes.WinDLL来加载DLL,并使用它们调用在Python中定义的函数。具体用法可以查阅Python官方文档。
py2exe
py2exe是一个用于将Python脚本打包成可执行文件的工具。它可以将Python代码打包为独立的可执行文件,包括DLL和其他依赖项。可以通过pip安装py2exe,然后按照文档中的指导进行使用。
PyInstaller
PyInstaller也是一个用于将Python脚本打包成可执行文件的工具,类似于py2exe。它可以将Python代码打包为独立的可执行文件,包含所有必需的依赖项。可以通过pip安装PyInstaller,然后按照文档中的指导进行使用。
Python_Dll_Build
参考:https://www.cnblogs.com/xueliangliu/p/9375664.html
但是很麻烦,要写.pyx,非常难受
cython 原理
(人家不叫cpython而叫cython!javac倒是比java加了一个c,不要弄混了)
动态链接库(.dll,.so)是系统开发中一种非常重要的跨语言协作方式。
把python语言写成的算法编译成动态库,能够提供给其他语言调用,这能够在很大程度上提高算法的开发效率。
或者在python中某些时候需要C做效率上的补充
但是,虽然python可以调用其他语言生成的动态库,python作为一种脚本语言,本身是不能直接编译生成动态库的。为了生成动态库,我们借助cython,将python脚本变成c语言文件。
pyx 工程
pythonDll.py
def str_add(str1,str2):
return int(str1) + int(str2)
pythonDll.pyx
# 和前面python版本的相比,cdef替换了def,并加了public关键字,表示这个函数要导出。将这个代码保存成pyx文件,比如run.pyx
cdef public str_add(str1,str2):
return int(str1) + int(str2)
pythonDll.pyx
# 和前面python版本的相比:
# 1. cdef替换了def,并加了public关键字,表示这个函数要导出
# 2. 加入了参数和返回值的类型,否则虽然依然可以生成c文件,但后面不能生成dll,报错:返回值类型与函数类型不匹配(显示的类型是“PyObject*”)
# 3. 将这个代码保存成pyx文件,比如run.pyx
cdef public char* get_wish(const char* url):
result = genshin_wish(url)
if result == "":
result = "获取失败"
return result
pyx 编译
将cython变为c语言版本(cython命令工具好像是python自带的,我不记得我有下载过)
cython pythonDll.pyx
生成文件:
cythonDll.h
/* Generated by Cython 0.29.15 */ #ifndef __PYX_HAVE__cythonDll #define __PYX_HAVE__cythonDll #include "Python.h" #ifndef __PYX_HAVE_API__cythonDll #ifndef __PYX_EXTERN_C #ifdef __cplusplus #define __PYX_EXTERN_C extern "C" #else #define __PYX_EXTERN_C extern #endif #endif #ifndef DL_IMPORT #define DL_IMPORT(_T) _T #endif __PYX_EXTERN_C PyObject *str_add(PyObject *, PyObject *); #endif /* !__PYX_HAVE_API__cythonDll */ /* WARNING: the interface of the module init function changed in CPython 3.5. */ /* It now returns a PyModuleDef instance instead of a PyModule instance. */ #if PY_MAJOR_VERSION < 3 PyMODINIT_FUNC initcythonDll(void); #else PyMODINIT_FUNC PyInit_cythonDll(void); #endif #endif /* !__PYX_HAVE__cythonDll */
cythonDll.c
// 有2523行之多,我就不复制了
查看 c 工程(开头跳过)
dllmain.c(动态库主文件,include生成的头文件并导出_str_add方法)
#include <Python.h>
#include <Windows.h>
#include "cythonDll.h"
extern __declspec(dllexport) int __stdcall _str_add(const char * a, const char * b) {
return PyLong_AsLong(str_add(PyUnicode_FromString(a),PyUnicode_FromString(b)));<br>
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved) {
switch( fdwReason ) {
case DLL_PROCESS_ATTACH:
Py_Initialize();
PyInit_run(); #dll初始化的时候调用,这是python3的写法,python2改成,initrun()。参见生成的run.h
break;
case DLL_PROCESS_DETACH:
Py_Finalize();
break;
}
return TRUE;
}
c 编译
cl编译方案
# -I表示链接你的python的头文件和静态链接的lib文件
cl /LD dllmain.c cythonDll.c -IC:\python36\include C:\python36\libs\python36.lib
# 或
cl /LD mhy.c -IC:D:\Soft\Dev\All\Python_Anaconda\include D:\Soft\Dev\All\Python_Anaconda\libs\python37.lib
不过他这里说找不到cl命令,应该是我没安装。
python的pip包里有cl,但pip install cl 好像是cl.py而不是cl.exe
反正和c语言的dll编译是一样的,用vs编译也行
VS编译方案
在得到了.c和.h文件后,我们需要为其创建一个VS DLL工程。打开VS软件,新建win32项目,其中应用程序类型选择DLL,附加选项选择空项目。
默认项目
dllmain.cpp,这是定义 DLL 应用程序的入口点
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
修改项目
将刚刚的.c和.h文件复制到项目存放代码的文件夹并添加到项目中。在项目中添加一个空的dllmain.cpp,并添加如下代码
#include <Python.h>
#include <Windows.h>
#include "run.h"
extern "C"
{
__declspec(dllexport) int __stdcall _str_add(const char * a, const char * b)
{
return str_add(a, b);
}
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
Py_Initialize();
//dll初始化的时候调用,这是python3的写法,python2改成,initrun()。参见生成的run.h
PyInit_run();
break;
case DLL_PROCESS_DETACH:
Py_Finalize();
break;
}
return TRUE;
}
举例2
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "mhy.h" // ADD
extern "C" // ADD
{
__declspec(dllexport) char* __stdcall _getwish(const char* url)
{
return get_wish(url);
}
}
// !!!注意
// 32位下__stdcall会让_ctest()变成__ctest@0,64位不会
// VS默认是__cdcel方式,而易语言是__stdcall。但实测其实都可以,加不加E语言都能调用
extern "C" __declspec(dllexport) int ctest()
{
return 5;
}
extern "C" __declspec(dllexport) int __stdcall ctest2()
{
return 6;
}
// 修改。否则调用DLL时会报错:无法找到指定DLL库文件中的输出命令
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
Py_Initialize();
// dll初始化的时候调用,这是python3的写法,python2改成,initrun()。参见生成的mhy.h
// 名称的命名规则为PyInit_+你的C文件的名称,此处也要注意修改
PyInit_mhy();
break;
case DLL_PROCESS_DETACH:
Py_Finalize();
break;
}
return TRUE;
}
VS项目设置(添加包含目录和库目录)
复制.c和.h代码进去后会报错:无法打开源文件Python.h,需要添加库目录
右键项目 > 属性 > VC++目录 > 常规 >
包含目录 > 添加Python的include路径,如“C:\ProgramFiles\Python36\include”
库目录 > 添加Python的lib,如“C:\Program Files\Python36\libs”
注意编译的版本选择Release,根据Python版本选择x64平台或x32平台。 编译后可以得到dll文件。
Python版本的问题
Python的x64、x32平台应与VS保持一致
终极方法是是使用VS的环境编译一次Python源码,该方法详见Python笔记中的 “编译Python源码” 一章
其他Python编译方法
关键词:pyo、pyd、pyc
pyc、pyo、pyd
.pyinb
略
.py
略
.pyc
一种二进制文件,是由py文件经过编译后,生成的文件,是一种byte code。
pyc能提高模块的加载速度提高了,并没有提高代码的执行速度,一定意义上的保护源码不被泄露,通常情况下不用主动去编译pyc文件
而且pyc是一种跨平台的字节码,是由python的虚拟机来执行的,这个是类似于JAVA或者.NET的虚拟机的概念。
pyc的内容,是跟python的版本相关的,不同版本编译后的pyc文件是不同的,2.5编译的pyc文件,2.4版本的 python是无法执行的。.pyo
pyo是优化编译后的程序 python -O 源文件即可将源程序编译为pyo文件
.pyd
pyo是python的动态链接库
为什么需要pyc文件
这个需求太明显了,因为py文件是可以直接看到源码的,如果你是开发商业软件的话,不可能把源码也泄漏出去吧?所以就需要编译为pyc后,再发布出去。
当然,pyc文件也是可以反编译的,不同版本编译后的pyc文件是不同的,根据python源码中提供的opcode,可以根据pyc文件反编译出 py文件源码,网上可以找到一个反编译python2.3版本的pyc文件的工具,不过该工具从python2.4开始就要收费了,如果需要反编译出新版本的pyc文件的话,就需要自己动手了,不过你可以自己修改python的源代码中的opcode文件,重新编译 python,从而防止不法分子的破解。
.pyz(w)
从Python 3.5开始,定义了.pyz和.pyzw分别作为“Python Zip应用”和“Windows下Python Zip应用”的扩展名。
新增了内置zipapp模块来进行简单的管理,可以用Zip打包Python程序到一个可执行.pyz文件。
使用也比较简单,但无法想pyinstaller等打包exe工具一样脱离python环境单独运行,而且这类文件往往拿文本编辑器就能看到它的源码信息,总体来说还是比较鸡肋。
.exe
为什么会再单独提到exe文件,因为python有很多可以将python源码打包成exe的工具,从而脱离python环境单独运行
.dll
见DLL笔记
把代码编译成pyc文件
python方式
使用模块
import py_compile
py_compile.compile(r'文件路径')
# 或
py_compile.compile(r'demo.py', r'demo.pyc')
命令行方式
或者直接在terminal中运行,也生成demo.pyc文件
python -m py_compile 文件路径
模块使用 (不能运行)
编译完成后如果想要直接运行Pyc文件注意两点:
- 要把pyc文件从 pycache 目录中移动出来,放到py文件对应的位置
- 需要将model.cpython-36.pyc重命名为model.pyc
然后可能会遇到环境问题:
(web) H:\...>app.pyc
Traceback (most recent call last):
File "H:\...\app.py", line 1, in <module>
from flask import Flask, request
ModuleNotFoundError: No module named 'flask'
(web) H:\...>python app.pyc
(web) H:\...>
注意项:只能import pyc文件。不能用python3.6运行pyc文件
python源码编译.so文件
Python源码编译至.so文件的思路是先将py转换为c代码,然后编译c为so文件。
所需编译环境:
python安装:cython
pip install cython
linux 安装:python-devel,gcc
yum install python-devel yum install gcc
同样举上述例子:在demo文件夹下有一个demo.py,需要将demo.py编译.so。
demo.py内容如下:
def print_hello():
print('hello')
在demo文件夹下新建setup.py,内容如下:
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize(["demo.py"]))
在shell执行:
cd demo
python setup.py build_ext
在demo文件夹下,就会生成demo.c文件,同时在demo文件夹下生成build文件夹,在build文件夹下包含生成的.so文件。
自定义python模块打包发布
将自定义python模块打包发布有两种,一种是将python源码打包发布,一种是将python源码转换至动态链接库.so文件打包发布。下面介绍一下这两种打包方式。
A)、使用python源码打包
同样使用上述例子:在demo文件夹下有一个demo.py,需要将demo.py打包。
在demo文件夹下新建setup.py,内容如下:
from distutils.core import setup
setup(name = ‘demo’,
version = '1.0',
py_modules = ['demo'],
)
在shell执行:
cd demo
python setup.py bdist_wheel
在demo文件夹下,生成dist文件夹,dist文件夹中包含了生成的python模块。
B)、使用python源码编译成.so打包
使用上述例子:在demo文件夹下有一个demo.py,需要将demo.py打包。
首先将python源码转换为c代码:
在demo文件夹下新建generateC.py,内容如下:
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize(["demo.py"]))
然后将c代码编译打包,过程如下:
在demo文件夹下新建generateWHL.py,内容如下:
from setuptools import setup
from setuptools.dist import Distribution
from distutils.core import Extension
setup(name = 'demo',
version = '1.0',
ext_modules = [Extension("demo",['demo.c'])],
)
将上述两个文件执行,如下:在demo文件夹下新建setup.py,内容如下:
import os
cmd1 = "python generateC.py build_ext"
os.system(cmd1)
cmd2 = "python generateWHL.py bdist_wheel"
os.system(cmd2)
在shell执行:
cd demo
python setup.py
在demo文件夹下,生成dist文件夹,dist文件夹中包含了生成的python模块。
C)、安装卸载
可以使用pip直接安装和卸载生成的python模块。
4、其他
本文主要是针对python源码编译打包做了简单介绍,使用了最简单的例子。对于复杂的情况,比如打包时需要额外的数据文件,依赖包等等,需要具体查看setuptools模块的相关内容。
三
参考:https://www.zhihu.com/question/604029921,/answer/3053431742
将Python程序打包成DLL可以使用Cython或pybind11等工具。
以下是使用Cython打包Python程序的步骤:
创建一个Python文件,并在其中定义需要打包的函数
例如,我们定义一个名为“hello”的函数:
def hello(name):
print("Hello", name)
创建一个Cython文件,并声明Python函数的C接口创建一个名为“hello.pyx”的Cython文件,并在其中声明“hello”函数的C接口:
cdef public void hello(char *name):
print("Hello", name.decode())
创建一个setup.py文件,用于打包Cython文件并生成DLL
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize('hello.pyx'))
执行setup.py文件并生成DLL
使用以下命令执行setup.py文件,并生成用于Python程序的DLL文件:
python setup.py build_ext --inplace
生成的DLL文件会保存在和Cython文件相同的目录下。
现在你可以在其他Python程序中导入刚刚生成的DLL文件,并调用其中定义的函数。