2024年2月

WebAssembly程序总是以模块来组织,模块是基本的部署、加载和编译单元。在JavaScript编程接口中,模块通过WebAssembly.Module类型表示。WebAssembly.Module通过加载的.wasm二进制文件创建而成,它承载了描述wasm模块的元数据,类似于描述程序集的Assembly对象。WebAssembly.Module自身是只读且无状态的,有状态的是根据它结合指定的导入对象创建的模块实例,后者通过WebAssembly.Instance表示。这两个类型提供了几个核心API,解析我们就通过它们来介绍WebAssembly的这两个核心对象(
源代码
)。

  • WebAssembly.Module.customSections
  • WebAssembly.Module.imports
  • WebAssembly.Module.exports
  • WebAssembly.Instance.exports

一、WebAssembly.Module.customSections

我们在wasm模块中定义任意不同类型的成员,在编译生成的.wasm二进制文件中,这些成员会根据类型分布到对应的区域(section)中,确切地说“已知区域(known section)”。除了针对具体成员类型的已知区域, wasm模块还可以开辟一组命名的“自定义区域(custom section)”,静态方法WebAssembly.Module.customSections返回的ArrayBuffer指定名称的自定义区域在指定模块中的内容。目前的WebAssembly模块中大体可以定义如下11种类型的成员,对应的已知区域具有固定的代码(1-11)。

image

自定义区域的区域代码均为0,但是我们可以给它们进行命名。自定义区域赋予了我们在wasm模块文件中内嵌任意数据的能力。但是我们不能在.wat程序中为生成的.wasm添加自定义区域,但是如果我们在执行wat2wasm命令添加“--debug-names ”开关,编译后的.wasm中将自动添加一个名为“name”的自定义区域,该区域会将WAT程序中针对各种对象的命名(程序执行的时候不需要这些名称)存储起来,它们将会显示在我们的“调试视图”中以增强可读性。为了演示针对自定义区域的读取,我们采用WAT格式定义了如下这个程序(文件名为app.wat)。

(module
   (func (import "imports" "func"))
   (memory (import "imports" "memory") 1)
   (table (import "imports" "table") 4 externref)
   (global (import "imports"  "global") (mut i32))

   (func (export "func"))
   (memory (export "memory") 1)
   (table (export "table") 4 externref)
   (global (export "global") (mut i32) (i32.const 0))
)

如上面的代码片段所示,我们导入和导出了4种类型的对象(函数、Memory、Table和Global)。由于我们使用了两个Memory对象,wat2wasm编译工具在默认情况下并不支持,所以除了添加--debug-names开关,还需要添加--enable-multi-memory开关,完整的命令行如下所示。

wat2wasm app.wat -o app.wasm --enable-multi-memory --debug-names

针对自定义区域“name”的读取按照如下的形式实现在index.html页面中:在调用fetch函数成功下app.wasm模块文件后,我们之间调用构造函数根据得到的字节内容创建了一个WebAssembly.Module对象,然后将它和区域名称“name”作为参数调用静态方法customSections。

<html>
    <head></head>
    <body>
        <script>
           fetch("app.wasm")
                .then((response) => response.arrayBuffer())
                .then(bytes => {
                    var module = new WebAssembly.Module(bytes);
                    var sections = WebAssembly.Module.customSections(module, "name");
                    console.log(sections);
                })
        </script>
    </body>
</html>

得到的自定义区域内容体现为一个ArrayBuffer对象,它在网页调试控制台中有如下的显示。

image

二、WebAssembly.Module.imports & WebAssembly.Module.exports

WebAssembly.Module还定义了两个名称为imports 和exports的静态方法,我们可以利用它们得到wasm模块导入和导出对象的描述,接下来我们就将它们应用到我们的演示程序中。在index.html页面中,WebAssembly.Module对象创建出来后,我们将它作为参数传入上述两个静态方法中,然后将它们组合成又给对象,并以JSON的形式直接显示在页面里。

<html>
    <head></head>
    <body>
        <pre><code id="code"></code></pre>
        <script>
           fetch("app.wasm")
                .then((response) => response.arrayBuffer())
                .then(bytes => {
                    var module = new WebAssembly.Module(bytes);
                    var imports = WebAssembly.Module.imports(module);
                    var exports = WebAssembly.Module.exports(module);
                    document.getElementById("code").innerText = JSON.stringify({"imports":imports, "exports":exports}, null, 2);
                })
        </script>
    </body>
</html>

针对导入/导出描述的JSON以下的形式承载的页面中,可以看出导入描述中包含了每个导入对象的路径(“{module}.{name}”)和类型(function、table、memory和global)。导出描述包含了每个导出对象的导出名称和类型。

image

三、WebAssembly.Instance.exports

WebAssembly.Module仅仅是对加载的wasm模块的描述,宿主程序真正消费的是根据它创建的实例,该实例通过WebAssembly.Instance类型表示。WebAssembly.Instance构造函数具有两个参数,分别是提供描述元数据的WebAssembly.Module和指定的导入对象。宿主程序能够使用的仅仅是该实例导出的成员,它们通过WebAssembly.Instance对象的exports属性暴露出来。在如下所示的代码片段中,我们对index.html作了相应的修改来演示WebAssembly.Instance对象的导出列表。

<html>
    <head></head>
    <body>
        <script>
           fetch("app.wasm")
                .then((response) => response.arrayBuffer())
                .then(bytes => {
                    var module = new WebAssembly.Module(bytes);
                    var imports = {
                        "func": ()=> {},
                        "memory":  new WebAssembly.Memory({ initial: 1 }),
                        "table": new WebAssembly.Table({ initial: 4, element: "externref" }),
                        "global": new WebAssembly.Global({ value: "i32", mutable:true, initial:0})
                    };
                    var instance = new WebAssembly.Instance(module, {imports});
                    console.log(instance);
                })
        </script>
    </body>
</html>

如代码片段所示,在得到描述wasm模块的WebAssembly.Module对象后,我们创建出对应的导入对象,并将它们作为参数调用构造函数将WebAssembly.Instance对象创建出来,并将其exports属性代表的导出对象输出到调试控制台上。下图展示了导出列表在控制台中的输出,可以看出它们与app.wat程序是一致的。

image

本文示例代码已上传至我的
Github
仓库https://github.com/CNFeffery/dash-master


大家好我是费老师,
Dash
不久前发布了其
2.15.0
版本,新增了一些实用的特性,下面我们就来一起get其中的重点

本篇是mygin的第八篇,参照gin框架,感兴趣的可以从
Mygin第一篇
开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现。

目的

  • sync.Pool 的作用介绍
  • mygin中使用sync.Pool

sync.Pool 的作用

先看看官方文档怎样说的吧,我截取了官方文档的第一句。

// A Pool is a set of temporary objects that may be individually saved and retrieved.
.....
  • 简单翻译一下的意思是:池是一组可以单独保存和检索的临时对象。既然可以单独保存和检索的临时对象,对于大量重复地创建许多对象,造成 GC 的工作量巨大。而mygin的模式是
    责任链模式
    ,因此满足使用 sync.Pool。
  • 一个 Pool 可以安全地由多个 goroutine 同时使用。池的目的是缓存已分配但未使用的项目以供以后重用,从而减轻垃圾回收器的压力。
  • sync.Pool 是可伸缩的,同时也是并发安全的,其大小仅受限于内存的大小。sync.Pool 用于存储那些被分配了但是没有被使用,而未来可能会使用的值。这样就可以不用再次经过内存分配,可直接复用已有对象,减轻 GC 的压力,从而提升系统的性能。
    以上都是源于官方文档翻译的,文档中还提到fmt包中,打印也使用了sync.Pool,感兴趣的可以点进源码查看。

sync.Pool 使用

sync.Pool 的使用方式非常简单:
只需要实现
New
函数即可。对象池中没有对象时,将会调用
New
函数创建,我使用了mygin中的context

创建

var contextPool = sync.Pool{
	New: func() interface{} {
		return new(Context)
	},
}

使用和归还

c := contextPool.Get().(*Context)
json.Marshal(c)
contextPool.Put(c)

测试

func BenchmarkUnmarshal(b *testing.B) {
	for n := 0; n < b.N; n++ {
		c := &Context{}

		json.Marshal(c)
	}
}

func BenchmarkUnmarshalWithPool(b *testing.B) {
	for n := 0; n < b.N; n++ {
		c := contextPool.Get().(*Context)
		json.Marshal(c)
		contextPool.Put(c)
	}
}

测试结果:

go test -bench . -benchmem
goos: linux
goarch: amd64
pkg: github.com/scott-pb/mygin
cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
BenchmarkUnmarshal-8             5888780               208.1 ns/op           144 B/op          2 allocs/op
BenchmarkUnmarshalWithPool-8     7261801               165.0 ns/op            48 B/op          1 allocs/op
PASS
ok      github.com/scott-pb/mygin       2.808s

在这个例子中,可以看出,使用了 sync.Pool 后,内存占用仅为未使用的 48/144= 1/3,对 GC 的影响就很大了。执行速度也快了,当然不同的设备测试结果也会不同。

测试源码

我把测试源码放在了mygin中
mygin/context_test.go

mygin使用sync.Pool

修改mygin/engine.go

修改
engine.go
中实例化conetxt的部分具体在
ServeHTTP
方法中

修改前

//实例化一个下上文
c := &Context{
	Request:  r,
	Writer:   w,
	Params:   params,
	handlers: handlers,
	index:    -1,
}

修改后

//从pool中取
c := e.pool.Get().(*Context)
c.Request = r
c.Writer = w
c.Params = params
c.handlers = handlers
c.index = -1

// 执行处理函数链
c.Next()

//归还到pool中
e.pool.Put(c)

mygin测试

main方法代码如下

package main

import (
	"fmt"
	"github.com/scott-pb/mygin"
	"net/http"
)

func main() {

	r := mygin.Default()
	group := r.Group("/api")
	group.GET("/test", func(c *mygin.Context) {
		c.String(http.StatusOK, "success!\n")
	})

	err := r.Run(":8088")
	if err != nil {
		fmt.Println(err)
	}
}

curl测试

curl -i http://localhost:8088/api/test
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Thu, 01 Feb 2024 05:08:52 GMT
Content-Length: 9

success!

这样mygin的context上下文就加入了Pool池,对于高并发情况下的GC压力会减轻不少。我设计的上下文中内容很少,随着功能的增多,效果会更加明显。

哈喽大家好,我是咸鱼。

想必上网冲浪的小伙伴最近都被《幻兽帕鲁》这款游戏刷屏了。

(文中图片均来自网络,侵删)

image

幻兽帕鲁是 Pocketpair 打造的一款开放世界的生存建造游戏。在游戏中,玩家捕捉各种各样的“帕鲁”。

“帕鲁” 在玩家支配下,完成不同的工作、任务,像极了现实中的打工人(破防了家人们)。

image

在游戏中体验当资本家的快乐,一下子戳到了打工人社畜们的心上,类似的带有强共情的玩家评论非常多:

“你不干有的是帕鲁干。”

“周日玩帕鲁,周一变帕鲁。”

“上班当了一天的帕鲁,下班了打开电脑轮到我当资本家了。”

但是游戏的火爆和在线玩家数量的飙升导致官方的服务器性能跟不上,许多玩家反馈在玩的过程中出现延迟高、卡顿、掉线、无法联机等现象。

为了改善这种情况,咸鱼将教大家如何从零开始搭建属于自己的幻兽帕鲁私服,让你免于卡顿掉线无法联机之苦,在帕鲁大陆上尽情“压榨”帕鲁们。

PS:下面教程是咸鱼的一位学弟写的,原文链接:
CSDN-全网最详细 CentOS 搭建幻兽帕鲁私服教程

image

前期提要

目前网上搭建幻兽帕鲁私服的主流教程多为 ubuntu 和 windows,并且各大主流云服务器厂商提供现成的幻兽帕鲁 ubuntu 和 windows 私服,所以很难看见详细完整的 CentOS 教程,此教程提供给学习 CentOS 的同学参考。

机器准备

为了搭建私服,我们首先需要一台云服务器。云厂商没有特别要求,腾讯云、阿里云、华为云都可以,这里以阿里云为例子。

官方文档推荐的服务器要求 CPU 4 核、内存 8 GB,所以我们要准备好一个 4 核 8 GB 的服务器。

ps:经实测 4 核 8 GB的大概在 5 人左右性能就已经达到瓶颈了,CPU 占用率在98% 左右,内存很快就溢出了。所以想要稳定最低最低也要 4 核 16 GB。(之前游戏优化太烂不会自动释放内存的问题,官方在 2024-01-30 已基本解决)
图片

image

image
image

image

连接机器

首先打开终端登录服务器,这里我们可以用云厂商自带的终端也可以用本地的终端,以本地终端为例。

获取公网 ip:

image

设置密钥:

image

连接机器:

image

用户使用 root,连接成功后输入 root 的密码即可。

image

下载 steamCMD

SteamCMD 是 Steam 客户端的命令行版本。它的主要用途是使用命令行界面安装和更新 Steam 上可用的各种专用服务器。
首先是用户准备,我们创建一个 steam 的用户并切换到这个用户。

[root@localhost ~]# useradd steam

[root@localhost ~]# echo '你的密码' |  passwd --stdin steam

[root@localhost ~]# su steam

然后创建一个 steam 目录。

[steam@localhost ~]# mkdir ~/steam && cd ~/steam 

接着下载安装 steamCMD 压缩包 和一些依赖包。

# 安装依赖包
[steam@localhost ~]# yum install glibc libstdc++ glibc.i686 libstdc++.i686 -y

#安装 steamCMD
[steam@localhost ~]# wget https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz

#解压缩
[steam@localhost ~]# tar xvf steamcmd_linux.tar.gz

ps:第一次启动 steamcmd,程序会下载包,完成下载后会弹出报错,提示缺少
steamservice.so
,这个报错可以先不管。

执行 steamcdm 脚本安装 steamCMD。

[steam@localhost ~]# ./steamcmd.sh 

执行完成后,命令行提示符会变成以
Steam>
开头,我们输入
quit
退出。

下面可以开始安装幻兽帕鲁的专用服务器程序了。

下载 Palserver

PalServer 就是 PalWorld 私服的启动脚本,运行这个脚本之后,游戏里就能通过地址【公网ip:8211】游玩私服。
执行以下命令,即可开始下载服务器程序。

#使用SteamCMD登录到Steam服务器并更新ID为2394010的游戏
Steam> login anonymous 
Steam> app_update 2394010 
Steam> app_update 1007
Steam> quit

这里有小概率连接不成功,一直 retry,可以多尝试几次输入该命令,直到出现 Success 提示,表明安装完成。

steamcmd 会在
/home/steam/
目录下生成
.steam
或 Steam 目录,新版本生成的文件夹都是 Steam,这里我们选择用
.steam
目录。

#新建.steam 目录 
[steam@localhost ~]# mkdir /home/steam/.steam 

启动 PalServer 前可以修改一下配置文件,可以把默认的配置文件
DefaultPalWorldSettings.ini
拷贝一份,重命名为
PalWorldSettings.ini
,然后手动修改里面的内容,用 vim 修改内容。

[steam@localhost ~]# cd ~/.steam/SteamApps/common/PalServer
[steam@localhost ~]# cp ./DefaultPalWorldSettings.ini Pal/Saved/Config/LinuxServer/PalWorldSettings.ini
[steam@localhost ~]# vim Pal/Saved/Config/LinuxServer/PalWorldSettings.ini 

ps:配置文件不是强制的,如果没有特别要求,可以直接跳过该步骤,游戏会自动使用默认的配置。

幻兽帕鲁,启动!

接着我们进入 palserver 的目录中,然后执行以下命令。

#新建sdk64文件夹
[steam@localhost ~]# mkdir /home/steam/.steam/sdk64
[steam@localhost ~]# cp -r /home/steam/Steam/steamapps/common/Steamworks\ SDK\ Redist/linux64/steamclient.so /home/steam/.steam/sdk64

然后启动幻兽帕鲁。

#启动游戏
[steam@localhost ~]# cd /home/steam/Steam/steamapps/common/PalServer
[steam@localhost ~]# ./PalServer.sh

这个时候已经可以用游戏连接私服了,但用这种方法启动的服务器,只要终端窗口关掉,服务就会挂。

如果想让服务常驻在后台,可以选择把程序以后台方式启动,这样哪怕会话结束,PalServer 服务进程也不会被杀死。

我们先输入ctrl+c把当前服务杀掉,然后输入以下命令挂起服务。

#后台方式启动游戏
[steam@localhost ~]# cd /home/steam/.steam/SteamApps/common/PalServer
[steam@localhost ~]# nohup ./PalServer.sh &

连接服务器

在图下位置输入你的服务器公网 IP 地址+端口号(默认是8211)

image

输入完之后可能会报错。

image

耐心等待一段时间后,点进最近访问过的服务器列表,就可以看到你想进入的服务器了,输入密码,就可以开始游戏了!

image

本文介绍在
Visual Studio
软件中配置、编译
C++
环境下
GDAL
库、
SQLite
环境与
PROJ
库的详细方法。

GDAL
库是一个非常方便的地理数据处理库,但其在
C++
环境下的配置与编译流程较为复杂;尤其是最新的
GDAL 3
及以上版本,其在
C++
环境中的配置更是首先需要满足许多其他的
环境配置条件
(包括
SQLite
环境、
CMake
软件与
PROJ
库),使得其配置变得更复杂一些。本文就详细介绍在
Visual Studio
软件中,配置
GDAL
库所需的环境,并最终配置、编译
GDAL
库的方法。

1 写在前面

首先,本文是基于
Visual Studio 2022
软件进行的配置;其中,
GDAL
库版本为
3.5.2

SQLite
环境版本为
3.39.4

PROJ
库版本为
9.1.0
。此外,在配置过程中还需要用到跨平台编译工具
CMake
软件,其版本为
3.24.2
。如果大家安装的是以上库、环境或软件的其他版本,也可以参考本文,因为整体流程都是近似的;但可能部分操作的细节会稍微有一些差异。

其次,在编译
PROJ
库时发现,可能是由于
PROJ
库最新的
9.1.0
版本的自身要求,只能编译
Release
版本的
PROJ
库;若编译
Debug
版本则会报错;而同时,
SQLite
环境、
PROJ
库与最终的
GDAL
库需要保持一致(即三者要么都是
Release
版本,要么都是
Debug
版本)。因此,本文最终选择了配置
Release
版本的
GDAL
库;但最终测试代码时发现,在
Visual Studio
软件中无论是
Release
模式还是
Debug
模式,都可以成功调用配置好的
Release
版本的
GDAL
库。所以,本文就主要介绍
Release
版本的
GDAL
库的配置方法,但也会在对应流程处注明如果想配置
Debug
版本的
GDAL
库,该如何设置。

2 具体配置

配置
GDAL
库首先需要准备好
PROJ
库,而
PROJ
库的配置则需要准备好
SQLite
环境与
CMake
软件(这些似乎是
GDAL 3
及以上版本才需要的,网上很多
GDAL 2
版本的配置教程都没有这么麻烦的准备工作);因此我们首先逐一配置好上述这些额外需要的环境或软件。

2.1 SQLite配置

首先,我们进行
SQLite
环境的配置。
SQLite
是一个由
C
语言撰写的数据库引擎,其为后续需要配置的
PROJ
库提供了数据支持,同时配置
GDAL
库也会用到这一环境。

首先,我们进入
SQLite

官方下载网站
,并下载“
Source Code
”中的第一项
sqlite-amalgamation-3390400
,以及“
Precompiled Binaries for Windows
”中的第二项
sqlite-dll-win64-x64-3390400
与第三项
sqlite-tools-win32-x86-3390400
,一共是三个文件,如下图所示。这里需要注意,随着
SQLite
版本的更新,上述三个文件名称最后面的一长串数字会随着改变,大家只需要对照好文件名称前面的字样即可。

随后,将上述下载好的三个压缩包文件解压到同一个文件夹下(下图是我在配置完
SQLite
环境之后截的图,因此文件夹的个数与名称和大家的会有一些差异)。

随后,打开
Visual Studio
软件,新建一个项目。我这里因为
Visual Studio
软件原本就是开着的,所以就可以选择“
文件
”→“
新建
”→“
项目...
”。

随后,选择“
空项目
”,并选择“
下一步
”。

配置好“
项目名称
”与“
位置
”。其中,“
项目名称
”我在这里配置为
MySQLite
,“
位置
”就选择刚刚我们解压缩三个文件的文件夹即可。

随后,分别在“
头文件
”与“
源文件
”处右键,通过“
添加
”→“
现有项...
”的方法,将刚刚解压缩得到文件中的
sqlite3.h

sqlite3ext.h
文件放入“
头文件
”,并将
sqlite3.c

sqlite3.def
文件放入“
源文
件”。

完成后如下图所示。

接下来,选中项目名称
MySQLite
,并选择“
属性
”→“
配置属性
”→“
常规
”,选择“
配置类型
”,将其选择为
静态库(.lib)
,并在上方的“
配置
”与“
平台
”中将二者选择为“
所有配置
”与“
所有平台
”。

接下来,在“
C/C++
”→“
预处理器
”中,设置“
预处理器定义
”。

在其中复制如下代码即可。

_USRDLL
SQLITE_ENABLE_RTREE
SQLITE_ENABLE_COLUMN_METADATA
SQLITE_ENABLE_FTS5
SQLITE_ENABLE_UNLOCK_NOTIFY

复制后如下图所示。

接下来,在“
链接器
”→“
输入
”中配置“
模块定义文件
”。

找到刚刚解压缩得到的
sqlite3.def
文件,按下
Shift
按钮并右键,选择“
复制文件地址
”。

并将其粘贴至“
模块定义文件
”中。

接下来,在
Visual Studio
软件中,打开
sqlite3.def
文件,在其最后一行添加如下的代码。

sqlite3_unlock_notify

如下图所示。

接下来,在上图最上方的红色方框处,将原来的
Debug
修改为
Release
,如下图所示;表示我们接下来将配置
Release
版本的
SQLite
环境。如果大家需要
Debug
版本,那么这里就还是保持选中
Debug
即可。

接下来,选中项目名称,并右键选择“
生成
”。

接下来,
Visual Studio
软件将生成项目。

此时,在前面提到的
SQLite
环境所下载的
三个压缩包文件解压后所存放的
文件夹中,我们新建三个文件夹,分别命名为
include

lib

bin

随后,首先在刚刚生成的项目的
x64\Release
文件夹中,找到
MySQLite.lib
文件。这里如果大家刚刚生成的是
Debug
版本的项目,那么就是在
Debug
文件夹中。

随后,将其复制到刚刚新建的
lib
文件夹中。

接下来,将
sqlite-amalgamation-3390400
文件夹中的
sqlite3.h

sqlite3ext.h
文件复制。

将其粘贴至刚刚新建立的
include
文件夹中。

随后,将
sqlite-tools-win32-x86-3390400
文件夹中的
sqlite.exe
文件复制。

粘贴至刚刚新建立的
bin
文件夹中。

以上,即完成了
SQLite
环境的配置。

2.2 CMake配置

接下来,我们开始对
CMake
软件进行配置。
CMake
软件是一个跨平台的编译软件,在后期进行
PROJ
库配置时需要用到。

首先,我们还是在其
官方下载网站
进行软件的下载。这里我们下载其“
Binary distributions
”中的第一项即可。

下载完毕后,可以看到其安装包。

双击安装包,进行软件的安装即可。这里需要注意,安装过程中,在以下窗口中我们需要选择第三项,并勾选中最下方的勾选项,如下图所示。

以上,即完成了
CMake
软件的配置。

2.3 PROJ配置

接下来,我们开始
PROJ
库的配置。
PROJ
库是一个地理坐标转换库,用以处理不同的地理坐标数据;因为我们要安装的
GDAL
库是一个地理数据处理库,因此其固然需要
PROJ
这一地理坐标转换库来完成各项地理数据操作。

PROJ
库的配置有两种方法——第一种是传统方法,即用库的源代码来进行编译;第二种是
PROJ
库官网比较推荐的新方法,即用
OSGeo4W
软件来进行
PROJ
库的下载与编译。尽管
PROJ
库官网目前推荐用第二种方法来配置,但是我一直没有找到第二种方法配置
PROJ
库完毕后其所生成的数据文件,导致没有办法进行后续
GDAL
库的配置;另一方面,若采用第二种方法,
OSGeo4W
软件在下载
PROJ
库时经常会出现网络问题导致的下载终止,且想要再下载还只能重新开始,非常耽误时间。因此,我个人还是建议大家用第一种方法(也就是下文中的方法一)来实现;虽然这种方法操作起来比较麻烦,但至少比较稳定,而且可以保证后续
GDAL
库的配置是可以实现的。同时,本文也将并不推荐的第二种方法的操作流程介绍一下,如果大家感兴趣可以研究一下第二种方法。

2.3.1 方法一

首先,我们在
PROJ
库的
官方下载网站
中下载其源代码。

在官网,我们还可以看到官方将这种从源代码编译的方法称作“
传统方法
”。

随后,将下载后的压缩包解压,并在解压后得到的文件夹中新建立一个
build
文件夹。

接下来,打开我们刚刚下载、安装好的
CMake
软件,并在下图所示的两个编辑栏中,分别选定解压后的
PROJ
库文件夹与我们刚刚在其中新建的
build
文件夹;随后,点击“
Configue
”。

随后,在弹出的窗口中配置我们的
Visual Studio
软件版本,并在第二个选项中选择处理器的架构,一般选择
x64
即可。

随即,
CMake
软件开始运行第一次配置。稍等片刻,一般会出现满屏幕的红色,如下图所示。

其中,我们需要修改一下上图中蓝色方框内的五条数据。首先,我们在解压后的
PROJ
库文件夹中,新建
GTest
文件夹。

并将其作为
GTest_DIR
后所对应的数据。

接下来,在
CMAKE_INSTALL_PREFIX
中,配置一个我们后期将要存放
PROJ
库的路径;接下来的三个数据,我们分别选择前面配置
SQLite
环境时,所得到的
bin
文件夹中的
sqlite3.exe
文件、
include
文件夹与
MySQLite.lib
文件的路径。具体配置如下图所示。

随后,再点击一次“
Configue
”。

稍等片刻,待其配置完成后,再点击“
Generate
”;如上图所示。

待其生成完毕后,我们可以在刚刚解压后的
PROJ
库文件夹中新建的
build
文件夹中,看到如下所示的
.sln
文件。

随后,在开始菜单中找到位于
Visual Studio 2022
软件下的“
x64 Native Tools Command Prompt for VS 2022
”软件。

首先,cd进入
PROJ
库文件夹中刚刚新建的
build
文件夹。这里大家依据刚刚自己设定的文件夹路径来cd进入即可。

cd C:\proj-9.1.0\build

接下来,输入如下的代码,并回车运行。

msbuild ALL_BUILD.vcxproj /p:Configuration="Release"

其中,上述代码中
="Release"
表示我们将编译
Release
版本的
PROJ
库;如果大家需要编译
Debug
版本的库,则将其修改为
="Debug"
即可。

这里我们提一句,本文开头之所以说最终只能编译
Release
版本的
PROJ
库,是因为一旦前面的代码我们选择编译
Debug
版本,则其运行后会报错,出现如下所示的情况。

其中,这里不同的错误,都表明我们目前只能配置
Release
版本的
PROJ
库。

因此,我在这里最终选择编译
Release
版本的
PROJ
库。虽然选择编译
Release
版本的
PROJ
库在这里也会有警告出现,但这并不影响最后的结果。

接下来,我们继续输入如下所示的代码。

msbuild INSTALL.vcxproj /p:Configuration="Release"

其中,如果我们需要编译
Debug
版本的库,则将其修改为
="Debug"
即可。

运行代码,得到结果如下。

此时,前面提到我们在
CMake
软件中设置了
CMAKE_INSTALL_PREFIX
所对应的文件夹路径。

在这个路径中,此时我们可以看到其已经自动生成了
bin

include

lib

share
等四个文件夹。

以上,即完成了
PROJ
库的配置。

2.3.2 方法二

这里我们介绍一下
PROJ
库的第二种配置方法。但前面我们也提到,并不推荐这种方法,大家如果有需要参考一下即可。如果前面已经通过方法一实现了
PROJ
库的配置,那么直接跳过这里,继续阅读本文
2.4
部分即可。

首先,我们按照官方所给出的
OSGeo4W
软件下载路径,进行
OSGeo4W
软件的下载。

或者也可以直接从这里进行下载:
https://download.osgeo.org/osgeo4w/osgeo4w-setup.exe

接下来,进行软件安装的过程中,在下图所示的界面中选择第二个选项。

在下图所示的界面中,选择第一个选项。

在下图所示的界面中,进行如下的配置。

在下图所示的界面中,进行如下的配置。

在下图所示的界面中,进行如下的配置。

在下图所示的界面中,进行如下的配置。

随后,在下图所示的界面中,首先找到第一个选项。

将其展开后,找到带有
proj
字样的那一行。

这里要注意,只要确保带有
proj
字样的那一行的
New
列中,出现库的版本号即可,其他行的这一列都是显示为
Skip
即可。

随后,如果出现下图所示的提示,按照其推荐设置进行配置即可。当然,如果我们前面选择了只下载
PROJ
库的话一般并不会出现这个提示;如果下载时选中的库比较多,则可能会出现这个。

随后,即可开始下载。

稍等片刻,将下载完毕。

到这里,
PROJ
库的官网就说完成了这一库的配置。但是通过这样的方法,我一直没有办法找到库对应的数据文件或其路径(比如前述
lib

bin

include
文件夹等的路径)。所以最终还是选择用方法一来实现
PROJ
库的配置了。

2.4 GDAL配置

完成了以上所有的操作,终于可以开始
GDAL
库的配置了。

首先,依然是在其
官方下载网站
中进行源代码的下载。

随后,将下载得到的压缩包文件解压。

随后,找到其中的
nmake.opt
文件,并用
Visual Studio
软件打开。

随后,在其
42
行左右(具体行数可能会随着
GDAL
库版本的不同而不同)的位置,将等号后的数字修改为自己
Visual Studio
软件的
_MSC_VER
值。如果大家不知道自己
Visual Studio
软件的这一个值是多少,可以参考文章
获取Visual Studio所用MSVC编译器版本:_MSC_VER数值

接下来,在
66
行左右的位置,将这一路径设置为自己希望编译后
GDAL
库存放的路径。

随后,在
130
行左右的位置,如果大家需要编译的是
Debug
版本的
GDAL
库,则将等号后的数字修改为
1
即可;我们这里需要编译的是
Release
版本的
GDAL
库,就不用修改。

随后,在
213
行左右的位置,如果我们的电脑是
64
位的
Windows
系统,就将这一行的注释取消掉即可。

接下来,在
245
行左右的位置,如果需要
动态编译
就不用修改,如果需要
静态编译
就将等号后的数字修改为
0
。一般我们选择
静态编译
即可,因此将其修改为
0

接下来,在
265
行左右,需要我们配置
PROJ
库的一些路径。

其中,第一个路径是前面我们在
CMake
软件中设置了
CMAKE_INSTALL_PREFIX
所对应的文件夹路径下,
include
文件夹的路径;第二个路径是这一文件夹中,
lib
文件夹中
proj.lib
文件的路径。如下图所示。

这两行配置完毕,如下图所示。

最后,在
638
行左右,需要我们配置
SQLite
环境的一些路径。

其中,这里所涉及的文件夹,是前面提到的
SQLite
环境所下载的
三个压缩包文件解压后所存放的
文件夹中,我们新建的、分别命名为
include

lib

bin
的那三个文件夹。其中,将
include
文件夹的路径放在第一个位置,将
lib
文件夹中的
MySQLite.lib
文件的路径放在第二个位置。

配置完毕后,如下图所示。

随后,保存当前
nmake.opt
文件,并再一次打开“
x64 Native Tools Command Prompt for VS 2022
”软件,cd进入刚刚解压得到的
GDAL
库文件夹(也就是
nmake.opt
文件所在的那个文件夹,这一文件夹下还有一个名为
makefile.vc
的文件,如下图所示)。

随后,对于我们需要的
Release
版本,输入如下代码并运行。

nmake /f makefile.vc

运行后得到如下图所示的结果。

随后,输入如下所示的代码并运行。

nmake /f makefile.vc devinstall

运行后得到如下图所示的结果。

如果需要的是
Debug
版本的
GDAL
库,那么不用输入以上两句代码,直接分别输入并运行以下三句代码即可。

nmake /f makefile.vc WIN64=YES DEBUG=1
nmake /f makefile.vc install
nmake /f makefile.vc devinstall

此时,在我们刚刚于
nmake.opt
文件
66
行左右处所设定的
GDAL
库存放路径(如下图所示)中,可以看到已经自动生成了相关的文件夹,如下下图所示。

以上,即完成了
GDAL
库的配置。

3 配置测试

接下来,我们需要检查一下
GDAL
库是否可以使用。

我们在
Visual Studio
软件中新建一个项目,并写一段需要调用
GDAL
库的代码;本文就用下面这个很简单的一段代码来验证
GDAL
库是否可用。

#include <iostream>
#include <gdal.h>
#include <gdal_priv.h>
using namespace std;

int main() {
	GDALDataset* poDataset;
	GDALAllRegister();
	poDataset = (GDALDataset*)GDALOpen("pic.tif", GA_ReadOnly);
	if (poDataset == NULL)
	{
		cout << "找不到文件,但说明GDAL可以用了";
	}
	return 0;
}

其中需要注意,如果我们需要调用
GDAL
库,就需要在代码开头通过以下代码引入相关的头文件。

#include <gdal.h>
#include <gdal_priv.h>

随后,首先按照文章
如何在Visual Studio新C++项目中调用之前配置过的库?
中提到的方法,在
Visual Studio
软件中配置好相应的属性设置。

接下来,将刚刚于
nmake.opt
文件
66
行左右处所设定的
GDAL
库存放路径中的
bin
文件夹,与
PROJ
库配置好后在其指定文件夹(即
CMake
软件中设置了
CMAKE_INSTALL_PREFIX
所对应的文件夹)中生成的
share
文件夹,分别放于系统变量的
PATH
中;具体方法可以参考文章
Windows电脑环境变量(用户变量、系统变量)的修改

配置好后如下图最后两行所示。

这里如果不配置环境变量的话,在运行调用
GDAL
库的代码时,会出现如下图所示的错误或类似错误。

完成以上操作后,运行代码。此时,有可能出现找不到
proj_9_1.dll
文件的错误;如下图所示。

针对这一情况,我们只需要将编译好后的
PROJ
库中的这一文件复制。

并粘贴至当前调用
GDAL
库的项目的文件夹中即可。

随后,运行程序,即可成功调用
GDAL
库。

至此,大功告成~