• 第5讲:建立自己的C函数库,js调用自己写的C/C++函数,并包含依赖C/C++第三方静态库。


    在javascript中,Array有很多内置的功能,比如Array.map,Array.filter,Array.find等等,能用内置的功能就用内置的功能,最好不要自己实现一套,因为底层调用的可能压根就不是js语言本身,底层的实现可能由C/C++实现的。如果我们要做的一些功能,需要高性能密集计算,但是JavaScript内置函数无法满足我们要求的时候,这时候我们就要自己用C/C++编写一个程序,然后封装成wasm文件给JavaScript调用了,此时wasm还包含了.a文件这样的第三方库。

    我们这里有个需求,就是在地球上有两艘船,船A和船B在某个经纬度位置触发,以某个航向、速度行驶,求它们间最小距离是多少,达到最小距离的时候,经过时间是多少秒?
    首先这个功能用C/C++来编写,并且还要用到开源第三方库。
    下图的红圈注释里面有几个参数,分别表示经度、纬度、速度、航向,当然getCPA最后一个参数6.5表示6.5分钟的时间长度。表示计算6.5分钟以内,两船最小距离是多少,并且到达最小距离时,经过时间是多少。
    在这里插入图片描述

    打开Visual Studio 2022,新建一个cmake工程,项目名称为GeoCompute。这仅仅是一个测试项目,如果测试通过,没有问题了,就把该代码交给emcc或者em++去编译。
    CMakeLists.txt文件内容如下:
    在这里,我采用vcpkg来安装GeographicLib库
    可以执行如下命令安装。

    vcpkg install GeographicLib:x64-windows
    
    # CMakeList.txt: GeoCompute 的 CMake 项目,在此处包括源代码并定义
    # 项目特定的逻辑。
    #
    cmake_minimum_required (VERSION 3.8)
    set(VCPKG_ROOT "D:/CppPkg/WinVcpkg/vcpkg" CACHE PATH "")
    set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
    # Enable Hot Reload for MSVC compilers if supported.
    if (POLICY CMP0141)
      cmake_policy(SET CMP0141 NEW)
      set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>")
    endif()
    
    project ("GeoCompute")
    
    # 将源代码添加到此项目的可执行文件。
    add_executable (GeoCompute "GeoCompute.cpp"  )
    
    
    find_package (GeographicLib CONFIG REQUIRED)
    target_link_libraries (GeoCompute PRIVATE ${GeographicLib_LIBRARIES})
    
    if (CMAKE_VERSION VERSION_GREATER 3.12)
      set_property(TARGET GeoCompute PROPERTY CXX_STANDARD 20)
    endif()
    
    # TODO: 如有需要,请添加测试并安装目标。
    
    

    然后编写一个GeoCompute.cpp文件

    #include 
    #include 
    #include 
    #include 
    #include 
    
    const double EARTH_RADIUS = 6377830.0;  // 地球的平均半径,单位为千米
    const double M_PI = 3.14159265359;
    
    struct LatLon {
        double first;
        double second;
    };
    
    double deg2rad(double deg) {
        return deg * M_PI / 180.0;
    }
    
    double haversine_distance(double lat1, double lon1, double lat2, double lon2) {
        double dlat = deg2rad(lat2 - lat1);
        double dlon = deg2rad(lon2 - lon1);
    
        double a = std::sin(dlat / 2) * std::sin(dlat / 2) +
            std::cos(deg2rad(lat1)) * std::cos(deg2rad(lat2)) *
            std::sin(dlon / 2) * std::sin(dlon / 2);
    
        double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));
    
        return EARTH_RADIUS * c;
    }
    
    LatLon new_position_with_geolib(double lat, double lon, double speed, double cog, double T) {
        const GeographicLib::Geodesic& geod = GeographicLib::Geodesic::WGS84();
    
        double s12 = speed * T;
        double lat2, lon2;
    
        // Direct method gives the destination point given start point, initial azimuth, and distance
        geod.Direct(lat, lon, cog, s12, lat2, lon2);
        return { lat2, lon2 };
    }
    
    double new_distance(double T, double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB) {
        auto resA = new_position_with_geolib(latA, lonA, speedA, cogA, T);
        auto resB = new_position_with_geolib(latB, lonB, speedB, cogB, T);
        return haversine_distance(resA.first, resA.second, resB.first, resB.second);
    }
    
    LatLon getCPA(double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB, double tcpa) {
        double RES_TCPA = INFINITY;
        double RES_DCPA = INFINITY;
        double prev_dist = INFINITY;
        double cur_dist = INFINITY;
        std::vector<int> status;
        int t_lim = tcpa * 60;
        int step = 1;
        if (t_lim > 600) {
            step = int(double(t_lim) / 300.0);
        }
        for (int t = 0;t < t_lim; t += step) {
            prev_dist = new_distance(t, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
            cur_dist = new_distance(t + step, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
            if (prev_dist < RES_DCPA) {
                RES_DCPA = prev_dist;
            }
            if (cur_dist - prev_dist <= 0) {
                if (status.size() == 0) {
                    status.emplace_back(-1);
                }
            }
            else {
                if (status.size() == 0) {
                    status.emplace_back(1);
                    break;
                }
                else {
                    if (status[0] == -1) {
                        status.emplace_back(1);
                    }
                }
            }
            if (status.size() == 2 && status[0] == -1 && status[1] == 1) {
                RES_TCPA = t;
                break;
            }
        }
        return { RES_TCPA, RES_DCPA };
    }
    
    //1.  一开始距离就变大
    // 2. 从0时刻到指定tcpa一直减小
    // 3. 从0时刻到指定tcpa,先减小后增大
    int main() {
        double lat = 40, lon = 100, speed = 10, cog = 45, T = 3600;
        auto result = new_position_with_geolib(lat, lon, speed, cog, T);
        std::cout << "New Latitude: " << result.first << ", New Longitude: " << result.second << std::endl;
        /*
        latA, lonA, speedA, cogA = 21.3058, 109.1014, 15.12, 187.13
        latB, lonB, speedB, cogB = 21.288205, 109.118725, 3.909777, 254.42
        */
        int i = 0;
        while (true) {
            // 先减小后增大
            auto res_ = getCPA(21.3058, 109.1014, 15.12, 187.13, 21.288205, 109.118725, 3.909777, 254.42, 6.5);
            //auto res_ = getCPA(22.3058, 108.1014, 15.12, 187.13, 21.288205, 109.118725, 3.909777, 254.42, 6.0);
            //auto res_ = getCPA(0.0, 0.0, 15.12, 225.0, 0.0000001, 0.0000001, 3.909777, 45.0, 6.0);
            std::cout << res_.first << " --- " << res_.second << std::endl;
            i++;
            printf("%d\n", i);
        }
    
        return 0;
    }
    
    

    好了,如果代码测试完成了。现在我们创建一个cmake工程,项目名为EmscriptenTest,这是用来生成wasm文件和js文件来给JavaScript调用的。

    由于JavaScript运行在浏览器,不能直接支持windows的lib静态库,所以想办法得到.a库。
    首先从github拉取geographiclib库的源码

     git clone https://github.com/geographiclib/geographiclib.git
    

    然后进入到根目录:

    cd geographiclib
    

    最后用emcmake和emmake命令编译代码(前提是要安装emsdk:官网有教程说明:https://emscripten.org/docs/getting_started/downloads.html)

    emcmake cmake .
    
    emmake make
    

    编译完成之后:
    ![(https://img-blog.csdnimg.cn/direct/3e76d9aba3834e01b2894e163ee5fcc8.png)

    转到目录geographiclib/src
    找到libGeographicLib.a文件,然后把该文件拷贝到EmscriptenTest项目的lib目录下(如果没有lib目录则自己新建)

    然后是CMakeLists.txt文件:

    # CMakeList.txt: EmscriptenTest 的 CMake 项目,在此处包括源代码并定义
    # 项目特定的逻辑。
    #
    cmake_minimum_required (VERSION 3.8)
    set(CMAKE_TOOLCHAIN_FILE "D:/CppPkg/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake")
    # 手动设置GeographicLib的路径
    set(GEOGRAPHICLIB_INCLUDE_DIR "/path/to/vcpkg/installed/x64-windows/include")
    #set(GEOGRAPHICLIB_LIB_DIR "/path/to/vcpkg/installed/x64-windows/lib")
    set(GEOGRAPHICLIB_LIB_DIR "填写你的EmscriptenTest/lib目录的绝对路径")
    
    # Enable Hot Reload for MSVC compilers if supported.
    if (POLICY CMP0141)
      cmake_policy(SET CMP0141 NEW)
      set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>")
    endif()
    
    project ("EmscriptenTest")
    
    # 将源代码添加到此项目的可执行文件。
    add_executable (EmscriptenTest "GeoCompute.cpp")
    
    include_directories(D:/CppPkg/WinVcpkg/vcpkg/installed/x64-windows/include )
    target_link_libraries(EmscriptenTest ${GEOGRAPHICLIB_LIB_DIR}/libGeographicLib.a)
    
    add_library(GeoCompute STATIC GeoCompute.cpp)
    set_target_properties(GeoCompute PROPERTIES SUFFIX ".wasm")
    set_target_properties(GeoCompute PROPERTIES LINK_FLAGS "--bind -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='GeoComputeModule' -s EXPORTED_FUNCTIONS='[\"getCPA\"]'")
    
    
    if (CMAKE_VERSION VERSION_GREATER 3.12)
      set_property(TARGET EmscriptenTest PROPERTY CXX_STANDARD 20)
    endif()
    
    # TODO: 如有需要,请添加测试并安装目标。
    
    
    

    这个EmscriptenTest.cpp没什么用,里面写个main函数,直接return 0;就完事儿了。
    在项目根目录下新建GeoCompute.cpp文件
    GeoCompute.cpp文件的内容:
    可能会提示报错,但是如果点击重新生成,生成成功的话,是没事儿的。
    在这个文件中,我修改了getCPA函数的返回类型为double* , 因为直接返回TDCPA结构体,JavaScript是无法识别的,一定要返回一个指针。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    const double EARTH_RADIUS = 6377830.0;  // 地球的平均半径,单位为千米
    const double _M_PI = 3.14159265359;
    
    struct LatLon {
        double lat;
        double lon;
    };
    
    struct TDCPA {
        double res_tcpa;
        double res_dcpa;
    };
    
    extern "C" {
        EMSCRIPTEN_KEEPALIVE
        double deg2rad(double deg) {
            return deg * _M_PI / 180.0;
        }
        EMSCRIPTEN_KEEPALIVE
        double haversine_distance(double lat1, double lon1, double lat2, double lon2) {
            double dlat = deg2rad(lat2 - lat1);
            double dlon = deg2rad(lon2 - lon1);
    
            double a = std::sin(dlat / 2) * std::sin(dlat / 2) +
                std::cos(deg2rad(lat1)) * std::cos(deg2rad(lat2)) *
                std::sin(dlon / 2) * std::sin(dlon / 2);
    
            double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));
    
            return EARTH_RADIUS * c;
        }
        EMSCRIPTEN_KEEPALIVE
        LatLon new_position_with_geolib(double lat, double lon, double speed, double cog, double T) {
            const GeographicLib::Geodesic& geod = GeographicLib::Geodesic::WGS84();
    
            double s12 = speed * T;
            double lat2, lon2;
    
            // Direct method gives the destination point given start point, initial azimuth, and distance
            geod.Direct(lat, lon, cog, s12, lat2, lon2);
            return { lat2, lon2 };
        }
        EMSCRIPTEN_KEEPALIVE
        double new_distance(double T, double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB) {
            auto resA = new_position_with_geolib(latA, lonA, speedA, cogA, T);
            auto resB = new_position_with_geolib(latB, lonB, speedB, cogB, T);
            return haversine_distance(resA.lat, resA.lon, resB.lat, resB.lon);
        }
    
        EMSCRIPTEN_KEEPALIVE
        double* getCPA(double latA, double lonA, double speedA, double cogA, double latB, double lonB, double speedB, double cogB, double tcpa) {
            double RES_TCPA = INFINITY;
            double RES_DCPA = INFINITY;
            double prev_dist = INFINITY;
            double cur_dist = INFINITY;
            double* tdcpaPtr = new double[2];
            std::vector<int> status;
            int t_lim = tcpa * 60;
            int step = 1;
            if (t_lim > 600) {
                step = int(double(t_lim) / 300.0);
            }
            for (int t = 0; t < t_lim; t += step) {
                prev_dist = new_distance(t, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
                cur_dist = new_distance(t + step, latA, lonA, speedA, cogA, latB, lonB, speedB, cogB);
                if (prev_dist < RES_DCPA) {
                    RES_DCPA = prev_dist;
                }
                if (cur_dist - prev_dist <= 0) {
                    if (status.size() == 0) {
                        status.emplace_back(-1);
                    }
                }
                else {
                    if (status.size() == 0) {
                        status.emplace_back(1);
                        break;
                    }
                    else {
                        if (status[0] == -1) {
                            status.emplace_back(1);
                        }
                    }
                }
                if (status.size() == 2 && status[0] == -1 && status[1] == 1) {
                    RES_TCPA = t;
                    break;
                }
            }
            tdcpaPtr[0] = RES_TCPA;
            tdcpaPtr[1] = RES_DCPA;
            return tdcpaPtr;
        }
    
    }
    
    

    好了,现在就测试全部重新生成。如果没有报错,则通过测试。通过测试之后。证明可以大胆使用em++命令直接编译GeoCompute.cpp为wasm文件了。

    打开cmd进入到EmscriptenTest工程根目录。

    em++ GeoCompute.cpp -O1 -o libGeoCompute.js -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME="GeoComputeModule" -s "EXPORTED_FUNCTIONS=['_deg2rad', '_haversine_distance', '_new_position_with_geolib', '_new_distance', '_getCPA']" -s "EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" -I D:/CppPkg/WinVcpkg/vcpkg/installed/x64-windows/include -L D:/HighPerformanceProjects/CppProjects/EmscriptenTest/lib -lGeographicLib
    

    我这里导出了所有的函数,函数前面要加上下划线。但是在cmakelists.txt中不需要加下划线。
    执行完成命令之后,会生成libGeoCompute.js和libGeoCompute.wasm文件。

    在这里插入图片描述

    现在可以使用npm创建一个原生的JavaScript项目。
    这是工程目录结构:
    在这里插入图片描述
    最重要是main.js的写法:

    const fs = require('fs');
    const path = require('path');
    
    // 加载 Emscripten 生成的模块
    const Module = require('./libGeoCompute.js');
    
    async function main() {
      try {
        const instance = await Module({
          locateFile: (filename) => path.join(__dirname, filename),
        });
    
        console.log('Module instance:', instance);
        console.log(typeof instance.cwrap)
        // await new Promise((resolve) => {
        //   console.log('Waiting for runtime initialization...');
        //   instance.onRuntimeInitialized = () => {
        //     console.log('Runtime initialized.');
        //     resolve();
        //   };
        // });
    
        // 使用 cwrap 包装 getCPA 函数
        const getCPA = instance.cwrap('getCPA', 'number', [
          'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'
        ]);
        console.log(typeof getCPA)
        // 调用导出的 getCPA 函数
        const resultPtr = getCPA(21.3058, 109.1014, 15.12, 187.13, 21.288205, 109.118725, 3.909777, 254.42, 6.5);
        console.log(typeof resultPtr)
        // 解析返回值(假设返回指向结构体的指针)
        const res_tcpa = instance.HEAPF64[resultPtr >> 3]; // 读取double指针的第0个元素
        const res_dcpa = instance.HEAPF64[(resultPtr >> 3) + 1]; // 读取地址偏移量+1
    
        console.log('TCPA:', res_tcpa);
        console.log('DCPA:', res_dcpa);
    
        console.log('Continuing after runtime initialization.');
    
        // 继续下面的逻辑...
      } catch (error) {
        console.error('Error:', error);
      }
    }
    
    main();
    

    index.html的内容:

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Blank Windowtitle>
    head>
    <body>
      
    body>
    html>
    
    

    package.json的内容:

    {
      "name": "nodedevtest",
      "version": "1.0.0",
      "description": "A minimal Electron application",
      "main": "main.js",
      "scripts": {
        "start": "node main.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "axios": "^1.6.8",
        "electron": "^30.0.1",
        "pixi.js": "^8.1.0",
        "request": "^2.88.2"
      }
    }
    
    

    得出的结果:
    在这里插入图片描述

    得出的结果是154和1519.5687501879786
    表示经过154秒以后,两船达到最小距离,并且最小距离为1519米多。

  • 相关阅读:
    Docker+K3S搭建集群
    华为hwics格式产品文档打开方式
    C++模板与STL(六):内存空间配置器及内存池技术模拟
    akshare复权算法-港股复权后数据代码分享
    队列(定义,基本操作,顺序存储,链式存储)
    血红素-金纳米颗粒(Heme-AuNP)复合纳米酶|金纳米颗粒核多孔空心碳纳米球壳(Au@HCNs)纳米酶
    GitHub无法访问的解决方法
    jni-02、lib路径、数组、对象、引用、extern修饰函数
    用连续自然数之和来表达整数 - 华为OD统一考试(C卷)
    iOS 真机打包,证书报错No signing certificate “iOS Distribution” found
  • 原文地址:https://blog.csdn.net/Xeon_CC/article/details/139957190