B/S技术开发桌面应用
随着HTML5规范的越来越完善,其功能也越来越强大,用Web技术来构建界面的代价远远小于传统桌面程序开发。笔者从事web开发多年,对B/S开发技术积累比较好,对传统桌面开发只知道一些皮毛。最近有这样一个需求:将一个用java开发的web网站做成单机版,前端页面使用到了ativex技术(IE Only)。于是笔者踏上了漫漫长征路,期间用到了Java、Node.js、C/C++、VB6.0、C#这5种开发语言,碰到了各种各样奇葩的问题,所幸的是百度虽不靠谱但谷歌还是靠谱的,国内外优秀的文章帮我解决了不少问题,下面进入正题。
1、客户端
经过我的调研,目前适合做成客户端的浏览器主要有两款,Electron 和 NW.JS。您可能要说了,为什么不用Delphi或者VB6.0内嵌的WebBrower控件做客户端,基于IE内核的,刚好需求里面网页也用到的了activeX,一举两得。笔者尝试过类似的解决方案,用了J2SE的两款swing浏览器,DJNativeSwing和JDIC,都是基于IE内核的,这种方法的主要问题是需要设置用户的IE浏览器,要允许AtiveX控件运行、加入可信站点、允许跨域访问什么的,而且客户机器千奇百怪很有可能缺少xxx.dll或者IE本身就是有问题的,因此客户端选择独立浏览器,不依赖于IE是我最终的选择。
言归正传,Electron 和 NW.JS技术架构是一致的,界面展示基于chromium浏览器(Chrome的内核也是chromium,chromium对HTML5支持的相当好),然后对本地操作的能力则是利用的node.js,在此不详细展开说chromium和node.js怎么结合起来的,相关知识大家自行了解,我们来说一下这两者区别:
(1)Electron http://www.wllm.com.cn/
①node.js的context和javascript的context是混合的,即在node.js里面可以访问页面dom元素
②node.js的第三方扩展用node-gyp编译后可以直接使用,无需再次编译
③不支持XP
(2)NW.JS(原名Node-Webkit)http://nwjs.io/
①v0.13.0以前的版本node.js和javascript的context是分开的,不可在node.js里里面访问页面dom元素
②v0.13.0以前的版本node.js的第三方扩展用node-gyp编译后,还得经过nw-gyp编译才能使用(大坑,很多第三方扩展node-gyp编译好了,nw-gyp怎么也编译不通过)
③支持XP
综上,我选择了NW.JS的最新版本(v0.13.0-rc1),要在去年还真不好决定选用哪个(2015年我尝试了nw.js的v0.12.0,死在nw-gyp打包第三方扩展上了)。
好了,独立浏览器已就绪,能很好的支持HTML5了,但是问题来了,IE的Ativex控件如何替代呢?
我们来分析下,这个Activex控件就是VB和Delphi里面的RichTextBox,作用是用来显示RTF富文本格式文档的。那么解决问题的办法有两个:
(1)将rtf转换成pdf
我们知道Chrome是原生支持打开pdf格式文档的,直接拖进去就可以打开,这个功能其实在chromium就有,但是有弊端,就是pdf文档的周围会有一堆工具按钮,打印、旋转什么的,我尝试了一下,无法隐藏,没去深究,放弃。
(2)将rtf转换成html5
在网上搜索了rtf转html方案,得到两个dll,一个dll是vc写的,支持将rtf转换成html4+本地图片文件,一个dll是c#写的,支持将rtf转换成html5,图片则以base64编码形式内嵌在html5页面里面,我选择了c#的dll程序集。但是这两个dll都不支持rtf文档里面同时含有内码和unicode码,转换会出现乱码,解决方法是调用RichTextBox控件先将同时含有内码和unicode码的rtf文本转成只含有内码的rtf文本再转换为html5。
下面就是node.js和c#程序集、VB的ocx控件交互的问题了。
碰到多语言交互我的第一想法就是建立一个统一的接口,各个语言开发接口的实现类和调用类,然后客户端按照接口调用服务端就完事。找到了大名鼎鼎的Thrift,能生成所有主流编程语言待实现接口。
Thrift的C#端开发参见
http://www.cnblogs.com/liping13599168/archive/2011/09/15/2176836.html
Thrift的Java端开发参见
http://blog.csdn.net/m13321169565/article/details/7836006
Thrift的Nodejs端开发参见
http://snoopyxdy.blog.163.com/blog/static/6011744020153243315712/
定义好通用接口文件后,利用Thrift生成node.js的客户端接口和C#的服务端接口,成功的实现了node.js和C#的交互,但是这种交互模式是C/S模式的,就是用户打开客户端以后,你还得开一个进程运行C#开发的server.exe,感觉简单的问题复杂化了,不划算,所以在此处使用Thrift是杀鸡用牛刀了,弃之,另想他法。
第二个多语言交互的想法就是COM接口了。VB和C#都提供COM组件让nodejs调用。
C#开发COM组件参见
http://www.cnblogs.com/panlijiao/archive/2012/10/14/2773882.html
这里面有个坑说出来大家注意下:
C#开发的COM组件不是标准的COM组件,不能像VB那样用regsvr32.exe注册,是用的.net提供的RegAsm.exe工具注册的,分为全局注册(GAC)和私有注册。全局注册以后就跟VB控件一样,无论你的dll放在哪里,主程序都能找到,私有注册你的程序集dll必须放在主程序同一目录下,否则找不到。全局注册要求你的程序集dll及依赖的程序集dll都必须是强签名的,我在这里浪费了一天时间,因为我上面找到的C#的dll不是强签名的,然后按照网上的方法用ILDASM.exe反汇编dll为il,强签名之后再重新汇编为dll。但是发现该dll不让反汇编,更改了ILDASM.exe源码,强行反汇编之后强签名,再用ILASM.exe重新汇编为dll,发现无法正常使用了。放弃,选择私有注册。
.Net程序集强签名参见
http://www.cnblogs.com/philzhou/archive/2012/12/06/2804680.html
去掉ILDasm的SuppressIldasmAttribute限制参见
http://www.cnblogs.com/TianFang/archive/2012/12/09/2810344.html
COM组件开发完毕之后node.js如何调用,有两个选择,一个是选择现有的第三方插件,二是自己实现插件。
(1)现有的第三方插件
一开始我想一步到位,node.js直接调用com,在npm(Node.js 的包管理器 ,是全球最大的开源库生态系统)上找到了一个满意的插件win32ole,但是该插件比较陈旧,要求node.js版本在v0.12.0以下,而nw.js的v0.13.0-rc1内置node.js版本都到v5.x.x了,无奈放弃。
后来又找到node.js的明星插件node-ffi,可以调用c++的动态链接库dll,这样就要求我在vc++里面开发调用c#和vb的com组件的dll,我盘算了一下,既然都要用到vc++开发了,何不直接开发node.js第三方插件,省去使用node-ffi。
(2)自己实现node.js插件
node.js插件开发环境搭建,就是去node.js官网下载node.js的头文件和静态库node.lib,在vs工程新建dll工程,设置工程属性,附加头文件目录添加你下载的头文件所在目录,附加库文件目录添加node.lib所在目录即可,当然你也可以选择下载node.js源代码编译生成头文件以及静态库node.lib,然后再引用。
node.js插件开发参见
http://nodejs.cn/doc/node/addons.html
vc++调用com组件方法参见
http://www.cppblog.com/woaidongmao/archive/2011/01/10/138250.html
在这里多说一句,我本来想偷下懒在VC++里面直接调用C#的dll程序集的,大家知道VC++是非托管语言,不依赖于.net,要想不通过COM组件调用C#程序集,要么把工程属性调成公共语言库支持,要么是用CLRRuntime模拟CLR执行环境,然后invoke调用C#程序集里面的方法。
①把工程属性调成公共语言库支持
VC++通过gcnew以及^来创建和访问C#的类,详细参见
http://www.2cto.com/kf/201505/401650.html
这个方法程序VS能正常编译,node-gyp编译不通过,错误一大堆,不提也罢
②是用CLRRuntime
参见
弊端是要求.net frmaework 4.0(可能有降低要求的方法),而win7才自带.net frmaework 3.5,感觉不太划算。
两条路都不顺,我就默默回到了COM组件的道路上。
对了,在这里提醒大家注意一下,多语言交互乱码问题最是常见,开发过程中我总结了一下:
C/C++的char*是ansi码,COM组件接口里面的BSTR是unicode码,node.js的v8引擎中字符串是utf-8码,
三种编码的转换函数贴出来,按照上面的关系选择转换函数,就不会乱码。
wchar_t * ANSIToUnicode(const char* str) { int textlen; wchar_t * result; textlen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); result = (wchar_t *)malloc((textlen + 1)*sizeof(wchar_t)); memset(result, 0, (textlen + 1)*sizeof(wchar_t)); MultiByteToWideChar(CP_ACP, 0, str, -1, (LPWSTR)result, textlen); return result; } char * UnicodeToANSI(const wchar_t* str) { char* result; int textlen; textlen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL); result = (char *)malloc((textlen + 1)*sizeof(char)); memset(result, 0, sizeof(char)* (textlen + 1)); WideCharToMultiByte(CP_ACP, 0, str, -1, result, textlen, NULL, NULL); return result; } wchar_t * UTF8ToUnicode(const char* str) { int textlen; wchar_t * result; textlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); result = (wchar_t *)malloc((textlen + 1)*sizeof(wchar_t)); memset(result, 0, (textlen + 1)*sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, str, -1, (LPWSTR)result, textlen); return result; } char * UnicodeToUTF8(const wchar_t* str) { char* result; int textlen; textlen = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); result = (char *)malloc((textlen + 1)*sizeof(char)); memset(result, 0, sizeof(char)* (textlen + 1)); WideCharToMultiByte(CP_UTF8, 0, str, -1, result, textlen, NULL, NULL); return result; } char* ANSIToUTF8(const char* str) { return UnicodeToUTF8(ANSIToUnicode(str)); } char* UTF8ToANSI(const char* str) { return UnicodeToANSI(UTF8ToUnicode(str)); }
调用示例
void MyObject::GetQuestionContent(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); MyObject* obj = ObjectWrap::Unwrap(args.Holder()); _bstr_t PaperFile = UTF8ToUnicode(*v8::String::Utf8Value(args[0]->ToString())); _bstr_t ResDir = UTF8ToUnicode(*v8::String::Utf8Value(args[1]->ToString())); _bstr_t InUserKey = UTF8ToUnicode(*v8::String::Utf8Value(args[2]->ToString())); long QuestionTypeID = args[3]->Int32Value(); long QuestionID = args[4]->Int32Value(); BSTR msg = 0; _bstr_t rtf_old = obj->pAccountPaperParser->GetQuestionContent(PaperFile, ResDir, InUserKey, QuestionTypeID, QuestionID, &msg); obj->outMsg = msg; char* pParams[] = { _com_util::ConvertBSTRToString(rtf_old) }; VARIANT varOutput; CallStringMethodByName(obj->pVB, "convertRtf2Rtf", 1, pParams, &varOutput); _bstr_t html = obj->pCSharp->convertString(varOutput.bstrVal); args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, UnicodeToUTF8(html))); ::SysFreeString(msg); ::SysFreeString(rtf_old); VariantClear(&varOutput); ::SysFreeString(html); }
此处系统函数_com_util::ConvertBSTRToString等价于UnicodeToANSI。
此外,由于在VC++使用了 #import "组件所在目录myCom.dll" no_namespace 这样的调用COM组件的语法,VS编译可以通过,node-gyp认为是线程不安全的,会编译不通过,解决办法是node-gyp configure生成待编译的vc++工程之后,修改生成的xxxx(你的插件名称).vcxproj,去掉包含/MT字符串的这一行,然后再继续node-gyp build即可。
最后,VS和node-gyp生成的插件DLL,node.js可以直接使用,nw.js要想直接使用,要做点小手脚,即替换node-gyp的win_delay_load_hook.c文件后再用node-gyp编译,生成的addon在node.js和nw.js就都可以用了,比以前需要用nw-gyp再编译一遍爽多了。
参见
http://docs.nwjs.io/en/v0.13.0-rc1/For%20Users/Advanced/Use%20Native%20Node%20Modules/
有这么一段:
Starting from 0.13.0, native modules built by node-gyp or npm in upstream can be supported.
In Linux and OSX you can just load the native module directly. In windows you’ll need to replace the file
%APPDATA%\npm\node_modules\node-gyp\src\win_delay_load_hook.c with the one at https://github.com/nwjs/nw.js/blob/nw13/tools/win_delay_load_hook.c
Before 0.13.0, the V8 version and Node ABI in NW.js is different from official Node.js. To use native Node.js modules with NW.js, you have to rebuild the modules with one of following tools.
到此客户端大局已定,我还面临着最后一个问题,即VB的控件RichtextBox(即richtx32.ocx)不是dll,而是ocx,大家知道dll是没有界面的,但是ocx是有界面的,将有界面的ocx当成普通的Activex dll来看待,使用CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid)强行创建的后果就是控制台程序在win7运行正常,在xp去运行失败,直接闪退,原因应该是找不到宿主窗口。
这个问题也困扰了我一天,最终国外一篇文章有说道:
http://www.codeproject.com/script/Articles/ArticleVersion.aspx?aid=51&av=36826
通过走IDispatch接口创建com组件,可以在xp成功运行,具体原理未深究,感觉是绕过了宿主窗口检测。
客户端到此结束,一个增强型的chromium浏览器诞生。
2、服务端
服务器端是一个标准J2EE war包,那么如何在本地发布,我有以下两个方向的尝试
(1)使用php改写项目
大家知道,自PHP5.4之后 PHP内置了一个Web 服务器,php -S localhost:8080即可启动,个人测试及小网站不必依赖于Apache或者Nginx。
参见 http://yuankeqiang.lofter.com/post/8de51_b36213
此种方法的有点显而易见,php环境运行小,只有十几兆,jre环境则太笨重。不过看着war里密密麻麻的.java和.jsp文件我放弃了这种想法,改写工作量太大。
(2)选择tomcat embeded还是jetty
还是抱java的大腿,毕竟那个war包我可以不用动它了,内置一个servlet容器去运行它,tomcat embeded、jetty都不错,jetty稍微小那么一点点,我就选了jetty。另外如果你不想基于现有war包添加servlet或者filter那么可以在启动jetty的时候直接添加,参见:
http://blog.chenlb.com/2012/05/embed-jetty-http-servlet-jsp.html
服务器改动不大,比较困难的问题是服务器程序打包。java程序跨平台的代价就是,它必须依赖jre。传统的做法是用exe4j将jar打成exe,并且把同目录下的jre文件夹当成依赖目录,一并发送给用户。我一直以来都是这么做的,主要问题是jre文件太多,太大,本次项目寻求新的解决方法。
(1)直接将java代码编译成本地代码
①开源项目GCJ
编译环境要求多,蛮复杂,没仔细研究
②Excelsior JET
看视频应该很好用,但是收费,预算足的可以考虑
(2)一种巧妙地桥接方式
C#在推出的时候,借鉴了java语言的设计思路,因此很多C#的类和java的类是相似的。ikvm.net就是这样诞生的,他将java的类映射到.net上面的类,从而实现让java代码运行在.net framework上面,将你的jar包编译为dll程序集。
参见
我就是用的ikvm将所有jar包转成了dll,跑在了.net framework上,由于win7以上系统都自带.net framework,因此也算是脱离了jre,减小了程序体积。
3、数据库
原本B/S架构的数据库服务器是SQLServer,移植到单机版,优选Access,Java连接Access有两种方式:
(1)jdbc-odbc桥
这种方式依赖于操作系统的JET引擎,我测试了一下,在XP上打不开Access2007以上创建的.accdb数据库文件,只能连接.mdb数据库文件,而.mdb的加密算法已经落伍了,随便一个破解软件几秒钟就能试出密码。
(2)纯jdbc
①HXTT MS Access JDBC Drivers
http://www.hxtt.com/access.html
收费软件,未注册版运行一次不能查询超过50次,一次返回的记录数不能超过1000条,使用过程中发现连接Access2010创建的有密码.accdb数据库文件好像有问题
②UCanAccess
https://sourceforge.net/projects/ucanaccess/
开源Access数据库JDBC驱动,完美支持.accdb数据库,不过要想支持有密码的数据库文件,得依赖于jackcess解密库,并且自己实现调用解密库打开加密文件的解密类,下面贴上示例代码:
public static Connection getConnection(String dbpath) throws Exception { Connection conn = null; Class.forName("net.ucanaccess.jdbc.UcanaccessDriver"); String dbur1 = "jdbc:ucanaccess:///" + dbpath + ";jackcessOpener=com.test.utils.CryptCodecOpener"; conn = DriverManager.getConnection(dbur1, "admin", "密码"); return conn; }
com.test.utils.CryptCodecOpener 类如下
public class CryptCodecOpener implements JackcessOpenerInterface { public Database open(File fl, String pwd) throws IOException { DatabaseBuilder dbd = new DatabaseBuilder(fl); dbd.setAutoSync(false); dbd.setCodecProvider(new CryptCodecProvider(pwd)); dbd.setReadOnly(false); return dbd.open(); } }
到此为止,一切问题似乎都已经解决,就等着打包了。然而,最终boss却在此时出现了。
这个得从UCanAccess的实现机制来讲,它是启动一个hsql内存数据库,(从他依赖于hsqldb.jar也可以看出来),然后把Access数据库表加载进入hsql数据库里面去,(所以UCanAccess很占内存,刚打开数据库文件时能飙到200M),本来在标准jre下,这一切都没有问题,但是我把服务端程序打成jar,用ikvm.net转换成dll放在.net环境上运行时,.net表示无法创建hsql内存数据库。于是,一朝回到解放前。
痛定思痛,我决定更换数据库,换成sqlite,本身小、不占内存、查询据说还比Access快。但是开源版的sqlite是没有加密数据库功能的,作者只是预留了sqlite3_key和sqlite3_rekey接口,并未实现,有加密数据库功能的sqlite3作者是收费的,而且就算我买了也没用啊,提供的是dll,我java还得用jna去调用dll,挺麻烦。但是我又实在不想裸奔,还是得寻找jdbc方面的sqlite解决方案。
①纯java实现的sqlite
sqlitejdbc-v033-nested.jar
纯java实现的读取sqlite创建的db的jdbc jar包,不用想了,这jar包都不知道你的db怎么加密的,肯定是解不开加密的db文件的,只能打开没有加密的db,因为不同公司有自己的sqlite3_key实现。
②开源sqlite jdbc项目,依赖于sqlite3.dll
主要有两个,SQLite Wrapper by Christian 和 SQLite Xerial Driver
介绍参加
http://blog.sina.com.cn/s/blog_654337ca01016x4n.html
各自的优缺点在文章里面都有说。
我看了一下两者的源码,发现在SQLite Wrapper的源码中,以DriverManager.getConnection(dbUrl, "用户名", "密码")方式获取数据库连接时,他会先判断你有没有实现sqlite3_key函数,有的话就会去调用,从而解密数据库,我从此看到了一线希望。不过两者驱动自带的sqlite_jni.dll都是基于官方的sqlite3.dll通过jni封装而来,不带加密功能,需要自己编译生成带加密功能sqlite_jni.dll。两者对比,SQLite Xerial Driver编译环境要求较高,选择了编译SQLite Wrapper。
首先得先找到sqlite3_key函数的实现,在这里向大家推荐开源项目wxSqlite3。
https://sourceforge.net/projects/wxcode/files/Components/wxSQLite3/
下载后解压,wxSqlite3/sqlite3/secure/src目录就是带加密功能的sqlite3源码
然后按照SQLite Wrapper编译文档编译
http://www.ch-werner.de/javasqlite/
官方示例javasqlite3.mak入口文件是从sqlite源码生成sqlite3.lib再依赖于sqlite3.lib生成sqlite_jni.dll,但是你要真这么做你就死定了,一堆乱七八糟的错,能把你整崩溃,主要原因我猜是SQLite Wrapper更新慢,sqlite3和wxSqlite3更新快,下载最新版的wxSqlite3里面的sqlite3加密源码跟他的脚本不匹配。
我是从javasqlite3-dll.mak入口文件进行编译的,直接把wxSqlite3/sqlite3/secure/ase128下面生成好的带加密功能的sqlite3.lib拿过来,跳过从sqlite源码生成sqlite3.lib的步骤,从而生成sqlite_jni.dll和sqlite.jar,最终实现jdbc操作加密的db库。
至此,所有问题全部解决,java服务端主程序在jetty服务启动完成后调用独立浏览器当作客户端访问http://127.0.0.1:8080/,服务端主程序等待独立浏览器被用户关闭后退出。
打包之后,程序在xp,win7,win8,win10都可运行,当然,xp需装一下.net framework 3.5(VS2012及以后的VS版本无法安装在xp之上了,但是开发出来的dll和exe依然是可以选择兼容xp的,具体方法参见附件)。
最后提醒下,无论是jar包还是ikvm.net生成的dll,都可以被反编译,因此建议核心代码用ikvmc编译成exe并且对exe加密,不要全部用ikvmc编译成dll。
衷心感谢NW.js、Node.js、wxSqlite3、SQLite Wrapper以及SQLite Xerial Driver这些优秀的开源项目,世界有你们更精彩!
相关推荐
在IT行业中,Delphi是一种强大的RAD(快速应用程序开发)工具,尤其适合于构建桌面应用程序。本教程主要聚焦于使用Delphi进行B/S(浏览器/服务器)数据库应用系统的开发,这在当今互联网环境中具有广泛的应用场景。 ...
《B/S构架的设备管理系统》采用了J2EE架构,B/S结构模式,用于煤炭行业的设备管理,即资产动态管理、设备动态管理、单体液压支柱动态管理、材料动态管理、人事及办公动态管理。该系统在峰峰集团的建设及应用,不仅能够...
在IT行业中,Delphi是一款强大的RAD(快速应用程序开发)工具,尤其在Windows平台上的桌面应用程序开发领域具有广泛的应用。本教程“Delphi开发B/S数据库应用系统教程”专注于使用Delphi进行B/S(Browser/Server,...
这些项目涵盖了B/S(浏览器/服务器)和C/S(客户端/服务器)架构,展示了一系列不同的应用场景,旨在帮助开发者理解和学习.NET开发技术。 1. **博客引擎**:这是一个B/S架构的应用程序,用于创建和管理个人或集体的...
三层B/S架构是一种常见的软件设计模式,特别是在开发Web应用程序时广泛采用。...通过深入学习和实践这个三层B/S架构实例,初学者可以逐步掌握Web应用程序开发的核心技术,为后续的复杂项目打下坚实的基础。
C#是微软开发的一种面向对象的编程语言,广泛应用于Windows平台上的应用开发,包括桌面应用、Web应用和游戏开发等。其语法简洁明了,支持多种高级特性如泛型、匿名方法、LINQ等,是构建高效、稳定的后台服务的理想...
它使得开发者能够利用已有的桌面应用开发技能,如Delphi、CBuilder、VC、VB、PB、易语言、VFP以及VC#等,快速构建适应于网络环境的Web应用程序系统。这一转换过程极大地降低了从传统桌面应用向现代Web应用迁移的成本...
这个标题表明,我们将讨论一个基于浏览器/服务器(B/S)架构的人事工资管理系统,该系统是用ASP.NET技术,并且编程语言为C#。ASP.NET是微软开发的一个用于构建Web应用程序的框架,它提供了丰富的功能和工具,可以...
将PB11.5开发的C/S应用转化为B/S,主要涉及到以下几个关键步骤: 1. **分析业务逻辑**:首先需要对原有的C/S程序进行详细分析,理解其业务逻辑、数据交互方式和用户界面。 2. **选择合适的技术栈**:PB11.5本身...
此外,使用IntraWeb开发的应用程序还可以打包成独立的可执行文件运行,就像其他桌面应用程序一样,并且具备相应的调试能力。 本书详细介绍了如何利用Delphi 7.0和IntraWeb 7.0组件集,结合Windows操作系统和SQL ...
基于Delphi设计B/S架构的数据库应用系统是将传统的C/S(Client/Server)模式与Web技术相结合,以实现更高效、更易于维护的应用。B/S架构,即Browser/Server(浏览器/服务器)结构,它利用Web浏览器作为客户端,通过...
总结来说,"适用C/S及B/S架构表格控件"是一个基于ActiveX技术的免费数据网格组件,它适用于VB、VC++以及VS2005开发环境。无论是在传统的桌面应用程序还是Web应用中,这个控件都能帮助开发者快速构建数据密集型的用户...
【计算机应用基础考试系统 B/S】是一个由先锋软件公司开发的专业考试系统,旨在提供一个高效、便捷的在线考试环境。该系统支持B/S(浏览器/服务器)架构,意味着用户可以通过Web浏览器进行访问,无需在本地计算机上...
它具有现代化的编程特征,如垃圾回收机制、类型安全和自动内存管理,特别适合开发跨平台的桌面应用、游戏和Web应用。 【小游戏开发】:小游戏通常具有轻量级、易上手、快速体验的特点。开发者通常会利用C#中的Unity...
B/S 架构软件的劣势包括应用服务器运行数据负荷较重,应用服务器运行数据负荷较重,一旦发生服务器“崩溃”等问题,后果不堪设想。因此,许多单位都备有数据库存储服务器,以防万一。 C/S 和 B/S 的主要区别是硬件...
这表明在实现B/S结构的同时,可能还有一个本地桌面应用的原型,用于测试和调试TTS功能。Windows Forms提供了一个图形用户界面(GUI)框架,开发者可以利用它来创建交互式的界面,包括文本输入框和播放按钮等元素。 ...
Visual Studio 2010是一款强大的开发工具,支持多种编程语言,如C#、VB.NET等,用于构建Windows桌面应用、Web应用和服务。在这个项目中,VS2010被用作开发环境,开发者可以利用其丰富的功能进行界面设计、代码编写、...
Flex是一种基于ActionScript和MXML的开放源代码框架,它允许开发者创建富互联网应用程序(RIA),在网页上提供与桌面应用类似的用户体验。 【描述】"FLEX制作的WEB 工作流设计器 节点缩放移动 连接线等等功能"表明...
【标题】"Web QQ代码,Ajax,C#,B/S" 涉及的主要知识点包括Web应用程序开发、前端交互技术以及后端编程语言的应用。Web QQ是一种基于浏览器的即时通讯工具,它允许用户无需下载客户端即可在网页上实现QQ聊天功能。 ...
1. C#:作为微软开发的面向对象的编程语言,C#具有强大的类库支持和高效的运行性能,特别适合用于开发Windows桌面应用和Web应用。 2. B/S架构:Browser/Server模式,用户通过浏览器访问服务器上的应用程序,减少了...