当前位置:网站首页>使用WebAssembly在浏览器端操作Excel

使用WebAssembly在浏览器端操作Excel

2022-07-05 20:49:00 你的薄荷醇

WebAssembly其实出现已经好多年了,是在浏览器端虚拟环境中运行汇编语言的一个方式,它编写的程序叫做wasm。WebAssembly 于 2015 年首次发布,主要的目标是让浏览器执行的速度追赶上原生应用程序,目前绝大多数浏览器都开始默认启用 WebAssembly。

编写WebAssembly,可以用很多语言,比如C,C++,RUST,.NET 等语言

下面我将演示用C++开发一个WebAssembly程序的完整过程。

开发系统环境:ubuntu16

一、WebAssembly开发过程

1.开发环境安装

虽然WebAssembly可以用C++开发,但这并不意味着,使用GCC,或者cl等编译器编译出的程序,直接可以在浏览器中运行,要把C/C++或者其它语言编译为wasm文件,需要使用其它工具进行翻译。目前最标准的使用工具就是emscripten

emscripten安装

emscripten这个东西,它对运行环境的要求很严格,所以,安装过程要严格按照官方提供的方式操作

我的开发环境是ubuntu,在ubuntu上安装emscripten,首先你要安装git,你的系统有没有安装过git,在终端上,执行一下就行

git

如果安装过,输出的内容应该和下图输出的差不多
在这里插入图片描述
如果没有的话,就安装一个。虽然emscrpten也可以在github上下载,但还是建议安装.输入以下命令,等待安装就可以

sudo apt-get install git

安装完git以后,就可以按照官方安装教程一步步的来了

# 从git下载emsdk
git clone https://github.com/emscripten-core/emsdk.git

# 进入emsdk目录
cd emsdk

# 合并本地代码(第一次下载的话,不需要)
git pull

# 下载和站桩最新版本
./emsdk install latest

#把最新版本设置为活动版本
./emsdk activate latest

# 设置环境变量
source ./emsdk_env.sh

上面这些步骤很重要,因为目前可以参考的资料较少,最好按照上面的步骤执行,如果某一步执行错误的话,要多次执行,保证每一步都执行完成

执行完成后在emsdk文件夹中的内容,大概如下

在这里插入图片描述
然后验证一下安装好了没有

emcc -v

输出以下内容就代表装好了
在这里插入图片描述
python安装

python是编译WebAssembly最重要的工具

目前emscripten版本所需要的python,是需要python3.6以上,如果你安装3.6以下版本,编译会失败。

如果你用的ubuntu也是16,那么系统内置的是python2,不能使用现在emscripten,要安装python3.6以上的版本

python3.6版本的安装建议使用源码安装,因为通过apt-get,我没有安装成功

去python官方网站下载

https://www.python.org/downloads/source/

在这里插入图片描述
我下载的是3.6.9 如果你没有把握,也和我下载一样的版本,然后执行解压

tar -xvzf Python-3.6.9.tg

解压完成后,进入Python-3.6.9文件夹,然后执行

./configure

然后执行

make
make install

这样就开始安装python3.6了

安装完成后,删除原来的python2链接

rm -rf /usr/bin/python

然后吧python3作为默认链接

ln -s  (你的py3安装地址) /usr/bin/python

都操作完成后,直接敲入python,看看操作对了没有
在这里插入图片描述
如果你不知道刚才python3安装到哪,执行以下命令查询,然后再执行以上步骤

whereis python3

cmake安装

emscripten可以执行cmake编译,如果你要编译点小东西玩,就不需要了,但还是建议安装,如果你make用的熟的话,也可以使用make

可以看看自己安装过cmake没有,执行如下命令

cmake

在这里插入图片描述
如果安装过,大概输出就像上面这样

如果没有,就apt-get安装

apt-get install cmake

chrome浏览器

这个倒也不是必须的,除了chrome浏览器,edge等chrome内核浏览器也行,firefox也行,但是你安装的chrome,起码要75以后的版本才可以

以上东西都安装后,基本开发环境就完成了

2.用WebAssembly操作excel

下面来写个WebAssembly操作excel文件的demo

完整的代码可以到以下地址获取

https://github.com/ltframe/code/tree/main/wasm-excel-demo

操作execl.我采用的是C++编写的BasicExcel库,采用这个库的原因是,它虽然没有很强大功能,但足够小巧,使用简单,而且文件少,基本表格操作具备。我以前写过关于它的使用,可以去翻翻

实现的目标

1.读出内容,然后在C++中生成Json,输出到前端,再由前端程序显示出来

2.在前端修改内容,把修改后的json传回C++,然后生成新的文件,下载到浏览器

BasicExcel只能打开xls文件,xlsx不行

创建excel文档

创建一个excel文件,里面弄两个工作表,
在这里插入图片描述
然后在第一个工作表里输入内容
在这里插入图片描述
第二个工作表里也随便输入一点
在这里插入图片描述
编写C++文件

class myclass
{
    
  public:
    wstring outputstr = L"";
    myclass()
    {
    

    }
    void saveexcel(string value)
    {
    
        cJSON  *json = cJSON_Parse(value.c_str());
        cJSON *info = cJSON_GetObjectItem(json, "info");
        cJSON *count = cJSON_GetObjectItem(info, "count");
        cJSON *tablenames = cJSON_GetObjectItem(info, "tablenames");
        int _count = count->valueint;
        cJSON  *data = cJSON_GetObjectItem(json, "data");
        BasicExcel *e = new BasicExcel();
        e->New(_count);
        for (int i = 0; i < 1; i++)
        {
    
          BasicExcelWorksheet* sheet = e->GetWorksheet((unsigned)i);
          cJSON *name = cJSON_GetArrayItem(tablenames, i);
          sheet->Rename(StringToWString(name->valuestring).c_str());

          cJSON *list = cJSON_GetObjectItem(data, name->valuestring);

          for (int j = 0; j < cJSON_GetArraySize(list); j++)
          {
    
            cJSON *item = cJSON_GetArrayItem(list,j);

            for (size_t k = 0; k < cJSON_GetArraySize(item); k++)
            {
    
              cJSON *c = cJSON_GetArrayItem(item, k);

              BasicExcelCell* cell = sheet->Cell(j, k);
              cell->SetWString(StringToWString(c->valuestring).c_str());
            }     
          }
        }
        e->SaveAs("/data/demo.xls");
    }
    void readsheet(BasicExcelWorksheet* sheet1)
    {
    
      size_t rows = sheet1->GetTotalRows();
      size_t cols = sheet1->GetTotalCols();
      for (size_t r = 0; r < rows; r++) {
    
        outputstr+=L"[";
        for (size_t c = 0; c < cols; c++)
        {
    
          BasicExcelCell* cell = sheet1->Cell(r, c);
          //判断单元格类型
          switch (cell->Type())
          {
    
          case BasicExcelCell::WSTRING:
          {
    
            const wchar_t* ret = cell->GetWString();
            outputstr += L"\"";
            outputstr += ret;
            outputstr += L"\"";
            if (c != cols - 1) {
    
              outputstr += L",";
            }
           
          }
          break;    
          default:
            break;
          }
        }

        outputstr+=L"]";
        if(r!=rows-1){
    
          outputstr+=L",";
        }
      }
    }
    wstring loadexcel(string fb)
    {
    
       outputstr += L"{\"info\":{";
       BasicExcel *e  =new BasicExcel;
       wprintf(L"%ls\r\n",L"start load...");
       string fname = string("/data/")+fb;
       if (!e->Load(fname.c_str()))
       {
    
            wprintf(L"%ls\r\n",L"load failed");
            return wstring(L"load failed\n");
       }
       int sheetscount = e->GetTotalWorkSheets();
       outputstr +=L"\"count\":"+ std::to_wstring(sheetscount);
       outputstr+=L",\"tablenames\":[";
       for (size_t i = 0; i < sheetscount; i++)
       {
    
          outputstr+=L"\"";
          outputstr+=e->GetUnicodeSheetName(i);
          outputstr+=L"\"";
          if(i!=sheetscount-1){
    
              outputstr+=L",";
          }
       }
       outputstr+=L"]},\"data\":{";
        for (size_t i = 0; i < sheetscount; i++) {
    
            BasicExcelWorksheet* sheet1 = e->GetWorksheet(i);
            if (sheet1) {
    
              outputstr += L"\"";
              outputstr += e->GetUnicodeSheetName(i);
              outputstr += L"\":[";
              readsheet(sheet1);

              outputstr += L"]";
              if(i!=sheetscount-1){
    
                outputstr += L",";
              }
            }
        }
       outputstr += L"}}";
       wprintf(L"%ls",outputstr.c_str());
       return outputstr;
    }
};
EMSCRIPTEN_BINDINGS(myclass) {
    
  class_<myclass>("myclass")
    .constructor()
    .function("loadexcel", &myclass::loadexcel)
    .function("saveexcel", &myclass::saveexcel)
    ;
}
 void setup_IDBFS(){
    
      EM_ASM(
        FS.mkdir('/data');
        FS.mount(IDBFS,{
    },'/data');
      );
    }
int main()
{
    
  setup_IDBFS();
  setlocale(LC_ALL, "chs");
  return 0;
}

BasicExcel使用就不说了,想了解的话,可以去看我以前写的文章。

主要说说emscripten相关部分

EMSCRIPTEN_BINDINGS的作用是建立一个C++类和JS类之间的绑定,绑定后,在前台js代码里,可以像使用一个js类一样使用它

function(“loadexcel”, &myclass::loadexcel),意思是,导出这个函数给前台JS使用,第一个参数,是在JS里调用方法的名称,第二个参数就是你的C++成员函数引用

EM_ASM(
  FS.mkdir('/data');
  FS.mount(IDBFS,{
    },'/data');
);

这段代码表示创建一个虚拟的data文件夹,然后把他挂载到IDBFS系统中,以后对excel文件的操作都在这个文件夹中

setup_IDBFS函数的作用,是建立一个文件系统,采用IDBFS方式挂载虚拟文件的路径。因为WebAssembly终究还是运行在一个虚拟系统中,不可能访问你的硬盘系统,所以setup_IDBFS,意思就建立了一个JS的文件系统和你的C++代码文件系统的桥梁。

除了IDBFS,还有其它文件系统,每种文件系统的使用场景是不同的。可以自己去看看教程

编写cmake

CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(cx)
AUX_SOURCE_DIRECTORY(. DIR_SOURCE)
SET(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
SET(CMAKE_C_COMPILER emcc)
SET(CMAKE_CPP_COMPILER 'em++')

set(CMAKE_CXX_FLAGS "--bind")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='[FS]' -s INITIAL_MEMORY=268435456 \
    -s ASSERTIONS=1 \
    -s STACK_OVERFLOW_CHECK=2 \
    -s PTHREAD_POOL_SIZE_STRICT=2 \
    -s ALLOW_MEMORY_GROWTH=1 \
    -lidbfs.js")

ADD_EXECUTABLE(app ${DIR_SOURCE})

如果对CMAKE不太了解的话,可以先看看文档,简单了解一下cmake.

set(CMAKE_CXX_FLAGS "--bind")

表示要启用绑定功能,就是c++里写的EMSCRIPTEN_BINDINGS

CMAKE_EXE_LINKER_FLAGS 选项中lidbfs.js,表示要使用IDBFS文件方式

EXTRA_EXPORTED_RUNTIME_METHODS表示要导出FS文件系统前台使用

这样,C++部分就算完成了,就开始编译了,

在C++项目文件夹中执行如下命令,

mkdir build

进入build文件夹,执行

emcmake cmake ..

在这里插入图片描述
输出大概如上图

执行完成后,就出现了app.js app.wasm两个文件,这就是你的c++编译后的文件,app.wasm是c++编译后的文件,app.js是一个类似胶水的文件,把你的wasm和Js文件关联在一起,当然没有这个也可以,你也可以自己写调用模块
在这里插入图片描述
编写javascript

应为这个比较简单,本段只贴出操作的核心部分。其它部分省略

function writefile(e){
    
    let result=reader.result;
    const uint8_view = new Uint8Array(result);
    FS.writeFile('/data/'+file.name, uint8_view)
}
function savetodisk() 
{
    
   var data=FS.readFile("/data/demo.xls");
   var blob;
   blob = new Blob([data.buffer], {
    type: "application/vnd.ms-excel"});
   saveAs(blob, "demo.xls");
}
function myfun(e) {
    
    let files = document.getElementById('file').files;
    file=files[0];
    reader.addEventListener('loadend', writefile);
    reader.readAsArrayBuffer(file);
    setTimeout(()=>{
           
      let jsonstr = instance.loadexcel(file.name);
       //code...... 
    },1000)
}

writefile函数的功能,是打开本地的excel文件,在虚拟的路径/data/当中,创建一个文件,这样C++里就能读取到它了,这里的data就是C++文件中setup_IDBFS创建的文件夹

savetodisk是从虚拟文件路径中,读取demo.xls文件,然后在浏览器端下载,这个demo.xls是在C++文件中saveexcel函数创建的

编写HTML

这就简单的多,代码就不贴了,主要就是把你刚才生成的app.js引入就行了,wasm不用引入,app.js会加载它

<script type="text/javascript" src="app.js" async></script>

运行程序

wasm文件不能直接打开运行,所以需要一个服务器方式运行,我采用的是anywhere,用其它的iis,apache都可以

运行界面如下
在这里插入图片描述
打开调试工具,网络->wasm,可以看到app.wasm已经加载完了
在这里插入图片描述
接下来就可以打开excel文件了,点击选择,选中第一步创建好的excel文件,确定
在这里插入图片描述
显示的内容,就是excel文件的内容

然后随便改改单元格里的内容
在这里插入图片描述
点保存
在这里插入图片描述
提示下载新的文件,下载好后打开
在这里插入图片描述
就是刚才改完的内容

到此,全部功能开发完成

完整源代码和演示代码去下面的地址下载

https://github.com/ltframe/code/tree/main/wasm-excel-demo

二、WebAssembly的未来

WebAssembly在速度方面,确实比Javascript提升很多,加上有很多的底层语言编写的库,这样就使得web应用的功能提升很多,比如我们通常如果要读取excel的话,一般做法是把excel上传到服务器,然后服务器解析后,再返回前端,现在有了wasm这种方式,在前端就可以搞定,这样服务器的压力少了很多。

还有,以前浏览器不能实现的功能,通过wasm也能实现了。比如,如果你把ffmpeg用WebAssembly编译后,那么浏览器上将可以播放除了mp4以外更多的视频格式,这确实就突破了浏览器本身很多的限制

未来,会有更多WebAssembly应用出现,一些原本只能运行在桌面上的应用,会随着WebAssembly的发展,更多的出现在web应用中

原网站

版权声明
本文为[你的薄荷醇]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_44305576/article/details/125545900