0%

一、先来看一篇转载文章《在 VS2015 中使用 Qt4

http://tangzx.qiniudn.com/post-0111-qt4-vs2015.html 最早的原文,看不到了

https://github.com/district10/qt4-vs2015x64 原作者的github,里面的东东都下载不了了

二、firecat本人的教程

0、Qt官方

Qt4.8.7官方源码下载

https://download.qt.io/new_archive/qt/4.8/4.8.7/

官网的exe只提供了MSVC2010,没有更高版本的。高版本需要自己下载源码编译。

源码里面的配置文件已经提供了MSVC 2015的编译选项,\qt-everywhere-opensource-src-4.8.7\mkspecs\win32-msvc2015

参照官方提供的编译文档一步一步执行即可;但是配置文件里没有提供MSVC 2017的编译选项。

官方编译的文档

https://doc.qt.io/archives/qt-4.8/installation.html

https://doc.qt.io/archives/qt-4.8/configure-options.html

https://doc.qt.io/archives/qt-4.8/install-win.html

https://doc.qt.io/archives/qt-4.8/install-mac.html

https://doc.qt.io/qt-5/build-sources.html

1、Qt 4.8.7+MSVC 2017

推荐使用第三方提供的源码,它已经是修改好的,里面含有MSVC 2017编译选项,可以编译。

https://github.com/scharsig/Qt Qt4.8.7+MSVC2017源码

https://forum.qt.io/topic/91623/building-qt-4-8-7-with-visual-studio-2017 Qt4.8.7+MSVC2017论坛

https://github.com/sandym/qt-patches 仅供参考,编译补丁

https://github.com/Homebrew/formula-patches/tree/master/qt 仅供参考,编译补丁

https://github.com/BartVandewoestyne/qt_4_8_7_with_vs2017_patch 仅供参考,编译补丁

完整的编译过程:

下载第三方源码https://github.com/scharsig/Qt/tree/master/qt-4.8.7-vs2017 然后解压

-–step1—

Windows桌面-开始-程序-Visual Studio 2017-Visual Studio Tools-VC-x86 Native Tools Command Prompt for VS 2017

-–step2—

C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise>cd F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017

-–step3—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>configure -help

-–step4—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>
configure -make nmake -debug-and-release -opensource -confirm-license -platform win32-msvc2017 -prefix F:\Qt\Qt4.8.7-msvc2017 -nomake examples -nomake tests

如果不想编译这么多功能模块,可以精简为:

configure -make nmake -debug-and-release -opensource -confirm-license -platform win32-msvc2017 -prefix F:\Qt\Qt4.8.7-msvc2017 \
  -no-qt3support -no-multimedia \
  -no-audio-backend -no-phonon -no-phonon-backend -no-libtiff \
  -no-libmng -no-dbus -no-nis -nomake examples -nomake tests

 -release              Compile and link Qt with debugging turned off. -debug                Compile and link Qt with debugging turned on. -nomake tests         Disable building of tests to speed up compilation -nomake examples      Disable building of examples to speed up compilation -confirm-license      Automatically acknowledge the LGPL 2.1 license.

-–step5—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>nmake

-–step6—

F:\Qt\setup-exe\4.8.7\Qt-master\qt-4.8.7-vs2017>nmake install

-–step7—

添加到Qt Creator

-–step8—

新建项目测试,Qt Creator+Qt4.8.7+MSVC2017编译项目时,如果报错:

intermediate\moc\moc_rs_actionzoompan.cpp:-1: error: C1041: 无法打开程序数据库“F:\CADCAM\QCAD\src\build-LibreCAD-v1.0.4-qt4-Desktop_Qt_4_8_7_MSVC2017_32bit-Debug\librecad\vc140.pdb”;如果要将多个 CL.EXE 写入同一个 .PDB 文件,请使用 /FS

解决办法:

在Qt Creator的项目文件,即.pro文件中,可以通过QMAKE_CXXFLAGS来给MSVC编译器传递编译开关。

QMAKE_CXXFLAGS += /FS

win32-msvc*:QMAKE_CXXFLAGS += /wd"4819" QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO -= -Zc:strictStrings 

MSVC 2017编译器常见错误的解决:

https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/c-cpp-build-errors?view=vs-2017

2、Mac OS+Qt 4.8.7

笔者的Mac OS版本是MacOS-10.15-Catalina,高版本的OS和Clang已经不再支持Qt官方发布的Qt4了。

解决办法可以参见我的另一篇博文:https://blog.csdn.net/libaineu2004/article/details/104740623

https://trac.macports.org/ticket/58651 mac下编译qt4遇到问题

https://github.com/macports/macports-ports/tree/master/aqua/qt4-mac mac编译补丁

方法1: 为当前用户设置环境变量

为当前用户设置 HTTP_PROXY 和 HTTPS_PROXY 环境变量,Podman 将自动读取这些环境变量并使用代理。

1
2
3
4
5
6
7
8
9
10
11
# Bash
export HTTP_PROXY="http://代理地址:端口"
export HTTPS_PROXY="https://代理地址:端口"

# 对于 bash, 也可以在 ~/.bashrc 中添加上述命令使其永久有效

# Fish
set -x HTTP_PROXY "http://代理地址:端口"
set -x HTTPS_PROXY "https://代理地址:端口"

# 对于 fish,也可以在 ~/.config/fish/config.fish 中添加以上命令

如果代理需要身份验证,可以在 URL 中添加用户名和密码。格式如下:

1
http://用户名:密码@代理地址:端口

方法2:为 Podman 服务设置配置文件

通过编辑 /etc/containers/registries.conf 配置文件为 Podman 服务设置代理。在该文件中添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[registries.search]
registries = ['docker.io', 'quay.io']

[registries.insecure]
registries = []

[registries.block]
registries = []

[registries.unqualified-search-registries]

[registry.mirrors]

[registry.configs]

[registry.configs.REGISTRY_NAME.HOSTNAME/HOSTPATH]
unqualified-search-registries = ["registry.fedoraproject.org", "registry.access.redhat.com", "docker.io"]
blocked=false

[registry.configs.REGISTRY_NAME.HOSTNAME]
http-proxy="http://代理地址:端口"
https-proxy="https://代理地址:端口"

替换 REGISTRY_NAME.HOSTNAME 为您要配置的注册表,如 docker.io。如果代理需要身份验证,则使用类似 http://user:password@proxy.example.com:8080 的格式。

方法3: 为单个 Podman 命令设置代理

为单个 Podman 命令临时设置代理,方法是在命令前添加 –build-arg 参数。例如:

1
podman --build-arg HTTP_PROXY="http://代理地址:端口" --build-arg HTTPS_PROXY="https://代理地址:端口" pull nginx

方法四: 配置 http-proxy.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ systemctl status podman
● podman.service - Podman API Service
Loaded: loaded (/usr/lib/systemd/system/podman.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/podman.service.d
└─http-proxy.conf
Active: inactive (dead) since Mon 2023-11-20 18:45:12 CST; 3 months 22 days ago
Docs: man:podman-system-service(1)
Process: 50669 ExecStart=/usr/bin/podman $LOGGING system service (code=exited, status=0/SUCCESS)
Main PID: 50669 (code=exited, status=0/SUCCESS)

Nov 20 18:45:07 downlaod systemd[1]: Starting Podman API Service...
Nov 20 18:45:07 downlaod systemd[1]: Started Podman API Service.
Nov 20 18:45:07 downlaod podman[50669]: time="2023-11-20T18:45:07+08:00" level=info msg="/usr/bin/podman filtering at log level>
Nov 20 18:45:07 downlaod podman[50669]: time="2023-11-20T18:45:07+08:00" level=info msg="Not using native diff for overlay, thi>
Nov 20 18:45:07 downlaod podman[50669]: time="2023-11-20T18:45:07+08:00" level=info msg="Setting parallel job count to 13"
Nov 20 18:45:07 downlaod podman[50669]: time="2023-11-20T18:45:07+08:00" level=info msg="Using systemd socket activation to det>
Nov 20 18:45:07 downlaod podman[50669]: time="2023-11-20T18:45:07+08:00" level=info msg="API service listening on \"/run/podman>
Nov 20 18:45:12 downlaod systemd[1]: podman.service: Succeeded.

$ cat /etc/systemd/system/podman.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://192.168.21.101:7890"
Environment="HTTPS_PROXY=http://192.168.21.101:7890"
Environment="NO_PROXY=localhost,127.0.0.1,.coding.net,.tencentyun.com,.myqcloud.com,harbor.bsgchina.com"

  1. 安装依赖环境

    1
    2
    3
    4
    5
    6
    7
    8
    # 启用虚拟化平台
    dism /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

    # 启用linux子系统
    dism /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all

    wsl --install
    wsl --update
  2. 安装Podman

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 安装DockerCLI,用于兼容Docker命令
    winget install --id Docker.DockerCLI

    # 安装Podman
    winget install --id RedHat.Podman

    # 安装Podman Desktop (可选)
    winget install --id RedHat.Podman-Desktop

    # 初始化Podman
    podman machine init

    # 配置端口转发
    wsl sudo sysctl net.ipv4.ip_forward=1
  3. 配置wsl虚拟机

    1
    2
    3
    4
    5
    6
    7
    # 修改默认软件源
    sudo sed -e 's|^metalink=|#metalink=|g' \
    -e 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://mirrors.tuna.tsinghua.edu.cn/fedora|g' \
    -i.bak \
    /etc/yum.repos.d/fedora.repo \
    /etc/yum.repos.d/fedora-updates.repo
    sudo dnf makecache
  4. 测试

    1
    docker run --rm -d -p 80:80 --name httpd docker.io/library/httpd:latest
  5. 配置镜像加速
    podman的配置文件在容器内 /etc/containers/registries.conf,配置格式如下

    1
    2
    3
    4
    5
    unqualified-search-regustrues = ["docker.io"]

    [[registry]] # 注意此处配置不需要加'https'
    prefix = "docker.io" # 访问地址
    location = "docker.m.daocloud.io" # 加速地址
  6. 配置私有镜像库

1
2
3
[[registry]]
location = "harbor.example.io"
insecure = true

如果访问地址为https需要配置信任证书

1
2
sudo mkdir /etc/containers/certs.d
sudo cp <path to cert> /etc/containers/certs.d/ca.crt
  1. 配置文件翻译
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    # 有关此配置文件的更多信息,请参阅 containers-registries.conf(5)。
    #
    # 注意:使用未完全限定镜像名称的风险
    # 我们建议始终使用包括注册表服务器(完整 DNS 名称)、命名空间、镜像名称和标签在内的完全限定镜像名称
    # (例如,registry.redhat.io/ubi8/ubi:latest)。通过摘要(例如,
    # quay.io/repository/name@digest)拉取镜像可以进一步消除标签的不确定性。
    # 使用短名称时,始终存在镜像被伪造的风险。例如,用户想从某个注册表中拉取名为
    # `foobar` 的镜像,并期望该镜像来自 myregistry.com。如果
    # myregistry.com 不是搜索列表中的第一个,攻击者可能会在列表中靠前的位置
    # 放置另一个名为 `foobar` 的镜像。用户可能会意外拉取并运行攻击者的镜像和代码,而不是
    # 预期的内容。我们建议只添加完全可信的注册表(即,不允许未知或匿名用户
    # 创建任意名称的账户的注册表)。这将防止镜像被伪造、抢占或以其他方式变得不安全。
    # 如果有必要使用这些注册表,它应该添加到列表的末尾。
    #
    # # 一个主机[:端口]格式的注册表数组,当拉取未完全限定镜像时,按顺序尝试这些注册表。
    unqualified-search-registries = ["registry.fedoraproject.org", "registry.access.redhat.com", "docker.io"]
    #
    # [[registry]]
    # # "prefix" 字段用于选择相关的 [[registry]] TOML 表;
    # # 使用输入镜像名称时,只有与该名称最长匹配的 TOML 表会被使用
    # # (考虑到命名空间/库/标签/摘要分隔符)。
    # #
    # # 如果缺少 prefix 字段,则默认与 "location" 字段相同。
    prefix = "example.com/foo"
    #
    # # 如果为 true,则允许未加密的 HTTP 连接以及使用不受信任证书的 TLS 连接。
    insecure = false
    #
    # # 如果为 true,则禁止拉取匹配名称的镜像。
    blocked = false
    #
    # # "prefix" 所在命名空间的物理位置。
    # #
    # # 默认情况下,与 "prefix" 相同(在这种情况下,可以省略 "prefix",并且 [[registry]] TOML 表只指定 "location")。
    # #
    # # 例如:假设
    # # prefix = "example.com/foo"
    # # location = "internal-registry-for-example.net/bar"
    # # 那么对镜像 example.com/foo/myimage:latest 的请求实际上会与
    # # internal-registry-for-example.net/bar/myimage:latest 镜像匹配。
    location = "internal-registry-for-example.com/bar"
    #
    # # "prefix" 所在命名空间的(可能部分的)镜像。
    # #
    # # 将按指定顺序尝试这些镜像;第一个可以联系到并包含镜像的将被使用
    # # (如果所有镜像都没有该镜像,则最后尝试 "registry.location" 字段指定的主位置,或者使用未修改的用户指定引用)。
    # #
    # # "mirror" 数组中的每个 TOML 表可以包含以下字段,语义与直接在 [[registry]] TOML 表中指定的相同:
    # # - location
    # # - insecure
    [[registry.mirror]]
    location = "example-mirror-0.local/mirror-for-foo"
    [[registry.mirror]]
    location = "example-mirror-1.local/mirrors/foo"
    insecure = true
    # # 根据上述配置,拉取 example.com/foo/image:latest 时将按顺序尝试:
    # # 1. example-mirror-0.local/mirror-for-foo/image:latest
    # # 2. example-mirror-1.local/mirrors/foo/image:latest
    # # 3. internal-registry-for-example.net/bar/image:latest
    # # 并使用第一个存在的镜像。
    #
    # short-name-mode="enforcing"
    # 强制使用完全限定镜像名称

    [[registry]]
    location="localhost:5000"
    insecure=true
    # 允许使用不安全的连接拉取本地镜像。

1. Co., Ltd.

  • 全称:Company Limited
  • 含义:有限责任公司,常见于英国、中国及亚洲地区
  • 特点:”Co.“为Company缩写,”.“表示缩写符号,”,”用于分隔前后词

2. Inc.

  • 全称:Incorporated
  • 含义:股份有限公司,多用于美国、加拿大
  • 示例:Apple Inc.,强调股东责任限于股份投资

3. LLC

  • 全称:Limited Liability Company
  • 含义:有限责任公司(美国特有形式)
  • 特点:兼具合伙制灵活性与股份制有限责任,如Google LLC

4. GmbH

  • 全称:Gesellschaft mit beschränkter Haftung
  • 含义:有限责任公司,德国及德语区专用
  • 示例:Bosch GmbH1

5. AG

  • 全称:Aktiengesellschaft
  • 含义:股份有限公司,德国及瑞士常见
  • 示例:BMW AG

6. S.A.

- 全称:Société Anonyme(法)/Sociedad Anónima(西)
- 含义:股份有限公司,流行于法国、西班牙等拉丁语系国家
- 示例:L’Oréal S.A.1

7. Plc

  • 全称:Public Limited Company
  • 含义:公众有限公司(英国上市企业专用)
  • 示例:HSBC Holdings plc1

8. 株式会社(Kabushiki Kaisha)

  • 缩写:KK
  • 含义:日本股份有限公司
  • 示例:Toyota Motor Corporation KK

地域差异提示

  • 英国”Ltd.”与美国”LLC”虽均表有限责任,但法律结构不同
  • 荷兰用”BV”(私人有限公司),意大利用”S.p.A.”(股份公司)

方式一

通过udev规则监听设备事件,编写/etc/udev/rules.d/99-udev-mount.rules规则实现U盘插入捕获U盘插入事件

1
2
3
4
5
# 插入U盘自动挂载
ACTION=="add", KERNEL=="sd[a-z]*", RUN+="/bin/mkdir -p /media/udev-%k", RUN+="/bin/mount /dev/%k /media/udev-%k"

# 移除U盘自动卸载
ACTION=="remove", KERNEL=="sd[a-z]*", RUN+="/bin/umount /media/udev-%k"

规则编辑完成后执行以下命令使规则生效

1
sudo udevadm control --reload

1. 准备环境

  • 准备开发包:包含头文件(.h)、库文件(.dll或.so)及对接文档
  • 安装依赖:确保Python环境已安装ctypes库或第三方库例如Cython(用于复杂场景)
  • 配置路径:将SDK的库路径添加到环境变量或直接在代码中指定路径(推荐方式,不会因为换电脑导致无法编译,例如sdk/windows/sdk.dll)

2. 封装接口

加载SDK

1
2
3
4
5
6
7
8
9
10
import sys
from ctypes import *
from ctypes import wintypes

# 区分Windows和Linux环境,加载不同SDK
if sys.platform.startwith("win"):
sdk = WinDLL("sdk/windows/sdk.dll")
elif sys.platform.startwith("linux"):
sdk = CDLL("sdk/linux/sdk.so")

定义结构体

1
2
3
4
5
6
7
# 定义结构体,需要与SDK头文件一致
class DEMO
_fields_ = [
("fieldname-1", c_int), # int 类型
("fieldname-2", c_int_p), # int 指针
# 其他字段参考SDK文档
]

![[Python对接C库/IMG-20250804110742707.png]]
定义函数原型,需严格对齐SDK中的数据类型和函数参数顺序

1
2
3
4
sdk.Init.restype = c_bool # 映射返回值,Init为C/C++中的函数名
sdk.Init.argtypes = [ # 映射参数列表
c_int, c_int_p, c_char_p
]

3. 接口调用

函数调用

1
sdk.Init(c_int(0), c_int_p(0), c_char_p(b"this is a test"))

带有回调函数的函数调用
回调函数例如

1
int (*Callback) (int, char*);

Python中定义回调函数类型

1
CallbackType = CFUNCTYPE(c_int, c_int, c_char_p) # 返回类型在前,参数在后

若C函数使用__stdcall(常见于Windows API),需要WINFUNCTYPE替代CFUNCTYPE,若为__cdecl(默认),则使用CFUNCTYPE
Python实现回调函数(参数和返回值需与C定义严格一致)

1
2
3
def py_callback(num, text) -> int:
print(f"Received: {num}, {text.decode("utf-8")}")
return 0 # 返回值需与C定义匹配

处理指针参数

若回调参数包含指针,例如void*,需要使用c_void_p类型,并通过cast解析

1
2
3
def py_callback(data_ptr): 
data = cast(data_ptr, POINTER(c_int)).contents.value
return data

注册回调函数

1
2
3
4
c_callback = CallbackType(py_callback) # 使用定义的回调类型包装Python函数
global_keep_alive = c_callback # 关键! 将回调对象保存为全局变量或类属性,防止被回收
sdk.register_callback.argtypes = [c_int, CallbackType]
sdk.register_callback.restype = None

4. 资源释放

退出时需要调用SDK中的清理函数释放资源

1
sdk.Cleanup()

5. 注意事项

  • 结构体指针和缓冲区需要手动分配/释放,避免内存泄漏
  • 不同版本SDK接口可能有差异,建议统一开发与部署环境
  • 映射Windows中特有的类型例如WORD,DWORDwintypes包中
  • C调用Python回调时,若Python函数抛出异常可能导致程序崩溃。需要在回调内部处理异常。
  • 若C函数在子线程中调用回调,需确保Python的GIL(全局解释锁)已获取
    1
    2
    3
    4
    5
    6
    7
    from ctypes import py_object, pythonapi 
    PyGILState_Ensure = pythonapi.PyGILState_Ensure
    PyGILState_Release = pythonapi.PyGILState_Release
    def thread_safe_callback():
    state = PyGILState_Ensure()
    # 执行Python操作
    PyGILState_Release(state)

使用os.stat()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"fmt"
"os"
)

func main() {
dir := "new"
if _, err := os.Stat(dir); os.IsNotExist(err) {
fmt.Println(dir, "does not exist")
} else {
fmt.Println("The provided directory named", dir, "exists")
}
}

使用os.open()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"fmt"
"os"
)

func main() {
dir := "go"
if _, err := os.Open(dir); os.IsNotExist(err) {
fmt.Println("The directory named", dir, "does not exist")
} else {
fmt.Println("The directory namend", dir, "exists")
}
}

使用mkdir()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"fmt"
"os"
)

func main() {
dir := "new"
if err := os.Mkdir(dir, 0755); os.IsExist(err) {
fmt.Println("The directory named", dir, "exists")
} else {
fmt.Println("The directory named", dir, "does not exist")
}
}

ECB模式,PKCS5填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package sm4

import (
"bytes"
"encoding/hex"
"github.com/tjfoc/gmsm/sm4"
)

func SM4EcbEncrypt(key, plaintext []byte) (string, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return "", err
}

plaintext = PKCS5Padding(plaintext, block.BlockSize())
ciphertext := make([]byte, len(plaintext))

for start := 0; start < len(plaintext); start += block.BlockSize() {
block.Encrypt(ciphertext[start:start+block.BlockSize()], plaintext[start:start+block.BlockSize()])
}

return hex.EncodeToString(ciphertext), nil
}

func SM4EcbDecrypt(key []byte, data string) ([]byte, error) {
plaintext, _ := hex.DecodeString(data)
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}

ciphertext := make([]byte, len(plaintext))

for start := 0; start < len(plaintext); start += block.BlockSize() {
block.Decrypt(ciphertext[start:start+block.BlockSize()], plaintext[start:start+block.BlockSize()])
}
ciphertext = PKCS5Unpadding(ciphertext)
return ciphertext, nil
}

func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}

func PKCS5Unpadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}