标签归档:c++

[WebAssembly]初学笔记 Pthreads多线程

如果有看不明白的地方请先看前置说明文章

对于有高性能要求的可并发任务来说,在程序一开始设计的时候就要把多线程协作考虑进去,因此介绍完了一些emscripten的基本操作之后,我立刻就来到了多线程的章节。

Emscripten提供了两种实现多线程的方式:

  • POSIX Threads (Pthreads) API (以下简称pthreads)(此工具中标准库的std::thread也基于pthreads实现)
  • Wasm Workers API (以下简称wasmworkers)(在这里继续再批判一次官方这破API文档竟然不写每个函数的用法,其它东西扯了一堆)

我参阅了一下wasmworkers的文档,以上两种API基本能实现相同的功能,区别在于它的wasmworkers是直接根据js原生worker原本的API实现的,因此输出的编译结果会比较小,而pthreads实现了完整的pthreads api,所以输出的编译结果会比较大。由于考虑程序的可移植性,所以这里我只学习使用pthreads实现多线程,如果对pthreads不熟悉的话可以看一看这个Rookie-Note上的学习文档简单了解一下,还是挺简单的,我也刚学会才来写了这篇笔记。

本篇笔记不会对pthreads api如何使用进行全面的说明,因为内容比较多且不是专属于wasm的内容,在代码编写上也和普通的 C pthread程序没有什么区别,可以看我上面贴的菜鸟笔记链接去学习,这里主要就说明一下emscripten中pthread的实现方式和举个例子,顺便贴上emscripten的pthread指导链接

首先要注意

  • 使用以上两种多线程API的时候都不可以和 `-sSINGLE_FILE` 编译参数搭配使用,也就是使用多线程就无法把输出文件全部打包到一个js文件里了。
  • 要使用pthreads需要浏览器开启SharedArrayBuffer支持(在目前的浏览器中由于存在安全原因默认是禁用的),在nodejs中可以直接使用。wasmworkers我没有测试,但应该也是一样的。
  • 编译参数:要使用pthreads库,需要在编译时添加 `-pthread` 参数。
  • 由于标准库中的std::thread在emscripten中也是基于pthread实现的,因此如果你比较熟悉标准库的`std::thread`也可以直接使用它,但要记得添加 `-pthread` 编译参数。

继续阅读[WebAssembly]初学笔记 Pthreads多线程

[WebAssembly]初学笔记 使用Embind在Javascript与C++之间交互

如果有看不明白的地方请先看前置说明文章

Embind是emscripten提供的又一种在js和c++之间的交互方案,其提供更加丰富的交互方式,不止是前面的笔记中介绍的那种简单的函数调用。

Embind库API参考文档地址:https://emscripten.org/docs/api_reference/bind.h.html

官方指导文档地址:https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html,下面简单概括一下这个库包含的功能。

1. 在js环境中绑定c++中的(绑定指的是把另一个语言中的概念映射为当前语言中类似的概念):

2. 在c++环境中:使用`val`类操作js中的任意对象。 继续阅读[WebAssembly]初学笔记 使用Embind在Javascript与C++之间交互

[WebAssembly]初学笔记 在C++中嵌入Javascript代码

如果有看不明白的地方请先看前置说明文章

前一篇笔记写了如何在js中调用c++的函数,要在c++里执行js代码也有几种方法,另外和从js到c++的交互只能调用函数和操作内存不同,c++里可以编写完整的js代码并获得结果。

这里依然先附上官方的参考:https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html


使用emscripten_run_script直接执行代码

这是emscripten提供的执行js代码的方法,此方法无法获取js代码的运行结果(类似于返回值),可以获取返回值的版本在下文。这个方案的内部实现方式是把这个代码字符串扔外面的js环境用eval()来执行。

在test.cpp中编写如下代码:

#include <emscripten.h>

int main() {
	emscripten_run_script(
		"console.log('岂因祸福避趋之');\n"
		"console.log('6');");
	return 0;
}

然后执行命令编译:emcc -sMODULARIZE=1 -sASSERTIONS -sEXPORT_ES6 -o dist/test.mjs test.cpp

继续阅读[WebAssembly]初学笔记 在C++中嵌入Javascript代码

[WebAssembly]初学笔记 从Javascript调用C++函数

如果有看不明白的地方请先看前置说明文章

要在js中使用c++编写的函数或者在c++中运行js代码,则需要有一些方法打通这两个环境,官方已经写了一个列表列举出了所有的方法,可以参考:https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html

在此我会对部分方法简单进行举例。

上一篇笔记说到如果不需要自动执行main,可以不写main函数,此时我们需要使用wasm中编写的函数就需要手动设置需要导出的函数。


函数导出

要从C++中导出函数给js环境使用,有两种方法:一种不用改动代码,需要在编译参数上进行指定,另一种需要在代码中添加宏来指定。

这两种方法的使用场景我认为是这样:如果有一份源码不是你写的,或者说就是引用别处的一个库,那么我们去修改其源码肯定不便于后期维护,因此在编译参数中指定导出的方法最稳妥,但如果要编译的源码就是你专门为了这个wasm项目写的,那么使用宏指定可以让编译参数更加简洁,在代码中也可以更清晰地看出导出了哪些函数。

方法一:需要在编译时添加参数指明要导出的函数名称:

-sEXPORTED_FUNCTIONS=_func1,_func2,_func3,其中func1,func2,func3是在c++中你定义的函数名称,作为导出函数时,需要在其名称前面添加一个下划线,然后这些函数就会被添加给wasm的实例对象。

现在将test.cpp的内容修改为下:

#include <iostream>
extern "C" { // 由于C++导出方法会对方法进行重命名,这是用来指定以C形式导出方法

//定义一个无返回值函数,参数为一个int
void poi1(const int val) {
	std::cout << "poi1: " << val << std::endl;//输出val的值
}
//定义一个返回整数的函数,参数为两个int
int poi2(const int val, const int val2) {
	std::cout << "poi2: " << val << " " << val2 << std::endl;//输出两个val的值
	return 114514;
}
//定义一个无返回值函数,参数为一个bool
void boolTest(bool val) {
	std::cout << "bool: " << val << std::endl;
}

}

然后执行命令编译:emcc -sMODULARIZE=1 -sASSERTIONS -sEXPORTED_FUNCTIONS=_poi1,_poi2,_boolTest -o dist/test.mjs test.cpp

继续阅读[WebAssembly]初学笔记 从Javascript调用C++函数

[WebAssembly]初学笔记 安装环境和尝试编译

以前也尝试过学WASM,但由于当时没有应用场景所以安装完环境就弃坑了,这次来好好学习一下用法。这篇笔记是边学边写的,如果有错误或者需要补充的地方请留言。

WASM通常需要使用其他高级语言编写后编译为wasm二进制文件交给运行时去运行,但如果你的头够铁,也可以尝试手写wasm指令,或者通过wasm的文本格式理解它的底层运行原理。

本笔记在Windows平台上使用C++编写源码编译为wasm,所以需要安装emscripten工具包。除了C++以外,Rust也是一个推荐选项,而其它语言目前还没有非常完备的支持。

安装Emscripten

这是用于把C/C++编译到wasm的工具包。

Emscripten的项目地址在:https://github.com/emscripten-core/emscripten

文档地址在:https://emscripten.org/index.html

安装方式官方手册在:https://emscripten.org/docs/getting_started/downloads.html,这里我简单介绍一下,但还以官方手册为准,我不会同步更新。

首先把emscripten仓库克隆到任意位置,然后进入该目录

git clone --depth 1 https://github.com/emscripten-core/emsdk.git

cd emsdk

然后依次执行以下命令进行安装

emsdk.bat install latest
emsdk.bat activate latest --permanent

上面的` –permanent`参数是为了让emsdk的命令在全局可用,否则你需要在每个命令环境中都执行一次activate。执行完了之后可以关闭当前的命令窗口,然后找个地方开始准备写我们的代码了。 继续阅读[WebAssembly]初学笔记 安装环境和尝试编译

[SQLite]导入自己写的扩展出现”SqliteError: 找不到指定的程序”

自己写了个sqlite c++扩展,然后编译之后载入出现了标题上的错误,如果是英文的话应该是”SqliteError: The specified module could not be found”。

出现这个错误并不是sqlite没找到对应的dll文件,如果是没找到文件的话会提示“找不到指定的模块”。

这个情况是sqlite载入dll之后找不到程序入口,但我在 load_extension 和 c++ 代码中的入口名称明明就是一致的,于是我用 `dumpbin /exports dll文件` 查看dll到底导出了什么入口,发现导出了这么个东西:

_Z22我的入口名P7sqlite3PPcPK20sqlite3_api_routines

我把这一串名字写到 load_extension 中之后终于可以正确导入了,但我肯定不能让它一直生成这样不确定的名字,所以开始找找办法让它只导出我指定的名字。

然后发现这是我用了c++的原因,要解决dll导出的入口名和代码里写的不一致的问题,只要在 `__declspec(dllexport)` 前面加上 `extern “C” ` 即可,完整的是

extern "C" __declspec(dllexport)

这样改完再编译,使用dumpbin查看导出的函数名就已经正常了。

[C++]std::async异步操作 笔记

相关文档:http://zh.cppreference.com/w/cpp/thread/async

这只是一篇个人学习笔记,如有错误欢迎指出

咕了C++一段时间,我又回来学习了,这次是关于STL里自带的线程相关的模板函数`async` 。

async可以非常方便地用来创建一个异步任务,不同于基于libuv那种事件轮回型的异步编程,这个异步是使用一个新的线程来执行任务,而coder不用自己去管理新的线程。

继续阅读[C++]std::async异步操作 笔记

[C++]lambda表达式笔记

我刚开始学C++的时候似乎还没有lambda表达式这东西。这个blog的第一篇可见文章在2012年8月,还有一些更早的都是些垃圾水文被我删掉了,开设这个blog的时间我记得在学了web一段时间之后,在那之前我也在各种免费php空间搭过wordpress用过一段时间,后来废了。在更之前我还坚持用自己写的“佳佳空间”当blog用了很长一段时间(虽然后来终于还是放弃了,因为写得太垃圾)。推算下来可能要到9年多前了,大概就是小学的时候?反正是在2011年之前,而lambda表达式是C++11标准里的,所以那时候应该确实没有这东西。

 

好了回到正题。

C++的lambda表达式和js里的有点不一样,它可以自己选择是否打通函数范围之外的上下文,以使用外面的变量。还可以选择如何打通(引用外部变量和复制外部变量)。

lambda函数的结构长这样

[外部变量规则]{函数体}//没参数省略括号
[外部变量规则](参数){函数体}//省略返回类型指定
[外部变量规则](参数)->返回类型{函数体}//到C++11为止的完整形态

C++20又加了一个形式,暂时就不写在这了,毕竟2020年还没到呢不着急,而且也没编译器实现了。

继续阅读[C++]lambda表达式笔记

【复习】C++定义变量

好久没动C++了,几乎都给忘得差不多了,于是我来打一些复习笔记的说。

写了那么久的js都已经没有主函数的概念了。。。所以出现了函数到处定义,变量到处定义,企图动态修改类之类的事情,然后就是一堆错误信息<(。_。)>

首先从定义变量开始

//括号里代表写不写都一个意思
(signed) short (int) a;
(signed) int miao;
(signed) (long) int b;
(signed) long long (int) d;
unsigned short (int) c;
unsigned int;
unsigned (long) int d;
unsigned long long (int) bilibili;

float e;//-3.4e38~3.4e38
double f;//long double的默认形式 -1.7e308~1.7e308
(signed) char g;//-128~127(不同值代表不同符号)
unsigned char h;//0~255
bool i;//true/false

继续阅读【复习】C++定义变量

函数指针利用

在C++里,变量有指针,比如int *a;之类的,指针的用处是很大的,可以让程序操作过程更加灵活。
我写这篇文章是为了记录刚刚自己无师自通的函数指针和它的用法。

一般的函数用法类似这样【只是举个例子我懒得写return了_( •́ ​ω •̀ 」∠)/_】

int a(){
	cout<<"这是a函数";
}
a();
//输出结果:这是a函数

继续阅读函数指针利用

最简单的dll创建和调用

说好的最简单创建方法,所以这篇文章也会很简单。
我折腾了一晚上和半个下午,好不容易才解决了一大堆麻烦的问题,提炼出了网上各种麻烦代码的主要步骤

我一共用了两个文件:
2.cpp 生成2.dll文件
3.cpp 生成3.exe文件

这里省去了大部分教程里都要求包含的头文件

首先就是创建dll 继续阅读最简单的dll创建和调用

哔~~哔~~哔~~哔~~

这是一个无聊的产物[那是相当无聊才会来做这种东西啊~]
先隆重介绍一下,这是一个可以发出声音的程序![其实就是用Beep函数。。]
然后我给它取了个好长好长名字的名字,叫“音乐灵感的来源~”

然后就上代码了

#include "iostream.h"//带上输出功能
#include "windows.h"//用Beep函数
int main(){
	int pinlv;//定义频率变量
	for(int i=0;i<1000;i++){//循环999次 pinlv=rand();//得到随机数 while(pinlv>2000){//超出了预定范围的话[频率超过一定范围声音会刺耳听不清]
			pinlv=rand();//再来得一次随机数,一直到得到范围内的数为止
		}
		Beep(pinlv,200);//哔哔哔哔哔哔~
		cout<<i<<"  "<<pinlv<<endl;//输出次数和本次的频率为多少
	}
	return 0;//结束啦
}

这是多么可爱的一个程序啊。然后我就不说什么了。。

这里来打个Win32应用程序最基本代码的笔记

此代码直接丢进VC或VS或C-Free或其他什么win下的编译器就可以直接编译运行,无需冷藏。。(在VS下需把编码调一下)

 
#include "windows.h" 
LRESULT CALLBACK MainWProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
switch (message) 
{ 
case WM_PAINT: // 窗口客户区需要重画 
{ 
HDC hdc; 
PAINTSTRUCT ps; 
char szText[] = "这神马程序"; // 使无效的客户区变的有效,并取得设备环境句柄 
hdc =BeginPaint (hwnd, &ps) ; // 显示文字 
TextOut(hdc, 250, 10, szText, strlen(szText)); 
EndPaint(hwnd, &ps); 
return 0; 
} 
case WM_DESTROY: // 正在销毁窗口 
// 向消息队列投递一个WM_QUIT消息,促使GetMessage函数返回0,结束消息循环 
PostQuitMessage(0); 
return 0 ; 
} 
// 将我们不处理的消息交给系统做默认处理 
return DefWindowProc(hwnd, message, wParam, lParam); 
}
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) 
{ 
TCHAR szClassName[] = "MainWClass"; 
WNDCLASSEX wndclass; // 用描述主窗口的参数填充WNDCLASSEX结构 
wndclass.cbSize = sizeof(wndclass); // 结构的大小 
wndclass.style = CS_HREDRAW|CS_VREDRAW; // 指定如果大小改变就重画 
wndclass.lpfnWndProc = MainWProc; // 窗口函数指针 
wndclass.cbClsExtra = 0; // 没有额外的类内存 
wndclass.cbWndExtra = 0; // 没有额外的窗口内存 
wndclass.hInstance = hInstance; // 实例句柄 
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); // 使用预定义图标 
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 使用预定义的光标 
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 使用白色背景画刷 
wndclass.lpszMenuName = NULL; // 不指定菜单 
wndclass.lpszClassName = szClassName ; // 窗口类的名称 
wndclass.hIconSm = NULL; // 没有类的小图标 
// 注册这个窗口类 
RegisterClassEx(&wndclass); // 创建主窗口 
HWND hwnd = CreateWindowEx( 
0, // dwExStyle,扩展样式 
szClassName, // lpClassName,类名 
"喵~", // lpWindowName,标题 
WS_OVERLAPPEDWINDOW, // dwStyle,窗口风格 
NULL, // X,初始 X 坐标 
NULL, // Y,初始 Y 坐标 
1000, // nWidth,宽度 
500, // nHeight,高度 
NULL, // hWndParent,父窗口句柄 
NULL, // hMenu,菜单句柄 
hInstance, // hlnstance,程序实例句柄 
NULL) ; // lpParam,用户数据 
// 显示窗口,刷新窗口客户区 
ShowWindow(hwnd, nCmdShow); 
UpdateWindow(hwnd); // 从消息堆中取出消息 
MSG msg; 
while(GetMessage(&msg, NULL, 0, 0)) 
{ 
// 转化键盘消息 
TranslateMessage(&msg); // 将消息发送到相应的窗口函数 
DispatchMessage(&msg); } // 当GetMessage返回0时程序结束 
return msg.wParam; 
}

这么就写完了