0

开篇声明:存在必是合理,请不要白费力气和我讨论这个模式的必要性。

最近要在一个项目中把大部分代码都封装到标准C的DLL中,asp.net的部分仅仅做 [Dllimport "xxxxx" ],输入输出接收和传送字符串等这些最简单的动作。


这就需要考虑几个问题:虚拟主机支持、IIS6的兼容性、IIS7-32位模式兼容性、IIS7-64位模式兼容性、32位本地桌面兼容性、64位本地桌面兼容性。

以及:既然是WEB程序,肯定就关系到数据库的使用,既然代码都是用C++(使用标准C++,不是托管C++)编写,那么ADO.NET是肯定不能使用的了,所以选择了SQLITE(原因不在本话题,省略过)。

幸好是选择了SQLITE,才会发现这个问题,要不然如果直接使用ADO加载ACCESS或SQLSERVER的话,将来需要更换数据库时才发现问题就惨了。

一开始,我直接使用SQLITE提供的现成DLL:SQLITE3.DLL,本地桌面测试通过,结构是这样的:

 

可是在IIS中使用的时候,问题出现了:“找不到所需的库文件:SQLITE3.DLL”。怎么会找不到呢? 都放在BIN目录中,我的年龄还不至于到了看眼花的程度,但是本着敬业的态度,还是到医院做了一番检查,医生和仪器都一致同意:“您的视力没有问题。”。确定不是我眼睛有毛病后,想到是不是因为这是试用版WINDOWS做了限制,但是本着敬业的态度,于是我打电话给鲍尔默:“老鲍,试用版WINDOWS会不会在IIS中做了什么限制的手脚?”,老鲍说:“你放心,绝对没有限制,和正式版一模一样。但是我建议你购买正式版WINDOWS,可以享受到7*24小时的电话技术支持服务、免费更新服务……”,老鲍说WINDOWS没有问题,那就肯定没有问题。那么问题出在哪里呢?这时候,有个电话找我:“为什么你昨天给我的那个ISO文件我刻盘了不能安装?”,我问:“你是怎么刻的?详细道来。”,电话那边说:“我把你给我的文件直接拖放到刻录机中,让他自己自动刻录……”,一听就来气了:“镜像文件居然让你这样刻录,亏你还是计算机研究生,你趁早改行回家挑粪去算了。顺便叫你老师和你一起去挑粪,这种弱智问题不要问我了,问你校长去。”。挂了电话。MD,这年头学校都教出什么货色来,还计算机研究生呢,刻个镜像文件都不会……,自己嘟哝到一半,忽然灵光一闪:“镜像文件!”,恍然大悟,想起来了,BIN中的东西实际上都是镜像而已,真正执行的时候,是把这些文件复制到系统专用目录中去执行的,并不是直接执行BIN目录中的文件。我在C#中仅仅是DLLIMPORT  “CPP.DLL”,所以IIS当然就是仅仅只把CPP.DLL复制到系统目录中去了,而C++中的算法是“加载当前目录中的SQLITE3.DLL”,这时候SQLITE3.DLL仍然只是在BIN目录中,当然就找不到了。

知道了原因,马上就闪现了2个解决方案:1 让IIS把SQLITE3.DLL也加载就行了。 2 把SQLITE3.DLL封装到CPP.DLL中。

两个方案应该用哪种呢?直觉告诉我,应该用第二种,因为方案1为系统算法带来了多余的行为,对于C#这一方来说,它仅仅是“中间人”的角色, C#负责做的事情是在逻辑层面(CPP.DLL)和输入输出层面(IIS或者WINFORM)建立一个沟通渠道,它没有理由跨过逻辑层而直接与数据库层(SQLITE.DLL)打交道,即使仅仅是加载,而没有参与真正读取数据库也不行,因为它没有理由去加载一个它“不认识”的家伙。那就这么定了,把SQLITE.DLL集成到CPP.DLL中,让系统只加载一个WIN32的DLL,这样一来也减少了错误发生的机会:要么全部出错,要么就不会出错。

说得轻巧,做起来难,怎么合并?用记事本打开它们两个,然后把内容拼接到一起,再另存为“CPP.DLL”?我可不是研究生,这么高级的操作我不会。还是想别的办法吧,幸好,SQLITE是开源的,并且免费使用在包括商用在内的任何领域。到WWW.SQLITE.ORG去下载了源码,就三个文件,一个sqlite3.c文件,一个sqlite3.h文件,还有一个sqlite3_ext.h(这个文件干什么用的也没有去研究,总之就是多余的,可以删掉了)文件。

把两个文件加载到c++的源码中,去掉原先加载的sqlite3.lib, 编译,得到新的cpp.dll,果然容量增大了380多K,这次肯定行了。把IIS中BIN文件夹里面的东西删掉,再扔这个新的cpp.dll进去,测试运行,成功!此时也不再需要依赖sqlit3.dll了。此时的架构是这样的:

 

 

 

通常,从一个想法的产生到能够真正实现出来,不知道要经过多少次调试、查错、论证……,哪怕它仅仅是一个“Hellow World!”也不能逃过这些劫难,而这次,一切却如此顺利,所以可以说,这种幸运的机会是少之又少的,完全取决于你上辈子积蓄的阴德。

黑暗的旧社会终于被解放了。新中国万岁!

可是,新中国成立10多年后,依然有了一次整体上10多年的社会事件,而那次事件的某些后遗情况也直接影响到我们2008年的今天。

此时,有一些Buger们,正潜藏在DLL的某个阴暗角落里,在等待着反清复明的机会:“下个月乾隆要来纽约,我们先埋伏着,只要他一上到世贸大厦的一半,十四弟的那几架波音787自然会启动,一切等待和大人的指示,别的事情一概不管,以免走漏风声。特别要当心那个叫纪晓岚的。”。

一切都运行得井井有条。dll在windows2003中的测试的过程居然如此风平浪静,似乎要预示着一场毁天灭地的灾难即将淹没庞贝城。

一天的工作结束,不结束也不行了,眼皮都打架了。记录完版本开发进程,备份了代码到网上去,重启电脑,切换到VISTA去,准备洗澡。

说到这里,顺便给大家一个提示:开发工作用的电脑,最好是有两块硬盘,有条件的组RAID1,不想组的也要做个定时同步计划,没有两块硬盘的就应该把项目备份到网上私人空间里或者U盘里,要不然,会在将来某个时刻死得很惨。

我装有两个系统,一个是32位2003,一个是64位VISTA,常用的是VISTA,2003只在必要的时候才使用(例如这次开发这个东西,为了先保证32位平台,就先在2003中测试开发)。因为切换一次系统就要合并一次QQ记录的关系,所以干脆就不在2003里面开QQ了。 洗澡归来,打开VISTA,上QQ看有没有哪个妞来请我出去做秘密郊游,果然有,约好时间,准备睡觉,为明天的郊游做好休息,睡前为了再欣赏一遍今天的劳动成果,打开chrome,在地址栏输入127.0.0.1/using_cpp.aspx,回车。

平时我看电视都是喜欢看新闻频道,一来关心国家大事,二来经常可以看到世界各地的美人。此时传来东央电视台的播报 :"据一位不愿意透露姓名的FBI官员称,当年911事件另有说法:5架波音787客机的64位导航系统未将对象引用设置到对象的实例,导致同时启动了自动寻找地面重点对象的程序"。

 

我的眼前一黑, 完了,明天还怎么和紫嫣妹妹去郊游,想到可能是因为人困了,眼睛出毛病,本着敬业态度,我连夜跑去医院做了眼睛检查,医生和仪器再次确认:“您的视力没有问题”。难道是因为VISTA是30天试用版的关系,我再次接通鲍尔默的电话:“老鲍……”,还没等我问,他就先回答了:“上次你挂电话那么快,我还没有来得及说,不光是WINDOWS2003,就连VISTA的试用版也是和正式版完全一致,不会存在任何使用限制,此外,我强烈建议你购买我们的VISTA系统,你可以享受到7*24小时的……”,算了,不睡了。

我打开IIS7控制台,32位模式、64位模式,经典模式、集成模式、 总共 4*4=16种设置方案,全都无效。重启换回WINDOWS2003,32位模式IIS下运行工作正常,再换回VISTA的64位IIS,工作失败,再试试64位桌面程序加载情况:失败。

可能是编译器问题吧,以前就听说过同是一块cpu,32位模式下编译的东西是不能直接在64位下面运行的这种类似情况,打开VISTA下的VC2008,把cpp.dll重新编译为64位,然后把C#代码先复制到asp.net文件中,让它自动识别工作模式,结果是失败。再在64位环境下编译32位版本cpp.dll,再设置IIS7工作于32位模式,还是失败。死定了!

5小时的详细调试过程省略100万字……

把问题最小化,省略所有的多余代码,事件的起因找到了:

extern "C" _declspec(dllexport) char* ReadData()
{
    std::string str
= "abcd";//实际工作中,这个abcd是由数据库产生的
    
char* temp = strdup(str.c_str());
    
return temp;
    
//delete temp;
}


配对的C#代码是:

[DllImport(@"E:\project\web\test\bin\cpp.dll")]
public static extern string ReadData();

void Page_Load()
{
    Response.Write(ReadData());

}

 

这样的配对在32位环境下工作没有问题,但是在64位下的话,肯定失败,原因出在strdup()上面,可能有的朋友会问:

先 char* temp = new char[str.length()]; 行不行?  回答是32位下面行。64位不行(包括64位下面运行32位模式也不行)。那么传递Stringbuilder作为参数给C++行不行?同样,32位行,64位不行(包括64位下面运行32位模式也不行)。


那么,如果直接返回const char* 不就行了吗? 答案类似,32位下面行,64位下面虽然不出错,但是读取不到字符串。

再次经历3个小时的调试,再次省略100万字……


extern "C" _declspec(dllexport) int Test(char* build)
{
    std::
string str = "abcd";

    memcpy(
build, str.c_str(), str.length()+1);
    
return 0;
}

然后,配对的C#代码是:

[DllImport(@"E:\project\web\test\bin\cpp.dll")]
public static extern int Test(StringBuilder build);

void Page_Load()
{
StringBuilder build 
= new StringBuilder();
    Test(build);

    Response.Write(build.ToString());
}


这次的关键点在于:使用memcpy(需要保存值的指针, 值, 值的长度+1 );  进行内存复制。

搞来搞去,使用这个函数之后,一切都正确了,任何别的32位下面做的那些传统办法尝试都是徒劳的,不是“尝试读取受保护的内存”就是收到空字符串,有时候甚至是刚出炉的dll只有在第一次执行才成功,接下来就永远出错了,然后再回锅编译,再然后第一次执行成功,第2至n次执行失败,如此反复,明显就是64位系统的内存管理机制已经变完了。

把项目实际代码添加回去,终于成功了。各种模式的IIS下面运行正常,32位和64位系统的桌面程序加载也正常、 使用edong网的虚拟主机测试工作正常(他们那里是32位IIS,至今还没发现哪个虚拟主机是64位,不过以后总会有的)。

经过测试,绝对不是.net编译器的x86、x64、anycpu这些编译选项的原因,详细的就不表了。一切都是出在vc上面。

 

最后,还有一个非常重要的注意点:vc2008直接默认配置的话,无论你编译32位还是64位,弄出来的组件都是不能正确在IIS64中加载的,VC2005也一样,为此,我特意安装了VC6来编译,然后再在VC2008中升级项目,才能正确编译通过,这个步骤肯定是设置存在问题,但是目前还没有时间来查找是哪一步的设置出错。想偷懒的朋友用这个土办法就行了。

 

工作终于完成的同时,天亮了,今天是阴天,也正好,苍天蒙蒙才能雾里看花,为秘密郊游添加一份神秘色彩,既不阴暗也不强烈。我用一些色彩颜料修饰一下黑眼圈,等着紫嫣妹妹到来,一路上,听我我描述眼科大夫的人类视力进化论、智能视觉检测仪的工作原理、与微软CEO的商务对话、乾隆下江南的奇遇记、911事件的来龙去脉、调试VISTA系统的源代码工作原理等等这些惊心动魄的午夜经历,紫嫣妹妹露出了无比敬佩的赞扬:“云山苍苍,江水泱泱,先生之风,山高水长”,我羞涩地回道:“此夜曲中闻折柳,何人不起故国情”。

关闭 返回顶部
联系我们
Copyright © 2011. 聚财吧. All rights reserved.