2024年9月

musl Linux 和 glibc 是两种不同的 C 标准库实现,它们在多个方面存在显著差异。

  1. 历史和使用情况


    • glibc 是较早且广泛使用的 C 标准库实现,具有较长的开发历史和广泛的社区支持。它被大多数 Linux 发行版采用,特别是在桌面和服务器环境中。
    • musl 是一个相对较新的实现,旨在提供更小、更快、更安全的 C 库。它被一些轻量级 Linux 发行版如 Alpine Linux 采用。
  2. 功能和兼容性


    • glibc 功能全面且复杂,支持多种扩展和功能,具有较高的稳定性和可靠性。
    • musl 虽然功能较少,但更严格地遵循 POSIX 标准,且代码量比 glibc 少得多,不需要额外的外部依赖库。musl 的二进制兼容性有限,但随着新版本的发布,兼容性在逐步提高。
  3. 性能和资源占用


    • musl 设计为轻量级,适用于嵌入式系统和资源受限的环境,能够创建小巧的静态可执行文件。
    • glibc 虽然功能强大,但在资源占用和性能方面可能不如 musl。
  4. 调试和开发支持


    • glibc 由于其功能更全面,通常在应用调试和开发初期更受推荐。
    • musl 在某些调试工具(如 gdb 和 ltrace)的支持上可能不如 glibc。
  5. 许可证和社区支持


    • musl 采用 MIT 许可证,比 glibc 的 LGPL 许可证更宽松,便于发布静态可执行文件。
    • glibc 有更大的社区支持和更广泛的文档资源。
  6. 特定领域的应用


    • musl 在嵌入式系统、容器化应用和轻量级发行版中表现出色。
    • glibc 在桌面和服务器环境中更为常见,支持更多的功能和扩展。


musl libc 和 glibc 在 .NET 应用程序中的兼容性问题主要体现在以下几个方面:

  1. musl libc 和 glibc 都提供了 C 标准库函数的实现,理论上应用程序应该能够互换使用。然而,实际中发现这两个库在标准 libc 函数使用的系统调用上存在差异。这意味着即使两个库都实现了相同的 C 标准库函数,它们在底层调用的操作系统功能可能不同,从而导致兼容性问题。
  2. 在运行时环境方面,glibc 和 musl 的处理方式也有所不同。例如,Java 的 jpackage 和其他启动器需要修复以确保在不同平台上正确使用适当的 JDK 动态库。这表明 .NET 应用程序在使用 musl libc 时可能会遇到类似的动态库解析问题。
  3. 如果 .NET 应用包含本机库,则 musl libc 可能不兼容。Alpine Linux 使用 musl libc,而某些应用程序如果依赖于 glibc 提供的本机库,可能会在 Alpine 系统上运行失败。这种情况下,开发者需要特别注意应用程序对本机库的依赖,并确保这些依赖在 musl libc 环境下可用。
  4. 尽管 musl libc 在性能和体积上有优势,但其功能和行为与 glibc 存在显著差异。例如,高版本的 glibc 可能引入了新的 API 或改变了现有 API 的行为,这可能导致在低版本系统上运行时出现错误。因此,在使用 musl libc 替代 glibc 时,开发者需要仔细测试和验证应用程序的行为一致性。

musl 和 glibc 在多个具体方面存在差异,这些差异可能导致 .NET 应用程序在两者环境下运行时出现兼容性问题。以下是主要的差异:

  1. 实现方式和功能


    • musl libc 是一个简单、轻量级的 C 标准库,设计目标是实现纯粹的 C 标准,没有任何额外的功能。相比之下,glibc 提供了更多的扩展功能,适用于多数 Linux 系统。
    • musl 支持静态链接、实时性和内存效率,而 glibc 则提供了更广泛的功能和兼容性。
  2. 性能


    • musl 的 malloc 系列函数和 memcpy 系列函数可能实现较慢,特别是在多线程环境中。
  3. 二进制兼容性


    • musl 和 glibc 的二进制兼容性非常有限。虽然一些 glibc 链接的共享库可以在 musl 下加载,但大多数 glibc 链接的应用程序如果直接替换为 musl 将会失败。
  4. 平台和操作系统支持


    • glibc 具有广泛的兼容性,支持许多架构和操作系统。相比之下,musl 对其他平台和操作系统的移植性较差。
  5. 类型定义和结构体


    • musl libc 中的类型定义很有特点,重要类型都定义为联合体,只负责分配内存,至于类型本身的语义,则由实现宏来重新定义。
  6. 本地库兼容性


    • 如果 .NET 应用程序包含本地库(即那些依赖于特定 libc 实现的库),那么 musl 和 glibc 的不同可能会导致兼容性问题。大多数 .NET 应用程序不包括本地库,因此在这种情况下不需要担心这个细节

musl libc 和 glibc 在 .NET 应用程序中的兼容性问题主要包括系统调用的差异、动态库解析的不同、本机库依赖性以及版本冲突和功能差异等方面。在 musl Linux 和 glibc Linux 环境下运行 .NET 应用程序时,需要注意以下几点:

  1. glibc 环境下的 .NET 运行


    • 在 glibc 环境下,.NET 应用程序可能会遇到 glibc 版本不兼容的问题。例如,在碰到的案例中,运行 .NET 自包含可执行文件时可能会出现 glibc 错误。解决方法包括确认和更新 glibc 库、使用 Docker 容器运行应用程序以及尝试其他 .NET 的发行版。
    • 在 Linux 上,glibc 是主要的 C 库,许多 Linux 发行版都使用它。因此,.NET 应用程序在这些发行版上通常可以正常运行,前提是 glibc 版本与 .NET 运行时兼容。
  2. musl 环境下的 .NET 运行


    • musl 是一个轻量级的 C 库,常用于基于 musl 的 Linux 发行版,如 Alpine Linux。在 musl 环境下,.NET 应用程序可能会遇到 musl 版本不匹配的问题。例如,在 Stack Overflow 的讨论中,用户尝试降级 .NET 版本以匹配 musl 库,但遇到了加载库的问题。
    • .NET Core 3.0 及更高版本支持 musl,因此可以在 musl 环境下运行 .NET 应用程序。然而,musl 与 glibc 在某些方面存在差异,可能会导致兼容性问题。
  3. 兼容性和版本问题


    • 在 musl 和 glibc 环境下运行 .NET 应用程序时,需要注意 libc 库的版本兼容性。例如,在 Alpine 3.12 中,musl-libc 的版本是 1.1.24,而 .NET 6 的二进制文件可能缺少某些符号,导致运行问题。
    • 在 Linux 上部署 .NET 程序时,可能会遇到 .NET 运行环境与操作系统之间的不兼容性。因此,选择合适的 .NET 版本和 libc 库版本非常重要。
  4. 最佳实践

  • 为了实现最佳兼容性,建议选择长期支持版本(LTS)的 .NET 版本。
  • 在 musl 环境下,可以尝试降级 .NET 版本以匹配 musl 库,或者使用 Docker 容器来隔离运行环境。
  • 在 glibc 环境下,确保 glibc 库的版本与 .NET 运行时兼容,必要时进行升级。

在使用 Docker 容器在 musl 或 glibc 环境下运行 .NET 应用程序时,以下是一些最佳实践:

  1. 选择合适的镜像基础层


    • 如果你的应用程序需要 glibc(GNU C Library),可以选择包含 glibc 的基础镜像。例如,可以使用
      alpine
      镜像,它提供了 glibc 兼容性层
      libc6-compat
    • 如果你的应用程序不需要 glibc,或者你希望减少镜像大小,可以选择基于 musl 的镜像,如
      alpine
      镜像 。
  2. 多阶段构建


    • 使用多阶段构建来优化镜像大小和构建过程。这样可以在一个阶段中安装所有依赖项和工具,在另一个阶段仅复制最终的可执行文件到镜像中 。
  3. 解决版本冲突


    • 在 Docker 容器中,GLIBC 版本冲突可能导致程序无法正常运行。可以通过升级 GLIBC 库来解决这一问题,并提升系统的兼容性 。
  4. 初始化 Docker 资产


    • 使用
      docker init
      命令创建必要的 Docker 资产,包括 Dockerfile 和其他相关配置文件。这将帮助你更好地管理容器化应用程序 。
  5. 容器化与微服务架构


    • 微服务架构支持水平扩展,允许根据需要独立地扩展每个服务。可以在容器化环境中部署,如 Docker 和 Kubernetes,以实现更高的弹性和资源利用率 。
  6. 跨平台开发与部署


    • 利用 .NET Core 的跨平台特性,确保应用程序在不同操作系统上都能高效、便捷地开发与部署

总结来说,在 musl Linux 和 glibc Linux 环境下运行 .NET 应用程序时,需要特别注意 libc 库的版本兼容性,并根据具体情况选择合适的 .NET 版本和运行环境。

绪论

本合集将详细讲述如何实现基于群只能遗传算法的五子棋AI,采用C++作为底层编程语言
本篇将简要讨论实现思路,并在后续的文中逐一展开

了解五子棋

五子棋规则


五子棋是一种经典的棋类游戏,规则简单却充满策略性。游戏在一个19×19的棋盘上进行(也可以使用13×13或15×15的棋盘)。游戏的目标是率先在棋盘上连成五个相同颜色的棋子(横向、纵向或斜向)。

基本规则:

  1. 棋子:游戏使用两种颜色的棋子,通常为黑白两色。
  2. 落子:玩家轮流在棋盘上放置自己的棋子。
  3. 胜利条件:第一个在直线上(横向、纵向或对角线)连成五个棋子的玩家获胜。

五子棋操作简单,规则易懂,但需要很高的策略和技巧才能赢得比赛。

人类玩家是如何下五子棋的?

以下是一些五子棋对决的思路:

控制中心区域

  • 中心位置的重要性:棋盘中心的控制对游戏至关重要。控制中心区域可以给你更多的机会去创建和阻止对方的五子连线。

创建威胁

  • 连线威胁:尽量让对方必须防守而不能专注于自己的进攻。
  • 双活三:如果形成两个三子连线,并且这两个连线不会被对方轻易阻挡,就能够在几步内取得胜利。

防守对方的连线

  • 观察对方的棋子布局:注意对方棋子的排列,尤其是对方试图形成的三子、四子连线。
  • 及时阻挡:如果对方有连续的三子或四子的排列,应该优先阻挡对方的连线。

预判对方策略

  • 猜测对方意图:了解对方的策略,预测对方的下一步棋,提前做出相应的防御或进攻。

AI应该如何模仿?

为了让AI棋手学会下五子棋,甚至超越人类玩家的水平,首先应当有以下步骤:

  1. 理解棋盘信息:将棋盘的状态转换为程序能够处理的格式。这通常包括将棋盘上每个位置的状态(如空白、黑子或白子)编码为特定的数据结构,以便程序可以进行分析和处理。

  2. 设定行为集合:定义AI可以执行的操作范围。在五子棋中,AI可以在棋盘上任意未被占据的位置落子。

  3. 设定决策模式:确定AI的决策方式。本例中,AI采用贪心策略,即在每一步中选择预期回报最高的行动。贪心策略通过评估每个可能的落子位置的即时收益,选择对当前局势最有利的行动。

理解棋盘信息

理论上来讲,能够给AI提供的信息越多,那么AI做出的决策质量就越高,对于棋盘信息可以以格子为单位,评估该格子对于己方、和敌方的价值。

举例来说,如果在此处落子,敌方可以构成五子连珠,那么对于地方而言这是非常高价值的格子,那么在己方回合,当务之急是在此处落子,阻止对方胜利,除非在其它位置落子己方可以胜出。

对此,我们可以对棋盘上每一个可行位置进行打分,评估其对于己方、敌方的价值。

如何定义该位置对己方的价值?


一枚棋子可以在四个方向上与其它棋子连成五子,即:水平、竖直、对角线、主对角线

description

可以采用如下方法判断在某一具体方向上的价值

  1. 在四个方向中选择某一方向
  2. 向正方向、负方向分别查找4格,如遇到空格或敌方棋子则提前停止
  3. 统计己方棋子个数,以及两端的被遮拦情况。

例如对于下图,在该处落子后,形成水平方向上的两子连珠,且一端有遮拦一端无遮拦

description

一共可能形成如下几种情形,我们可以依据经验公式评估其价值。

子数 1子 2子 3子 4子 5子
无遮拦 MAX
一端遮拦 - -
两端遮拦 - - - -

'-' 表明该位置在该方向上具有的价值较低,不予考虑。然而,如果其他方向上的情况更有利,那么该位置的价值仍然可能非常高。。

接下来,我们可以讨论棋子在多个方向上的价值,一般来说,仅需要考虑最高价值的两个方向。

这是因为两个活三(无遮拦的三子连珠)足以致胜,三个活三并没有明显优势。

价值 最优方向 次优方向
Lv1 MAX ?
Lv1 ?
Lv2
Lv3
Lv3 -
Lv4
Lv4 -
Lv4 - -

“?” 指代任意情况,例如(MAX-?)对应了(MAX-MAX)、(MAX-①)、(MAX-②)、(MAX-③)、(MAX-'-')

该定义方式将行为的优先级分为了四个等级

  • Lv1:下子直接取胜,或在一回合内取胜。
  • Lv2:下在大概率在若干回合内取胜。
  • Lv3:能够迫使对方一直防御。
  • Lv4:收益较低。

如何综合攻防?


若要综合攻防,必须将具体位置对敌方的价值考虑进去。倘若某一位置对敌方来说是高价值的,那我们在此处落子可以破坏敌方阵型,削弱敌方价值,同样我们可以给出如下价值表

综合价值排序 己方价值 敌方价值 对应的奖励数值
1 Lv1 ? \(2^{20}\)
2 ? Lv1 \(2^{16}\)
3 Lv2 ? \(2^{12}\)
4 Lv2 \(2^{8}\)
5 Lv3 \(2^{4}\)
6 Lv4 \(2^{0}\)

“?” 指代任意情况,例如(Lv1-?)对应了(Lv1-Lv1)、(Lv1-Lv2)、(Lv1-Lv3)、(Lv1-Lv4)
在进行判断时,应当从上往下逐一判断。

这里给出的奖励数值仅供参考。

总结


在本小节中,我们精心构建了一种全新的综合评估方法,旨在全面衡量棋盘上的每个格子对于己方和敌方的战略价值。通过设计一系列精细的量化指标,我们赋予了AI/计算机深入解读棋盘格局的能力,使其能够准确判断每个格子的具体价值。这一方法为AI/计算机制定决策提供了坚实的数据支撑。

行为集规定与决策制定

为了使AI做出高效的决策,我们首先需要定义一套合适且简洁的行为集合。这意味着AI在做出选择时,不必每次都逐一考虑棋盘上的所有位置。在此基础上,我们需要开发一种策略,帮助AI从众多可能的决策中筛选出最为恰当的一个。通过这种方式,AI能够在复杂的环境中迅速而准确地做出最佳决策。

ROI 感兴趣区域


倘若上一轮你在棋盘中心落子,那么下一轮你不应当在棋盘的最角落落子。

一般来说,在落子时,只有与已有棋子(无论是己方还是敌方)邻接的位子才具有价值,首先我们定义邻接。

考虑棋盘上只有一子的情形,规划出与其具有高价值“联动”的区域如下:

description

可以给出更具体的定义:

倘若一个格子位于另一格子的水平、竖直、主对角线或副对角线方向上,并且两格子距离小于等于2,那么称这两个格子为
邻接
关系。进一步的,称距离为1为强邻接,距离为2为弱邻接。

进一步的,我们定义感兴趣区域如下:

满足以下要求之一的空格子为感兴趣区域:

  1. 该格子是棋盘
    正中心
  2. 该格子与至少一个
    己方
    棋子所处格子存在
    邻接
    关系。
  3. 该格子与至少一个
    敌方
    棋子所处格子存在
    强邻接
    关系。

下图给出了己方落子ROI区域的示例,其中红色为己方棋子,蓝色为对方棋子,灰色表示感兴趣区域。
description

决策进行


在进行决策前,我们可以评估感兴趣区域中所有格子的价值,假定ROI中格子的个数是
\(N\)
,格子的价值分别是
\(x_0, x_1, ..., x_{N-1}\)
,我们可以采用下述两种方法选择决策

硬最大值 hardmax

选择奖励最大的决策,即

\[h(\mathbf{x}) = \arg\max_{i} x_i
\]

软最大值 hardmax
不同于硬最大值,软最大值以一定几率接受非最优解,其包含一个常量
\(K\)
,常量K越大表示对低价值决策的接受程度越大,当常量
\(K\to 0\)
时,软最大值退化为
硬最大值
;当常量
\(K\to +\infty\)
时,软最大值退化为
随机抽取

\[\text{softmax}(x_i) = \frac{e^{x_i/K}}{\sum_{j=1}^N e^{x_j/K}}
\]

结语

下一篇中我们将继续讨论如何训练AI。

在最后,给出一个流程图供大家参考,在后文中我将详细讨论。

description

先看一下效果吧:

我们直接通过改造一下原版的TreeView来实现上面这个效果

我们先创建一个普通的TreeView

代码很简单:

        <TreeView>
            <TreeViewItem Header="人事部"/>
            <TreeViewItem Header="技术部">
                <TreeViewItem Header="技术部-1"/>
                <TreeViewItem Header="技术部-1"/>
            </TreeViewItem>
            <TreeViewItem Header="财务部"/>
        </TreeView>

实现的效果如下:

如果把这个当成是项目的菜单栏,应该会被领导骂死,一个是不够灵活,数据是写死的;二是样式不好看,只有点文字部分才会展开。

创建一下模板

直接在设计器中右键我们的item,编辑副本,点击确定,我们会得到下面一段代码

里面有一个叫Bd的border,我们把这个border的背景色去掉,然后我们自己去创建两个新的border

<Border Background="Transparent" Margin="-200,0,-200,0" Grid.ColumnSpan="4"/>
<Border x:Name="bd1" Background="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"
Margin="-200,0,-200,0" Visibility="Hidden" Grid.ColumnSpan="4">
<Border.Effect>
<DropShadowEffect BlurRadius="5" ShadowDepth="2"/>
</Border.Effect>
</Border>
<ToggleButton x:Name="Expander" ClickMode="Press"IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource Mode=TemplatedParent}}"Style="{StaticResource ExpandCollapseToggleStyle}"/> <Border x:Name="Bd" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"> <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Border>

上面红色部分是我们新增的两个border,原本的叫Bd的border,我们只保留紫色部分的属性.

原本的代码里面有两个关于Bd的trigger

我们取名为bd1的border,最开始的Visibility设置的是Hidden,我们替换一下关于Bd的trigger,让它变成当IsSelected是true的情况下,让bd1的Visibility变成Visible.

<Trigger Property="IsSelected" Value="true">
    <Setter Property="Visibility" TargetName="bd1" Value="Visible"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="IsSelected" Value="true"/>
        <Condition Property="IsSelectionActive" Value="false"/>
    </MultiTrigger.Conditions>
    <Setter Property="Visibility" TargetName="bd1" Value="Visible"/>
<Setter Property="Background" TargetName="bd1" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>

再运行一下,看一下效果

基本上已经算是成功一半了,但是这个时候,我们的菜单只有一个有效果,其他的还是原来的样式,那是因为我们只有一个TreeViewItem使用了我们写的效果

如果我们每一个TreeViewItem都复制一下这句Style="{DynamicResource TreeViewItemStyle1}" ,是不是显得很呆,而且这只是在我们的菜单很少的情况下,如果菜单很多,这个方法就不可行。

所以这里我们用一个TreeView的ItemContainerStyle来操作一下

        <Style x:Key="treeViewStyle1" TargetType="{x:Type TreeView}" BasedOn="{StaticResource {x:Type TreeView}}">
            <Setter Property="ItemContainerStyle" Value="{StaticResource TreeViewItemStyle1}"/>
        </Style>

我们创建一个类型是TreeView的style,把它的ItemContainerStyle设置成我们之前添加的那个style,然后我们把这个style放到我们的TreeView上

这个时候我们再运行就会发现首级菜单的样式都实现我们想要的效果了,但是子集菜单还是原来的样式

我们在代码里面添加下面一个方法

        private voidApplyItemContainerStyle(ItemsControl itemsControl)
{
foreach (var item initemsControl.Items)
{
var treeViewItem = item asTreeViewItem;if (treeViewItem != null)
{
treeViewItem.Style
=treeview1.ItemContainerStyle;
ApplyItemContainerStyle(treeViewItem);
}
}
}

然后我们在构造函数里面把我们的TreeView当做是参数传进去

这个方法就是把所有的item和item的子项都设置成treeview的ItemContainerStyle;

我们再启动一下项目,就会发现效果是我们想要的效果了

到这里其实大部分效果都实现了,基本上也可以向领导交差了;

但是还缺少一个数据可拓展性和一个图标的功能,我们先看一下数据可拓展性

在平时的项目里面,一般都会有很多个不同的项目,每个项目可能都有好多个菜单,有的项目还想隐藏某一些菜单,我们总不能所有项目都通过visible属性来设置吧

特别是报表功能可能会有几十个,所以我们需要用到一个东西叫数据模板:HierarchicalDataTemplate;

我们先创建一个类

    public classTreeViewModel
{
public string Header { get; set; }public ObservableCollection<TreeViewModel> Children { get; set; }
}

然后回到设计器里面,把我们的代码改成下面的代码

<TreeView Style="{DynamicResource treeViewStyle1}" x:Name="treeview1">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:TreeViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Height="40" Orientation="Horizontal">
                <TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

对比一下红色部分的绑定,和类的属性,就能知道这个数据模板怎么用了.

再到构造函数里面去添加数据

public ObservableCollection<TreeViewModel> MenuCollection { get; set; }publicMainWindow()
{
InitializeComponent();


MenuCollection
= new ObservableCollection<TreeViewModel>()
{
new TreeViewModel
{
Header = "人事部"
},
new TreeViewModel
{
Header = "技术部",
Children = new ObservableCollection<TreeViewModel>
{
new TreeViewModel { Header = "技术部-1"},
new TreeViewModel { Header = "技术部-2"},
}
},
new TreeViewModel
{
Header = "财务部",
},
};

treeview1.ItemsSource =
MenuCollection;
}

注意这两段标红的代码,我们用一个集合MenuCollection模拟一下我们从数据库或者其他地方查询出来的菜单集合,然后把它做为数据源给treeview就可以了

再运行一下项目,它就差不多实现我们想要的效果了,现在再去找领导交差,领导还会夸你做的不错,只是还差一个图标了,这个就是锦上添花的东西了.

我们百度搜索一下  阿里ICON,去到官网里面,创建一个自己的账号,然后搜索一些自己喜欢的图标

把自己喜欢的图标添加到自己的项目中去,这里的项目名很重要,我取的是  FatSheep

在到我的项目里面去把这个资源文件下载到自己的项目中

下载下来的文件,我们把ttf后缀的文件添加到我们的项目里面去

把它作为资源引入到代码里面

<FontFamily x:Key="FatSheep">pack:application:,,,/自己项目的名字;component/Resources/iconfont.ttf#FatSheep</FontFamily>

记得修改一下自己的项目名字,我取的是TreeViewDemo,改成自己的项目名就好了,最后的结尾,是FatSheep,记得改成自己的ICON项目名称

接着我们在TreeViewModel里面添加一个Icon属性

    public classTreeViewModel
{
public string Header { get; set; }public string Icon { get; set; }public ObservableCollection<TreeViewModel> Children { get; set; }
}

然后我们在数据源里面添加一下数据

MenuCollection = new ObservableCollection<TreeViewModel>()
{
newTreeViewModel
{
Header
= "人事部",
Icon
= "\ue71c"},newTreeViewModel
{
Header
= "技术部",
Icon
= "\ue71c",
Children
= new ObservableCollection<TreeViewModel>{new TreeViewModel { Header = "技术部-1", Icon="\ue71c"},new TreeViewModel { Header = "技术部-2" , Icon="\ue71c"},
}
},
newTreeViewModel
{
Header
= "财务部",
Icon
= "\ue71c"},
};

设计器里面添加一下显示部分的代码

<TreeView Style="{StaticResource treeViewStyle1}" x:Name="treeView1" BorderThickness="0,0,1,0" Grid.Column="1">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:TreeViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Height="40" Orientation="Horizontal">
                <TextBlock Text="{Binding Icon}" VerticalAlignment="Center" FontFamily="{StaticResource FatSheep}" Margin="0,0,5,0"/>
                <TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

再启动项目,功能就完成了

这个笑脸是怎么来的了

那是因为我自己的项目里面添加了一个笑脸

我们复制一下这个代码,   &#xe71c;  我们把它改成 \ue71c,这是一个转义字符,就这样我们就能添加如何自己喜欢的图标了。

项目github地址:
bearhanQ/WPFFramework: Share some experience (github.com)

QQ技术交流群:332035933;

欢迎进群讨论问题,不论是winform,还是wpf,还是.net core的,还有很多萌妹.

本文使用的是dev-c++,如果涉及到VC++中不一样的操作,也会适当进行区分。

项目一:创建DLL

1、创建一个DLL类型的项目,当前命名为dlltest,并选择合适的路径进行保存。

2、在生成的预设置代码中,加入如下代码

//这是头文件dll.h
#ifndef _DLL_H_#define _DLL_H_

#if BUILDING_DLL
#define DLLIMPORT __declspec(dllexport)
#else
#define DLLIMPORT __declspec(dllimport)
#endif

classDLLIMPORT DllClass
{
public:
DllClass();
virtual ~DllClass();void HelloWorld(char*info);
};
extern "C"
{
DLLIMPORT int HW(int n);
}

DLLIMPORT int func(int n);
#endif
/*这是主体文件dllmain.cpp*/#include"dll.h"#include<windows.h>DllClass::DllClass()
{

}

DllClass::
~DllClass()
{

}
void DllClass::HelloWorld(char*info)
{
MessageBox(
0, info,"Hi",MB_ICONINFORMATION);
}

DLLIMPORT
int HW(int n)
{
return n;
}

DLLIMPORT int func(int n)
{
return
n;
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
switch(fdwReason)
{
caseDLL_PROCESS_ATTACH:
{
break;
}
caseDLL_PROCESS_DETACH:
{
break;
}
caseDLL_THREAD_ATTACH:
{
break;
}
caseDLL_THREAD_DETACH:
{
break;
}
}
/*Return TRUE on success, FALSE on failure*/ returnTRUE;
}

在上面的代码中,我们加入了HW和func两个导出函数,以及一个DllClass(自动生成)导出类。

点击编译后,我们可以在项目文件夹中,看到dlltest.dll,这就是我们需要的目标动态链接库。libdlltest.a则是vc里需要用到的lib文件。

3、extern "C"说明

当前可以用记事本打到libdlltest.def文件,可以看到如下内容:

加了extern "C"的HW函数地址偏移量还是HW,没有加extern "C"的func函数,地址偏移量变成了_Z4funci。这个地址在动态调用导出函数的过程中会用到。

项目二:动态调用dll导出的函数

1、再创建一个C++项目,将项目一生成的dll文件放入项目文件夹中:

2、使用LoadLibrary和和GetProcAddress动态载入动态链接库,并调用导出的函数:

#include <iostream>#include<windows.h>
using namespacestd;intmain()
{
HMODULE hMod
=LoadLibrary("dlltest.dll");if(hMod==NULL)
{
cerr
<<"load lib error";return 1;
}

Func f
=(Func)GetProcAddress(hMod,"HW");
cout
<<f(200);

FreeLibrary(hMod);
return 0;
}

在GetProcAddress中,调用HW函数可以直接传入偏移量HW;

如果调用func函数,则要传入偏移量“_Z4funci”;因为func函数没有声明为extern "C"。

3、特别备注,当前这种方式无法使用LoadLibrary和GetProcAddress获取导出类。

因为GetProcAddress获取的是函数的地址偏移量,为了可以动态使用导出的类,必须使用将一个纯虚函数做为基类,将导出创建和销毁类的函数。具体做法如下:

//dll.h
#include <stdlib.h>#include<stdio.h>

classvirtualXXX
{
public:virtual void functionOne() = 0;virtual void functionTwo() = 0;
};
#if defined(_WINDOWS)#ifdef XXX_API#define XXX_API __declspec(dllexport) #else #define XXX_API __declspec(dllimport) #endif #else #define XXX_API #endif class XXX_API xxx : publicvirtualXXX
{
public:voidfunctionOne()
{
printf (
"One\n");
}
voidfunctionTwo()
{
printf (
"Two\n");
}
};
extern "C" XXX_API virtualXXX *create();extern "C" XXX_API void delete_object( virtualXXX *p );//dll.cpp virtualXXX *create()
{
return ( newxxx() );
}
void delete_object( virtualXXX *p )
{
if( p )
{
deletep;
p
=NULL;
}
}

动态调用:

#include <Windows.h>typedef virtualXXX*(fun_create)(void);
fun_create
* vc_create =NULL;intmain()
{
HINSTANCE dllHandle
=NULL;
dllHandle
= LoadLibrary( "Win32_Test_dll.dll");
vc_create
= ( fun_create* )GetProcAddress( dllHandle,"create");
virtualXXX
* xxxHandle =vc_create();


xxxHandle
->functionOne();
xxxHandle
->functionTwo();

delete_object(xxxHandle);
}

这个方法参考文章
C++动态库导出类,及使用
,博主未实际进行测试。

项目三:静态调用导出的类

静态调用dll,在VC++需要头文件、dll和对应的lib文件(即项目一中生成的 libdlltest.a)。然后再使用#pragma comment(lib,"lib文件路径")对编译器进行配置lib路径,之后再进行调用。详细过程可以参考
《c++生成DLL并调用》

本文着重调论Dev-C++下的静态调用。对于MinGW64静态调用dll,只需要dll文件和相关的头文件,项目结构如下:

其中main.cpp中调用类的代码如下:

#include <iostream>#include<windows.h>#include"dll.h" 
using namespacestd;intmain()
{
DllClass c;
char str[]="hello";
c.HelloWorld(str);
return 0;
}

可以看到,无需在代码中进行任何设置。因为只有在链接的过程c++才会去找DllClass这个类的真实地址。

在编译成exe时,有如下两种方法:

方法一、可以使用命令行进行编译:

通过cmd进入main.cpp所在文件夹路径,运行:
g++ -o main.exe main.cpp -I . -L . -ldlltest

即可编译生成可执行文件exe。编译参数说明如下:

-I搜索头文件的目录
-I .在当前文件夹下搜索头文件
-L搜索动态库的目录
-L .在当前文件夹下搜索动态库

方法二、将参数加入编译选项中:

如果觉得用命令行编译太麻烦,可以将-I -L和-l加入Dev-C++的编译器选项中。

这样点击“编译运行”就可以正确找到对应的dll进行编译链接,正确生成exe文件。

本文关于Dev-C++创建并调用动态链接库dll到这里就结束了,欢迎大家指正:)

前言

前几天看到一篇文章,《
消费降级,我的订阅服务瘦身
》。
自己平时花钱有点大手大脚的,也没有统计个每个月固定的开销,现在正好趁这个机会记录一下。现在挣钱不容易,看下哪些开销可以进行降级。

腾讯云 - 服务器

分类 周期及成本 需要程度
网络服务 510元/年 需要/续订

这个服务器是平时自建博客,小功能的必选项,这个没办法一定要续费(不知道有没有便宜的渠道)

域名

分类 周期及成本 需要程度
网络服务 32元/年 需要/续订

和服务器功能类似,必须续费产品

上网

分类 周期及成本 需要程度
网络服务 8元/月 需要/续订

刚需,没办法。我是按需充值,每个月大概8元 10G吧

视频会员(爱奇艺 + B站)

分类 周期及成本 需要程度
生活服务 451元/年 不需要/不续订

之前追剧的时候,订的会员。看来是不需要了,准备关了

话费(3个手机号)

分类 周期及成本 需要程度
生活服务 55元/月 需要/续订

必须,这个不能不续订

CDN

分类 周期及成本 需要程度
网络服务 10元 不需要/不续订

上个月欠费了,直接给关了,感觉自己的服务也没有必要上CDN

其他(健身卡、党费等其他)

分类 周期及成本 需要程度
其他 1000元/年 部分

健身卡,准备不续了,能省下不少钱。

最后

把其他的一些开支+视频网站关闭,每年可以省下小1500元,还是很多的。近期在考虑要不要开通iCloud来保存照片,犹豫中