从零写一个ALSA声卡驱动学习(1)

前言:

本文档描述了如何编写 ALSA(高级 Linux 音频架构)驱动程序。文档主要聚焦于 PCI 声卡的实现。对于其他类型的设备,可能会使用不同的 API。不过,至少 ALSA 的内核 API 是一致的,因此本文档在编写这些驱动时仍然具有一定的参考价值。 本指南面向那些已经具备足够 C 语言技能并且掌握基本 Linux 内核编程知识的开发者。本文档不会讲解 Linux 内核编程的一般性内容,也不会涉及底层驱动实现的细节。它仅介绍在 ALSA 框架下编写 PCI 声卡驱动的标准方法。

内容参考学习链接文档:

https://kernel.org/doc/html/latest/sound/kernel-api/writing-an-alsa-driver.html#header-files

File Tree Structure:

下面的文件结构是Linux内核关于sound目录下的介绍:

sound/core/oss/seq/oss/include/drivers/mpu401/opl3/i2c/synth/emux/pci/(cards)/isa/(cards)/arm/ppc/sparc/usb/pcmcia /(cards)/soc/oss
  • core 目录 : 该目录包含 ALSA 驱动的中间层,是 ALSA 驱动的核心。此目录中存放的是 ALSA 的原生模块。其子目录包含了不同的模块,并依赖于内核配置选项。

  • core/oss  : 该目录存放 OSS PCM 和混音器(mixer)仿真模块的代码。由于 OSS 的 rawmidi 仿真部分非常小,因此被直接包含在 ALSA 的 rawmidi 代码中。序列器(sequencer)相关的 OSS 仿真代码则位于 core/seq/oss 目录(见下文)。

  • core/seq  : 该目录及其子目录存放 ALSA 序列器(sequencer)相关代码。它包含了序列器核心以及主要模块,如 snd-seq-midi、snd-seq-virmidi 等。这些模块仅在内核配置中启用了 CONFIG_SND_SEQUENCER 时才会被编译。

  • core/seq/oss  : 此目录包含 OSS 序列器仿真代码。

  • include 目录  : 该目录用于存放 ALSA 驱动对用户空间公开的头文件,或供多个不同目录中的文件共享使用。一般来说,私有头文件不应放在这个目录中,但你仍可能会在其中看到一些私有头文件,这是历史遗留问题所致。:)

  • drivers 目录  :该目录包含在不同平台间共享的驱动代码,因此它们不应具有架构相关性。例如,dummy PCM 驱动和串口 MIDI 驱动都位于此目录。其子目录中存放的是与总线或 CPU 架构无关的组件代码。 drivers/mpu401 包含 MPU401 和 MPU401-UART 模块。 drivers/opl3 与 opl4 包含 OPL3 和 OPL4 FM 合成器相关代码。

  • i2c 目录  :该目录包含 ALSA 的 i2c 相关组件。虽然 Linux 系统本身有标准的 i2c 层,但某些声卡只需要简单的操作,而标准的 i2c API 过于复杂,因此 ALSA 对某些声卡实现了自己的 i2c 代码。

  • synth 目录  :此目录包含中间层的合成器模块。目前,只有 Emu8000/Emu10k1 合成器驱动存放在 synth/emux 子目录中。

  • pci 目录  :此目录及其子目录存放 PCI 声卡的顶层驱动模块,以及与 PCI 总线相关的专用代码。如果驱动只包含单个源文件,则直接放在 pci 目录下;如果驱动包含多个源文件,则会单独创建子目录(例如 emu10k1、ice1712)。

  • isa 目录  : 此目录及其子目录存放 ISA 声卡的顶层驱动模块。

  • arm、ppc 和 sparc 目录  : 这些目录用于存放特定于某一架构的顶层声卡驱动模块。

  • usb 目录  : 该目录包含 USB 音频驱动。USB MIDI 驱动已经整合进了 USB 音频驱动中。

  • pcmcia 目录  : 该目录用于存放 PCMCIA(尤其是 PCCard)驱动。由于 CardBus 的 API 与标准 PCI 卡相同,因此其驱动位于 pci 目录中。

  • soc 目录  : 该目录包含 ASoC(ALSA SoC,系统级芯片)层的代码,包括 ASoC 核心、编解码器(codec)和 machine 驱动等。

  • oss 目录  : 该目录包含 OSS/Lite 代码。截止目前,除了 m68k 架构下的 dmasound 之外,其他代码均已被移除。

basic Flow for PCI Drivers:

PCI 声卡驱动的最小实现流程如下:

  1. 定义 PCI ID 表(参见“PCI 条目”部分)。

  2. 创建 probe 回调函数(用于设备检测和初始化)。

  3. 创建 remove 回调函数(用于设备移除时的清理操作)。

  4. 创建一个 struct pci_driver 结构体,包含上述三个函数指针(即:PCI ID 表、probe 和 remove 函数)。

  5. 创建初始化函数,该函数调用 pci_register_driver() 来注册上面定义的 pci_driver 表。

  6. 创建退出函数,该函数调用 pci_unregister_driver() 来注销该驱动。

Full Code Example:

下面展示了一个代码示例。目前有些部分尚未实现,但会在接下来的章节中补充完成。 在 snd_mychip_probe() 函数中的注释行里标注的数字,对应的是下一节中将详细解释的内容。

#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>/* module parameters (see "Module Parameters") */
/* SNDRV_CARDS: maximum number of cards supported by this module */
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* definition of the chip-specific record */
struct mychip {struct snd_card *card;/* the rest of the implementation will be in section* "PCI Resource Management"*/
};/* chip-specific destructor* (see "PCI Resource Management")*/
static int snd_mychip_free(struct mychip *chip)
{.... /* will be implemented later... */
}/* component-destructor* (see "Management of Cards and Components")*/
static int snd_mychip_dev_free(struct snd_device *device)
{return snd_mychip_free(device->device_data);
}/* chip-specific constructor* (see "Management of Cards and Components")*/
static int snd_mychip_create(struct snd_card *card,struct pci_dev *pci,struct mychip **rchip)
{struct mychip *chip;int err;static const struct snd_device_ops ops = {.dev_free = snd_mychip_dev_free,};*rchip = NULL;/* check PCI availability here* (see "PCI Resource Management")*/..../* allocate a chip-specific data with zero filled */chip = kzalloc(sizeof(*chip), GFP_KERNEL);if (chip == NULL)return -ENOMEM;chip->card = card;/* rest of initialization here; will be implemented* later, see "PCI Resource Management"*/....err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);if (err < 0) {snd_mychip_free(chip);return err;}*rchip = chip;return 0;
}/* constructor -- see "Driver Constructor" sub-section */
static int snd_mychip_probe(struct pci_dev *pci,const struct pci_device_id *pci_id)
{static int dev;struct snd_card *card;struct mychip *chip;int err;/* (1) */if (dev >= SNDRV_CARDS)return -ENODEV;if (!enable[dev]) {dev++;return -ENOENT;}/* (2) */err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);if (err < 0)return err;/* (3) */err = snd_mychip_create(card, pci, &chip);if (err < 0)goto error;/* (4) */strcpy(card->driver, "My Chip");strcpy(card->shortname, "My Own Chip 123");sprintf(card->longname, "%s at 0x%lx irq %i",card->shortname, chip->port, chip->irq);/* (5) */.... /* implemented later *//* (6) */err = snd_card_register(card);if (err < 0)goto error;/* (7) */pci_set_drvdata(pci, card);dev++;return 0;error:snd_card_free(card);return err;
}/* destructor -- see the "Destructor" sub-section */
static void snd_mychip_remove(struct pci_dev *pci)
{snd_card_free(pci_get_drvdata(pci));
}

驱动构造函数:

PCI 驱动的真正构造函数是 probe 回调函数。由于 PCI 设备可能是热插拔设备,因此 probe 回调函数及其调用的其他组件构造函数不能使用 __init 前缀。 在 probe 回调函数中,通常会使用以下流程:

  • 1)检查并递增设备索引。

static int dev;
....
if (dev >= SNDRV_CARDS)return -ENODEV;
if (!enable[dev]) {dev++;return -ENOENT;
}

其中 enable[dev] 是模块参数选项。 每次调用 probe 回调时,都要检查该设备是否可用。如果不可用,则只需递增设备索引并返回。dev 变量稍后(在第 7 步)也会继续递增。

  • 2)创建一个 sound card 实例

struct snd_card *card;
int err;
....
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);

详细内容将在“声卡与组件的管理”章节中进行说明。

  • 3)创建主组件 在这一部分,将分配 PCI 资源

struct mychip *chip;
....
err = snd_mychip_create(card, pci, &chip);
if (err < 0)goto error;

详细内容将在“PCI 资源管理”章节中进行说明。 当出现错误时,probe 函数需要进行错误处理。在本示例中,我们采用统一的错误处理路径,该路径被放置在函数的末尾:

error:snd_card_free(card);return err;

于每个组件都可以被正确释放,因此在大多数情况下,只需调用一次 snd_card_free() 就足够了。

  • 4)设置驱动的 ID 和名称字符串。

strcpy(card->driver, "My Chip");
strcpy(card->shortname, "My Own Chip 123");
sprintf(card->longname, "%s at 0x%lx irq %i",card->shortname, chip->port, chip->irq);

driver 字段保存芯片的最小 ID 字符串。这个 ID 会被 alsa-lib 的配置器使用,因此应保持简洁但唯一。即使是同一个驱动,也可以使用不同的 driver ID 来区分不同类型芯片的功能。 shortname 字段是一个简短但更具描述性的名称字符串,用于展示。 longname 字段包含的是将在 /proc/asound/cards 中显示的信息。

  • 5)创建其他组件,如混音器、MIDI 等 在此步骤中定义基本组件,例如 PCM、混音器(如 AC97)、MIDI(如 MPU-401)以及其他接口。如果需要定义 proc 文件,也应在这里进行。

  • 6)注册该声卡实例

err = snd_card_register(card);
if (err < 0)goto error;

这一部分也将在“声卡与组件的管理”章节中进行说明。

  • 7) 设置 PCI 驱动的数据,并返回 0

pci_set_drvdata(pci, card);
dev++;
return 0;

在上述步骤中,声卡记录被保存了下来。该指针同样会在 remove 回调函数 和 电源管理回调函数 中使用。

Destructor(析构函数):

析构函数,即 remove 回调函数,仅需释放声卡实例即可。随后,ALSA 中间层会自动释放所有附加的组件。 通常只需调用一行代码即可:

static void snd_mychip_remove(struct pci_dev *pci)
{snd_card_free(pci_get_drvdata(pci));
}

上述代码的前提是:声卡指针已通过 PCI 驱动的数据接口(即 pci_set_drvdata())保存。

Header Files:

对于上述示例,至少需要包含以下头文件:

#include <linux/init.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/initval.h>

最后一个头文件(指 <linux/moduleparam.h>)仅在源码中定义了模块参数时才是必须的。如果代码被拆分为多个文件,那么没有模块参数定义的文件就不需要包含该头文件。 除了这些头文件之外:

  • 如果要处理中断,需要包含 <linux/interrupt.h>;

  • 如果需要进行 I/O 操作访问,需要包含 <linux/io.h>;

  • 如果使用了 mdelay() 或 udelay() 延时函数,还需要包含 <linux/delay.h>。

ALSA 接口,例如 PCM 和控制接口(control API),则定义在其他的 <sound/xxx.h> 头文件中。 这些头文件必须在 <sound/core.h> 之后包含。

声卡与组件的管理(Management of Cards and Components):

  • Card Instance

对于每一块声卡,必须分配一个 “card” 记录。 “card” 记录是声卡的核心管理结构,它负责管理该声卡上的所有设备(组件),例如 PCM、混音器(Mixer)、MIDI、合成器等。同时,该记录还保存声卡的 ID 和名称字符串,管理与之相关的 proc 文件入口,控制电源管理状态以及处理热插拔断开等情况。卡记录中的组件列表用于在驱动销毁时正确释放所有资源。 如前所述,要创建一个声卡实例,可以调用 snd_card_new() 函数:

struct snd_card *card;
int err;
err = snd_card_new(&pci->dev, index, id, module, extra_size, &card);

该函数接受六个参数:父设备指针、声卡索引号、ID 字符串、模块指针(通常为 THIS_MODULE)、额外数据空间的大小,以及用于返回声卡实例的指针。 其中,extra_size 参数用于为芯片相关的私有数据分配 card->private_data 空间。需要注意的是,这块私有数据是由 snd_card_new() 函数自动分配的。 第一个参数是 struct device 的指针,用于指定父设备。对于 PCI 设备,通常传入 &pci->dev

  • Components

在创建好声卡之后,你可以将各个组件(设备)附加到该声卡实例上。在 ALSA 驱动中,每个组件由一个 struct snd_device 对象表示。组件可以是一个 PCM 实例、控制接口、Raw MIDI 接口等。每个这样的实例对应一个组件条目。 组件可以通过 snd_device_new() 函数来创建:

snd_device_new(card, SNDRV_DEV_XXX, chip, &ops);

该函数接收以下参数:声卡指针、设备级别(SNDRV_DEV_XXX)、数据指针以及回调函数指针(&ops)。

其中,设备级别用于定义组件的类型,并决定其注册和注销的顺序。对于大多数组件,设备级别已在 ALSA 中预定义。对于用户自定义的组件,可以使用 SNDRV_DEV_LOWLEVEL。

此函数本身不会分配数据空间,数据需要在调用之前手动分配,并将其指针作为参数传入。在上述示例中,该指针(如 chip)会作为该实例的标识符使用。

对于 ALSA 中预定义的组件(如 AC97、PCM 等),它们在各自的构造函数内部会调用 snd_device_new()。组件的析构函数由回调函数中的指针指定,因此开发者无需手动调用组件的析构函数。

如果你希望创建自定义组件,需要在 ops 结构中设置 dev_free 回调指针为对应的析构函数,这样在调用 snd_card_free() 时该组件就能自动被释放。 接下来的示例将展示如何实现与芯片相关的私有数据结构。

  • Chip-Specific Data

与芯片相关的特定信息,例如 I/O 端口地址、资源指针或中断号(irq number),会被存储在芯片专用的数据结构(record)中:

struct mychip {....
};

一般来说,分配芯片记录有两种方式。

  • 通过 snd_card_new() 分配。

如前所述,你可以将额外数据的长度作为 snd_card_new() 的第 5 个参数传入,例如:

err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,sizeof(struct mychip), &card);

struct mychip 是芯片记录的结构体类型。 作为返回值,分配好的记录可以通过以下方式访问:

struct mychip *chip = card->private_data;

使用这种方法,你无需进行两次内存分配。该记录会随着声卡实例一起被释放。

  • Allocating an extra device.

在通过 snd_card_new() 分配声卡实例(第 4 个参数设为 0)之后,调用 kzalloc():

struct snd_card *card;
struct mychip *chip;
err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);
.....
chip = kzalloc(sizeof(*chip), GFP_KERNEL);

芯片记录结构体中至少应包含一个字段用于保存声卡指针:

struct mychip {struct snd_card *card;....
};

然后,在返回的芯片实例中设置该声卡指针:

chip->card = card;

接下来,初始化各个字段,并使用指定的 ops 将该芯片记录作为一个低层设备(low-level device)进行注册:

static const struct snd_device_ops ops = {.dev_free =        snd_mychip_dev_free,
};
....
snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);

snd_mychip_dev_free() 是设备的析构函数,它将调用真正的析构操作:

static int snd_mychip_dev_free(struct snd_device *device)
{return snd_mychip_free(device->device_data);
}

其中,snd_mychip_free() 才是真正的析构函数。 这种方法的缺点是:代码量明显更大。 但它的优点在于:你可以通过在 snd_device_ops 中设置相关回调函数,在声卡注册和断开连接时触发自定义的回调操作。 关于声卡的注册与断开连接,请参见下文的子章节。

注册与释放

在所有组件都分配完成后,通过调用 snd_card_register() 来注册声卡实例。从这一刻起,设备文件的访问才会被启用。

也就是说,在调用 snd_card_register() 之前,外部无法访问这些组件,因此是安全的。

如果该函数调用失败,应在调用 snd_card_free() 释放声卡资源后退出 probe 函数。

要释放声卡实例,只需调用 snd_card_free()。

如前所述,该调用会自动释放所有附加的组件。

对于支持热插拔的设备,可以使用 snd_card_free_when_closed()。 该函数将在所有设备文件被关闭后,再延迟销毁声卡实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

链结构与工作量证明7️⃣:用 Go 实现比特币的核心机制

链结构与工作量证明:用 Go 实现比特币的核心机制 如果你用 Go 写过区块、算过哈希,也大致理解了非对称加密、数据序列化这些“硬核知识”,那么恭喜你,现在我们终于可以把这些拼成一条完整的“区块链”。 不过别急,这一节我们重点搞懂两件事: 区块之间是怎么连接成“链”…

深入理解 React 样式方案

React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…

YOLO电力物目标检测训练

最近需要进行电力物检测相关的业务&#xff0c;因此制作了一个电力物数据集&#xff0c;使用YOLO目标检测方法进行实验&#xff0c;记录实验过程如下&#xff1a; 数据集标注 首先需要对电力物相关设备进行标注&#xff0c;这里我们选用labelme进行标注&#xff0c;使用无人机…

从阿里云域名解析异常事件看下域名解析过程

近日阿里云核心域名aliyuncs.com解析异常&#xff0c;涉及众多依赖阿里云服务的网站和应用&#xff0c;故障从发现到修复耗时5个多小时。本文简要分析下整个事件的过程&#xff0c;并分析域名解析的过程&#xff0c;了解根域名服务器在其中的作用&#xff0c;以了解。 1、aliyu…

应用分享 | 精准生成和时序控制!AWG在确定性三量子比特纠缠光子源中的应用

在量子技术飞速发展的今天&#xff0c;实现高效稳定的量子态操控是推动量子计算、量子通信等领域迈向实用化的关键。任意波形发生器&#xff08;AWG&#xff09;作为精准信号控制的核心设备&#xff0c;在量子实验中发挥着不可或缺的作用。丹麦哥本哈根大学的研究团队基于单个量…

基于规则的自然语言处理

基于规则的自然语言处理 规则方法形态还原&#xff08;针对英语、德语、法语等&#xff09;中文分词切分歧义分词方法歧义字段消歧方法分词带来的问题 词性标注命名实体分类机器翻译规则方法的问题 规则方法 以规则形式表示语言知识&#xff0c;强调人对语言知识的理性整理&am…

Vue Fragment vs React Fragment

文章目录 前言&#x1f9e9; 一、概念对比&#xff1a;Vue Fragment vs React Fragment&#x1f4e6; 二、使用示例对比✅ Vue 3 中使用 Fragment✅ React 中使用 Fragment &#x1f50d; 三、差异解析1. **使用方式**2. **传递属性&#xff08;如 key&#xff09;**3. **插槽系…

3D图像渲染和threejs交互坐标系入门知识整理

1. Games101 b站上面就有&#xff0c;看到第9节课基本对于图形渲染的原理和渲染过程有所了解。然后就可以使用openGL和GLSL。 点输入->投影到二维&#xff08;生成三角形面&#xff09;->光栅化为像素->z-buffer深度缓存判断层级->着色shading 2. openGL和GLSL 参…

跨平台架构区别

文章目录 重编译时轻运行时&#xff08;uniapp&#xff09;轻编译时重运行时&#xff08;Taro&#xff09; 重编译时轻运行时&#xff08;uniapp&#xff09; 对 vue 语法直接进行编译转换成对应平台代码&#xff0c;再通过添加运行时代码去补充能力&#xff0c;比如 nextTick…

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…

HarmonyOS 应用开发学习记录 - 从Windows开发者视角看鸿蒙开发

起始 2024年6月21日召开的华为开发者大会2024上宣布Harmony OS NEXT&#xff08;即鸿蒙星河版&#xff09;面向开发者启动Beta版&#xff0c;这也被人们称为“纯血鸿蒙”&#xff0c;它基于鸿蒙内核&#xff0c;不再兼容安卓开发的APP应用。 时至今日近一年了&#xff0c;我也有…

MySQL 事务管理与锁优化:确保数据一致性和并发性

在多用户并发访问的数据库系统中,如何确保数据的**一致性(Consistency)和并发性(Concurrency)**是一个核心挑战。**事务(Transaction)和锁(Lock)**是 MySQL 应对这一挑战的两大利器。事务保证了操作的原子性、一致性、隔离性和持久性,而锁机制则在并发环境下协调不同…

OpenPrompt 有没有实现连续提示词和提手动示词一起优化的

OpenPrompt 有没有实现连续提示词和提手动示词一起优化的 OpenPrompt 中连续提示词与手动提示词的混合优化 OpenPrompt 确实支持同时优化连续提示词(Soft Prompt)和手动设计的离散提示词(Manual Prompt)。这种混合优化策略可以结合两者的优势: 连续提示词:通过梯度下降…

Android添加语言列表

方式一 frameworks\base\packages\SettingsProvider\src\com\android\providers\settings\DatabaseHelper.java Settings.System.putString(context.getContentResolver(),Settings.System.SYSTEM_LOCALES, "ru-RU,en-US"); 方式2 packages/apps/Settings/src/co…

解决uniapp开发app map组件最高层级 遮挡自定义解决底部tabbar方法

subNvue&#xff0c;是 vue 页面的原生子窗体&#xff0c;把weex渲染的原生界面当做 vue 页面的子窗体覆盖在页面上。它不是全屏页面&#xff0c;它给App平台vue页面中的层级覆盖和原生界面自定义提供了更强大和灵活的解决方案。它也不是组件&#xff0c;就是一个原生子窗体。 …

如何保障服务器的安全

如何保障服务器的安全 以下是保障服务器安全的核心措施及实施建议&#xff1a; 一、基础设施层防护 物理安全 机房设置防火/防水/防雷系统&#xff0c;部署门禁监控设备。 服务器固定于抗震机架&#xff0c;避免物理损坏。 网络防护 防火墙规则&#xff1a;仅开放业务必要端…

C语言 学习 C程序的内存模型 2025年6月10日08:55:13

堆栈与内存管理 堆栈(Stack) : 后进先出(LIFO) 线性数据结构 包含压栈(Push) ,弹栈(Pop) 用途:临时存储数据(函数调用,局部变量) 管理:由系统自动分配和回收 速度快 ,容量有限! 堆栈代码示例: //堆栈示例 :局部变量 void getText() {int text20;//储存在堆栈中 } 内存管理…

CppCon 2015 学习:Implementing class properties effectively

这段内容讲的是C中“属性”&#xff08;Property&#xff09;的实现及其设计理念&#xff0c;并结合一个实际类Text来说明。中文理解如下&#xff1a; 关于“属性”&#xff08;Property&#xff09; 属性&#xff1a;介于类的字段&#xff08;field&#xff09;和方法&#…

[electron]预脚本不显示内联script

script-src self 是 Content Security Policy (CSP) 中的一个指令&#xff0c;它的作用是限制加载和执行 JavaScript 脚本的来源。 具体来说&#xff1a; self 表示 当前源。也就是说&#xff0c;只有来自当前网站或者当前页面所在域名的 JavaScript 脚本才被允许执行。"…