热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javascript调用C语言,在 JavaScript 中使用 C 程序

原标题:在JavaScript中使用C程序✦✦✦✦✦✦✦✦Java是个灵活的脚本语言,能方便的处理业务逻辑。当需要传输通信时,我们大多选择

原标题:在 Javascript 中使用 C 程序

2fdbc897f323be3db17d7e72e2d701df.png

✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦

Java 是个灵活的脚本语言,能方便的处理业务逻辑。当需要传输通信时,我们大多选择 JSON 或 XML 格式。

但在数据长度非常苛刻的情况下,文本协议的效率就非常低了,这时不得不使用二进制格式。

去年的今天,在折腾一个 前后端结合的 WAF 时,就遇到了这个麻烦。因为前端脚本需要采集不少数据,而最终是隐写在某个 COOKIE 里的,因此可用的长度非常有限,只有几十个字节。

如果不假思索就用 JSON 的话,光一个标记字段 {"enableXX": true} 就占去了一半长度。然而在二进制里,标记 true 或 false 不过是 1 个比特的事,可以节省上百倍的空间。

同时,数据还要经过校验、加密等环节,只有使用二进制格式,才能方便的调用这些算法。

1优雅实现

不过,Java 并不支持二进制。

这里的「不支持」不是说「无法实现」,而是无法「优雅实现」。语言的发明,就是用来优雅解决问题的。即使没有语言,人类也可以用机器指令来编写程序。

如果非要用 Java 操作二进制,最终就类似这样:

var flags &#61; &#43;enableXX1 <<16 | &#43;enableXX2 <<15 | ...

虽然能实现&#xff0c;但很丑陋。各种硬编码、各种位运算。

然而&#xff0c;对于先天支持二进制的语言&#xff0c;看起来就十分优雅&#xff1a;

union {

struct {

int enableXX1: 1;

int enableXX2: 1;

...

};

int16_t value;

} flags;

flags.enableXX1 &#61; enableXX1;

flags.enableXX2 &#61; enableXX2;

开发者只需定义一个描述即可。使用时&#xff0c;字段偏移多少、如何读写&#xff0c;这些细节完全不用关心。

为了能达到类似效果&#xff0c;起先封装了一个 JS 版的结构体&#xff1a;

// 最初方案&#xff1a;封装一个 JS 结构体

var s &#61; new Struct([

{name: &#39;month&#39;, bit: 4, signed: false},

...

]);

s.set(&#39;month&#39;, 12);

s.get(&#39;month&#39;);

将细节进行了隐藏&#xff0c;看起来就优雅多了。

2优雅但不完美

但是&#xff0c;这总感觉不是最完美的。结构体这种东西&#xff0c;本该由语言提供&#xff0c;如今却要用额外的代码实现&#xff0c;而且还是在运行期间。

另外&#xff0c;后端解码是用 C 实现的&#xff0c;所以得维护两套代码。一旦数据结构或者算法变了&#xff0c;得同时更新 JS 和 C&#xff0c;很麻烦。

于是琢磨&#xff0c;能否共用一套 C 代码&#xff0c;同时用于前端和后端&#xff1f;

也就是说&#xff0c;需要能将 C 编译成 JS 来运行。

3认识 emen

能将 C 编译成 JS 的工具有不少&#xff0c;最专业的要数 emen。

emen 的使用方式很简单&#xff0c;和传统 C 编译器差不多&#xff0c;只不过生成的是 JS 代码。

emcc hello.c -o hello.html

// hello.c

#include

#include

int main() {

time_t now;

time(&now);

printf("Hello World: %s", ctime(&now));

return 0;

}

编译之后即可运行&#xff1a;

a74711cc40eda43caa6df43f5780fe23.png

很有趣吧~ 大家可以尝试下&#xff0c;这里就不多介绍了。

4实用缺陷

然而我们关心的不是有趣&#xff0c;而是实用。

事实上&#xff0c;即使一个 Hello World 编译出来的 JS 也过万行&#xff0c;多达数百 KB。就算压缩再 GZIP&#xff0c;仍有几十 KB。同时 emen 使用了 asm.js 规范&#xff0c;内存访问是通过 TypedArray 实现的。

这意味着 IE10 以下的用户都无法运行。这也是不可接受的。因此&#xff0c;我们得做如下改进&#xff1a;

减少体积

增加兼容

首先寄托 emen 本身&#xff0c;看看能不能通过设置参数&#xff0c;来达到我们的目的。不过一番尝试之后&#xff0c;并没有成功。那只能自己动手实现了。

5减少体积

为什么最终脚本会那么大&#xff0c;里面都放了些什么&#xff1f;分析了下内容&#xff0c;大致有这几个部分&#xff1a;

辅助功能

接口模拟

初始化操作

运行时函数

程序逻辑

辅助功能

比如字符串和二进制转换、提供回调包装等。这些基本都是用不着的&#xff0c;我们可以给自己写个特殊的回调函数。

接口模拟

提供文件、终端、网络、渲染等接口。之前见过用 emen 移植的客户端游戏&#xff0c;看来模拟了不少接口。

初始化操作

全局内存、运行时、各种模块的初始化。

运行时函数

纯粹的 C 只能做简单的计算&#xff0c;很多功能都依靠运行时函数。

不过&#xff0c;有些常用的函数&#xff0c;其背后的实现是及其复杂的。例如 malloc 和 free&#xff0c;对应的 JS 有近 2000 行&#xff01;

程序逻辑

这才是 C 程序真正对应的 JS 代码。因为编译时经过 LLVM 的优化&#xff0c;逻辑可能变得面目全非了。

这部分代码量不大&#xff0c;是我们真正想要的。

事实上&#xff0c;如果程序没有用到一些特殊功能的话&#xff0c;把逻辑函数单独抠出来&#xff0c;仍然是可以运行的&#xff01;

考虑到我们的 C 程序非常简单&#xff0c;所以简单粗暴的提取出来&#xff0c;也是没问题的。

C 程序对应的 JS 逻辑位于 // EMEN_START_FUNCS 和 // EMEN_END_FUNCS 之间。过滤掉运行时函数&#xff0c;剩下的就是 100% 的逻辑代码了。

6增加兼容

接着解决内存访问的兼容性问题。

在很老版本的 emen 里&#xff0c;是可以选择是否使用 TypedArray 的。如果不用&#xff0c;则通过 JS Array 来实现。但如今早已去除了这个参数&#xff0c;只能使用 TypedArray。

首先了解下&#xff0c;为何要用 TypedArray。

emen 申请了一大块 ArrayBuffer 来模拟内存&#xff0c;然后关联了一些 HEAP 开头的变量。

8cc5313959671ac482dce9bc8f3cf39f.png

这些不同类型的 HEAP 共享同一块内存&#xff0c;这样就能高效的指针操作。

然而不支持 TypedArray 的浏览器&#xff0c;显然无法运行。所以得提供个 polyfill 兼容下。

但经分析&#xff0c;这几乎不可能实现 —— 因为 TypedArray 和数组一样&#xff0c;是通过索引来访问的&#xff1a;

var buf &#61; new Uint8Array(100);

buf[0] &#61; 123; // set

alert(buf[0]); // get

然而 [] 操作符在 JS 里是无法重写的&#xff0c;因此难以将其变成 setter 和 getter。况且不支持 TypedArray 的都是低版本 IE&#xff0c;更不用考虑 ES6 的那些特征。

于是琢磨 IE 的私有接口。比如用 onpropertychange 事件来模拟 setter。不过这样做效率极低&#xff0c;而且 getter 仍不易实现。

经过一番考虑&#xff0c;决定不用钩子的方式&#xff0c;而是直接从源头上解决 —— 修改语法&#xff01;

我们用正则&#xff0c;找出源码中的赋值操作:

HEAP[index] &#61; val;

替换成:

HEAP_SET(index, val);

类似的&#xff0c;将读取操作:

HEAP[index]

替换成:

HEAP_GET(index)

这样&#xff0c;原先的索引操作&#xff0c;就变成函数调用了。我们就能接管内存的读写&#xff0c;并且没有任何兼容性问题&#xff01;

然后实现 8、16、32 位有无符号的版本。通过 JS 的 Array 来模拟&#xff0c;非常简单。麻烦的是模拟 Float 类型&#xff0c;不过 C 程序中未用到浮点&#xff0c;所以就没实现。

0c971efbf499f7d318f0970d1c577996.png

如果支持 TypedArray&#xff0c;则使用原生的接口&#xff1b;否则&#xff0c;用 Array 模拟版本。

这样&#xff0c; 既保障了高版本浏览器的性能&#xff0c;又兼顾了老浏览器的功能。

7大功告成

解决了这些缺陷&#xff0c;我们就可以愉快的在 JS 中使用 C 逻辑了。

脚本&#xff0c;只关心业务逻辑。例如采集哪些数据&#xff0c;这样代码就非常的优雅&#xff1a;

089f221319a459d16eca594fc2db394a.png

数据的储存、加密、编码&#xff0c;这些二进制操作&#xff0c;则通过 C 实现。

efa400f123417902dedfc1afc947402a.png

编译时使用 -Os 参数优化体积&#xff0c;最终的 JS 精简压缩之后&#xff0c;还不到 2 KB&#xff0c;十分小巧精炼。

120adb38d025bcc5c4ac0ed4f23bf412.png

于是&#xff0c;这个「前后端 WAF」开发就容易多了。我们只需维护一份代码&#xff0c;即可同时编译出前后端两个版本&#xff01;

所有的数据结构和算法&#xff0c;都由 C 实现。前端编译成 JS 代码&#xff0c;后端编译成 lua 模块&#xff0c;供 nginx-lua 使用。

4f6a33d612dafa35c416c6a66f930d17.png

前后端的脚本&#xff0c;都只需关注业务功能即可&#xff0c;完全不用涉及数据层面的细节。

测试版

事实上&#xff0c;还有第三个版本 —— 本地版。

因为所有的 C 代码都在一起&#xff0c;因此可以方便的编写测试程序。

这样就无需启动 WebServer、打开浏览器来测试了。只需模拟一些数据&#xff0c;直接运行程序即可测试&#xff0c;非常轻量。

同时借助 IDE&#xff0c;调试起来更容易。

8小结

每一门语言都有各自的优缺点。将不同语言的优势相互结合&#xff0c;可以让程序变得更优雅、更完美。

✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦

原文&#xff1a;http://www.cnblogs.com/index-html/p/using_c_in_java.html

点击“阅读原文”&#xff0c;看更多

精选文章

责任编辑&#xff1a;



推荐阅读
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 检查在所有可能的“?”替换中,给定的二进制字符串中是否出现子字符串“10”带 1 或 0 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 深入解析C语言中结构体的内存对齐机制及其优化方法
    为了提高CPU访问效率,C语言中的结构体成员在内存中遵循特定的对齐规则。本文详细解析了这些对齐机制,并探讨了如何通过合理的布局和编译器选项来优化结构体的内存使用,从而提升程序性能。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 在C#编程中,数值结果的格式化展示是提高代码可读性和用户体验的重要手段。本文探讨了多种格式化方法和技巧,如使用格式说明符、自定义格式字符串等,以实现对数值结果的精确控制。通过实例演示,展示了如何灵活运用这些技术来满足不同的展示需求。 ... [详细]
  • 本文深入探讨了MDK链接脚本的应用与优化技巧。首先,文章介绍了链接脚本的基本概念及其在嵌入式系统开发中的重要性。接着,通过具体实例详细分析了链接脚本的结构和功能,特别是在程序在FLASH中运行时,如何优化链接脚本以提高系统性能。此外,文章还讨论了无需将程序加载到SRAM中的技术细节,为开发者提供了实用的参考和指导。 ... [详细]
  • 在C语言程序开发中,调试和错误分析是确保代码正确性和效率的关键步骤。本文通过一个简单的递归函数示例,详细介绍了如何编写和调试C语言程序。具体而言,我们将创建一个名为 `factorial.c` 的文件,实现计算阶乘的功能,并通过逐步调试来分析和解决可能出现的错误。此外,文章还探讨了常见的调试工具和技术,如GDB和断点设置,以帮助开发者高效地定位和修复问题。 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • 本教程详细介绍了如何使用 Spring Boot 创建一个简单的 Hello World 应用程序。适合初学者快速上手。 ... [详细]
  • 本文详细介绍了如何在Unity中实现一个简单的广告牌着色器,帮助开发者更好地理解和应用这一技术。 ... [详细]
author-avatar
不会游泳的小饼儿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有