return0;
}
●设定VCClient为启动项目
运行成功。
3.使用BC创建一个客户端BCClient
●打开bc创建一个VCLFormsApplication应用程序。
●在VC的解决方案文件夹下新建文件夹筛选器BCClient。
●在BCClient内对BC项目全部保存起来,再导入到VCClient里面
●在BC窗口界面点一下button,再点一下视图,就能添加一个按钮了。
这里产生点击click处理事件。
●在Unit1.cpp中,#include"Unit1.h"后面加入#include"FastString.h"
●运行,发现[bcc32Error]Unit1.cpp(7):
E2209Unabletoopenincludefile'FastString.h'
●设定包含路径:
Project-》Options–》C++Compiler–》Directories&conditional
includefilesearchpath加上../FastStringDll
VS2012是在属性-》配置属性-》VC++目录-》包含目录
●编译通过
●加入代码:
:
FastString*pFS=newFastString("fdfd");
●对于以上代码提示无法链接
[ilink32Error]Error:
Unresolvedexternal'FastString:
:
FastString(char*)'referencedfromD:
\TEST\FASTSTRINGDLL\BCCLIENT\WIN32\DEBUG\UNIT1.OBJ
●View–》ProjectManager添加FastString.lib文件
链接失败提示:
无效的OMF,PossibleCOFF.
[ilink32Error]Error:
'D:
\TEST\FASTSTRINGDLL\DEBUG\FASTSTRINGDLL.LIB'containsinvalidOMFrecord,type0x21(possiblyCOFF)
这里是因为跨平台,所以vc下引用dll和bc下引用效果不一样。
所以如何解决这个问题呢?
●使用coff2omf工具,cmd中输入“coff2omf.exeFastStringDll.libbc.lib”进行转换。
需要先退出C盘,进入D盘lib文件所在的地方
到上一级目录,请使用cd..
进入D盘,请使用d:
进入想要的目录,请使用cd
这里还有其他两种方法
一、使用implib命令
1、用C++Builder的implib工具生成DLL对应的lib文件。
生成lib文件之后,C++Builder便可以使用这个lib文件了。
2、在.h中用extern"C"修饰VC导出的DLL函数。
如:
extern"C"__declspec(dllexport)intaFunc(inta),就可以调用了。
二、coff2omf.exe
工具程序coff2omf.exe将.lib转换为BCB可用的,然后添加进工程,直接使用VC的头文件就可以了。
三、动态调用
C/C++code
//Main.h
HINSTANCEhDllInst;//声明Dll句柄
int(__stdcall*sdfm)(unsignedchar,unsignedshort,long);//声明变量
C/C++code
//Main.cpp
void__fastcallTForm:
:
FormCreate(TObject*Sender)
{//框架启动时初始化Dll模块,这里以DllName.dll为例
if(NULL==hDllInst)
hDllInst=LoadLibrary("DllName.dll");
if(hDllInst)
sdfm=(int(__stdcall*)(unsignedchar,unsignedshort,long))GetProcAddress(hDllInst,"sdfm");
}
生成的新lib文件如图:
●把bc.lib包含到项目中
●提示构造函数无法链接。
4.在VC中,FastString输出函数CreateObject:
FastString.h文件:
extern"C"__declspec(dllexport)FastString*CreateObject(char*psz);
FastString.cpp文件:
FastString*CreateObject(char*psz)
{
returnnewFastString(psz);
}
●重新编译,重新更新lib文件。
●BCClient:
代码改为:
FastString*pFS=CreateObject("fdfdd");
●编译通过!
●但是如果加上:
pFS->Length();//提示链接出错。
5.把Length和Find改为虚函数。
以上链接通过。
6.BC的运行过程。
●IDE下运行无结果。
●直接运行提示出错:
找不到DLL
●Project–》options-》C++(SharedOptions)->将Finaloutput设定为Dll所在位置:
../Debug
●代码:
FastString*pFS=CreateObject("ffdfd");
intlen=pFS->Length();
ShowMessage(AnsiString(len));
7.Length或者Find的执行情况:
执行结果不正确,将其改为_stdcall执行结果正常
8.此时可以取消对类的输出。
即服务器端删除掉__declspec(dllexport)
Bc客户仍可正常执行。
9.对数据的敏感性:
客户编译器与服务器编译器同时依赖于同一个c++类定义,
在类的定义部分:
加上:
Public:
inta;
intb;
构造函数中:
a=2;
b=5;
以此文件生成dll,
客户端输出:
ShowMessage(AnsiString(pFS->a));
ShowMessage(AnsiString(pFS->b));
以此文件生成客户。
得到版本1.
改变a,b的定义顺序。
生成dll和客户。
得到版本2.
打乱客户和dll的搭配关系。
A和b的值都将颠倒。
实际上是一个显示的交叉转换过程。
10.于函数也有如上特性对虚拟函数的敏感性:
定义两个虚函数:
virtualintfa();
virtualintfb();
分别实现之。
编译服务器端和客户端。
得到版本1.
改变定义顺序:
virtualintfb();
virtualintfa();
编译服务器端和客户端。
得到版本2.
打乱版本的搭配顺序,调用将出现错乱。
11.对普通函数的不敏感性:
这里客户要用同种编译器,即vc
对于以上的虚函数,改为非虚的。
打乱次序后,调用不会出错。
说明是通过名字解析的方式进行的。
12.所以要对接口进行抽象。
降低耦合性。
添加接口类:
IFastString
头文件:
classIFastString
{
public:
virtualint_stdcallLength(void)=0;
virtualint_stdcallFind(char*psz)=0;
~IFastString();
IFastString();
};
实现文件:
#include"stdafx.h"
#include"IFastString.h"
IFastString:
:
IFastString(void)
{
}
IFastString:
:
~IFastString(void)
{
}
实现类FastString.h中:
#include"IFastString.h"
classFastString:
publicIFastString
……
CreateObject的返回值改为IFastString
其声明:
extern"C"__declspec(dllexport)IFastString*CreateObject(char*psz);放到接口头文件中。
客户端代码改为:
#include"IFastString.h"
IFastString*pFS=CreateObject("fddfd");
13.因为接口中已经没有数据,所以自然不存在数据的敏感性。
14.在vc客户端,使用delete进行内存释放:
验证普通析构函数会出现内存泄漏。
验证方法:
⏹接口类重新向外输出__declspec(dllexport)
⏹子类分配大量内存。
IFastString*pFS=CreateObject("fdfd");
intres=pFS->Length();
deletepFS;
boolb=_CrtDumpMemoryLeaks();//监控函数
cout<
return0;
⏹b为1,表示有泄漏。
⏹或者使用windows任务管理器等监控机构。
15.对以上使用虚析构函数,代码为:
classIFastString
{
public:
IFastString(void);
virtual~IFastString(void);
virtualint__stdcallLength(void)=0;//返回该字符串的长度
virtualint__stdcallFind(char*psz)=0;//查找指定的子串
};
注意此时既然使用虚析构,则不用输出接口类了。
验证vc客户端内存泄漏消失。
16.验证在bc客户端,仍然对析构函数链接失败。
IFastString*pFS=CreateObject("fddfd");
intlen=pFS->Length();
deletepFS;
析构函数位置不同而产生链接失败。
17.接口改为如下形式:
●取消输出
●增加Delete虚函数。
●采用默认的构造和析构函数。
classIFastString
{
public:
virtualvoid_stdcallDelete()=0;
virtualint_stdcallLength(void)=0;
virtualint_stdcallFind(char*psz)=0;
};
delete的实现:
voidFastString:
:
Delete()
{
deletethis;
}
18.BC客户端改为:
IFastString*pIFS=CreateObject("fdfd");
intres=pIFS->Length();
ShowMessage(AnsiString(res));
pIFS->Delete();
通过。
19.对接口进行改变
classIFastString
{
public:
virtualvoid_stdcallDelete()=0;
virtualint_stdcallLength(void)=0;
virtualint_stdcallFind(char*psz)=0;
virtualint_stdcallFindN(char*psz,intn)=0;
};
FastString实现新函数。
客户端对其进行调用。
生成版本2.
打乱版本1和2搭配次序。
●老客户用新对象,正常。
●新客户用老对象,失败。
20.增加新的接口:
classIPO
{
public:
virtualvoid_stdcallDelete()=0;
virtualvoid_stdcallSave()=0;
};
多重继承:
classFastString:
publicIFastString,publicIPO
客户端:
IPO*pIPO=dynamic_cast(pIFS);
验证
●VC在接口之间可以正常运行。
IFastString*pIFS=CreateObject("fdfd");
intres=pIFS->Length();
IPO*pIPO=dynamic_cast(pIFS);
pIPO->Save();
deletepIFS;
●BC客户端运行异常
21.中性的类型转换
接口改为如下形式:
增加dynamic函数。
classIEO
{
public:
virtualvoid_stdcallDynamic(char*name,void**ppI)=0;
virtualvoid_stdcallDelete()=0;
};
classIFastString:
publicIEO
{
public:
virtualint_stdcallLength(void)=0;
virtualint_stdcallFind(char*psz)=0;
};
classIPO:
publicIEO
{
public:
virtualvoid_stdcallSave()=0;
};
classFastString:
publicIFastString,publicIPO
实现方式:
voidFastString:
:
Dynamic(char*name,void**ppI)
{
if(strcmp(name,"IFastString")==0)
*ppI=(IFastString*)this;
elseif(strcmp(name,"IPO")==0)
*ppI=(IPO*)this;
elseif(strcmp(name,"IEO")==0)
*ppI=(IPO*)this;
}
调用方式:
IPO*pIPO;IEO*pIEO;
pIFS->Dynamic("IPO",(void**)&pIPO);
pIPO->Dynamic("IEO",(void**)&pIEO);
22.验证多接口之间的紧密耦合性。
略。
23.引用计数。
改写接口,为以上对象添加引用计数功能
实验心得
上机课有些手忙脚乱,安装软件时也遇到各种未知问题……但一切都井然有序地进行着。
做实验最大的难题,是需要先弄懂一个关键,WHY——为什么我们要做这个实验,即这个实验的目的是什么。
弄通目标之后,接下来的实验就进行得轻松愉快了。
最重要的一句话“因为跨平台,所以VC和BC下引用DLL的效果可能不一样。
”
XX,也让我们的实验显得容易很多。
善于使用搜索引擎也是益处多多的。
比如搜索一下“C++Builder的开发指南”,就会发现很多跨平台的例子。
这个会大大帮助我们理解实验,使实验顺利地进行下去。
实验报告毕竟是份文档,如果能把实验者自己的想法、截图的方式和报告的书写,沉淀为更加规范的格式,那想必是份完美的文档。
分布式不是一门很好理解的课程,但老师能从上课、完美翻译的参考书、实验、作业等一系列方式让我们多方位地理解这门课,我们是受益匪浅的。
最后,感谢老师辛苦的授课!