情景1:我们有这么一个程序
1.程序中有一个编辑框,一个静态文本框,一个按钮。
2.点击程序按钮,将会调用函数int FunB()
3.函数int FunB()将调用方法int FunA();
4.函数int FunA()将其编辑框中的数值返回;
5.最终在静态文本框中的内容与编辑框的内容相同
情景2:现在我们将上面的程序命名为A,
然后新增一个程序B,将函数int FunB()移动到程序B中,
这样我们需要用远程过程调用:
1.程序A中有一个编辑框,一个静态文本框,一个按钮。
2.点击程序A按钮,通过远程过程调用来调用程序B的一个函数int B::FunB()
3.程序B的函数int B::FunB()将反过来通过远程调用来调用程序A的一个方法int A::FunA();
4.程序A中的方法int A::FunA()将其编辑框中的数值返回;
5.最终在静态文本框中的内容与编辑框的内容相同
对于程序A来说,FunB移动到程序B中后,它的整体调用情况变化不大,
除了原来直接调用FunB,现在变成A到B的远程过程调用,
而原来FunB对FunA的直接调用,现在也变成B到A的远程调用
情景3:有一个程序,
1.程序中有一个按钮,一个文本框,一个静态框
2.按钮点击后将会调用int Fun1(); 返回值显示在静态框中
3.int Fun1() 这个函数将返回文本框中的数值
情景4:将情景3中的程序做一下改动,这个程序都是运行两个实例
这样其中一个实例点按钮时,将调用另外一个实例的int Fun1();
1.程序中有一个按钮,一个文本框,一个静态框
2.点击一个实例的按钮后将会通过远程过程调用来调用另外一个实例int Fun1();
返回值(后者编辑框中的值)显示在前者的静态框中
3.int Fun1() 这个函数将返回文本框中的数值
对于情景2和情景4
一般我们可以通过Windows的远程过程调用或者直接采用COM/DCOM来实现这个功能。
但是,我们能自己模仿来模拟实现这个功能吗?
现在
按照一般化的思路,我们假设远程通讯的两边,一边为程序A,另一边为程序B,
程序A将调用程序B的函数int FunB(); 程序B将调用程序A的某个函数int FunA();
在某一次调用中,可能A为客户,B为服务,但是另外一次调用中可能B为客户,A为服务,
A和B互为客户端服务端,他们的地位是对称的。
为了实现这个远程调用功能,我们需要为A提供一个dll,称为ProxyStubA.dll;为B提供一个dll,称为ProxyStubB.dll
由于AB之间互为服务和客户,因此这两个dll既当代理又当存根,互为存根代理。
其中ProxyStubA.dll,
提供了int FunB()导出函数,这个函数是代理函数,并且
当A调用时FunB时将函数标识打包发送到ProxyStubB.dll,并等待ProxyStubB.dll将返回值送回来,然后返回给A
同时ProxyStubA.dll接受从ProxyStubB.dll发送来的调用请求,然后调用FunA,
FunA可以预先由A注册给ProxySubA.dll
同样的,ProxyStubB.dll,
提供了int FunA()导出函数(可以不导出),这个函数是代理函数,并且
当int B::FunB()调用FunA时,FunA将函数标识和参数打包发送到ProxyStubA.dll,并等待ProxyStubA.dll将返回值送回来,然后返回给int B::FunB()
同时ProxyStubB.dll接受从ProxyStubA.dll发送来的调用请求,然后调用FunB,
FunB可以预先由B注册给ProxyStubB.dll
由于ProxyStubA.dll和ProxyStubB.dll的具有某种对称性,在远程过程调用的过程中,他们扮演代理和存根的作用。
不妨将他们都称为ProxyStub.dl。现在我们考虑该怎么实现ProxyStub.dll?
从上面的描述,我们知道,一个ProxyStub.dll有以下的几点职责:
1.导出远程函数的代理
2.打包函数标识和参数,并发送到对方
3.接收对方的对某个函数的返回值
4.接收对方对某个函数的调用,并解析
5.调用预先注册的回调函数
6.将回调函数的返回值打包,发回至对方
我们目前暂时不关心如何打包和解析函数标识、参数和返回值,也不关心如何将数据包发送到对方。对于配对的两个dll的来说,这个难度不大。
我们来关心如何实现代理函数,如何接收数据以及如何调用回调。
考虑一个通用的存根和代理实现
我的做法:
实现代理函数
int Fun()
{//此函数位于ProxyStub.dll内部
//1.将函数标识和参数打包
MakePackage();
//2.通过以太网或者管道将数据包发送到对方
SendPackage();
//3.在hReturned上阻塞等待,
//读取线程(见下面的ReadThreadProc)会接收所有数据,如果等待到了,
//会释放hReturned,说明返回值已经收到了
WaitFor(hReturned);
//4.到这里说明返回值已经收到了,从共享列表中取得返回值
ret = GetRet();
//将返回值返回给调用者
return ret;
}
用一个专门线程来等待数据的到来,然后解析数据,根据数据类型做不同的处理:
DWORD WINAPI ReadThreadProc(LPVOID lpParameter)
{//此线程位于ProxyStub.dll内部,由ProxyStub.dll自己创建,对调用者透明
while(1)
{
//1.从以太网或者管道中获得一个数据包,这是阻塞调用,如果没有数据包,将阻塞
GetPackage();
//2.分析数据包
switch(ParsePackage())
{
case FUN_CALL: //如果是一个函数调用
//3.根据函数标识和参数,调用回调,目前是直接在ReadThread中调用,
//不知道有没有办法让这个回调在主线程或者其他线程中调用
ret = CALLBACK_Fun(param);
//4.然后把返回值发回去
SendRetValue(ret);
break;
case FUN_RET: //如果是一个返回值
//4.将返回值放到一个共享列表中
InsertRet(retValue);
//5.通知函数收到结果,代理函数(上面的Fun)正在hReturned上等待
Notify(hReturned);
break;
};
}
}
我这样做表面上可以,但是实际使用过程中有很多的问题
1.回调发生在ReadThread,而不是主线程中,这会额外要求A/B的回调函数体进行多线程保护,同时,如果回调函数中获取界面的东西,必须做特殊的处理,与直接调用的情景1和3不同。
(当然,我们可以通过PostMessage的方式将调用发送到主线程,让主线程来调用。
但是这样会给主线程增加额外的负担,需要处理不同的消息,系统级的远程过程调用就不需要主线程来处理什么消息
很有可能,主线程根本就没有消息循环,比如控制台程序,所以有没有比PostMessage更好的解决方案)
2.在情景2中,由于远程调用是嵌套进行的,实际会产生死锁。
3.在情景4的实际使用中,很快就会发生死锁的情况,其中一种死锁情况如下:
3.1.实例1和实例2同时按下按钮
3.2实例1向实例2发送调用请求并等待实例2给出返回值,同时实例2也向实例1发送调用请求并等待实例1给出返回值
3.3死锁
采用系统的远程过程调用,都没有上述的问题,不知道系统是怎么实现远程过程调用的。
不知道大家有没有清楚系统远程过程调用的实现方法的,或者我这里的方法改如何才能改进?