从异步DLL转化到同步 ASP COM (今天看到的一篇好文章)

蝈蝈俊 2000-07-11 03:40:00
加精

  您希望自己是WEB或 eCommerce 的编程人员吗?您有一些需要转化到
COM部件以便在客户端使用的的具有某些用途的代码吗? Ashraf ElSwify
向您展示了如何只需花比剪切和粘贴稍稍多一点的精力就可以把一个异步
的事件驱动DLL或EXE转化成一个更加独立的ASP COM对象。

  我们都知道 COM编程为软件开发人员和项目管理人员带来了好处。这
些好处包括灵活性、独立性、放置位置清楚、可版本化以及可靠性。在您
的新代码中使用 COM为您带来了这些好处。另一条获得这些好处的方法就
是重新编写和利用现存的代码,而您已经在实施这些代码上花费了时间和
金钱。这就是我要在这篇文章中要说明的东西。

移植到COM

  在转化您现有的一行代码前,您首先需要准备一个已计划好的策略。
以下因素必须考虑:

· 您现有的代码是如何设计的,是用C ++或其它任何面向对象程序设计
语言还是使用散乱的 C函数再加上随处可见的代用。对您的代码的功能性
和代码的组织性有牢靠的认识是非常关键的。

· 您能在多大的程度上分离它的函数

· 定义那些被函数引用的全局变量和普通变量并且如何去在新的对象中
  映射它们。除非万不得已,不要在新的对象模块中显露出全局变量。
  新的对象越和全局模块数据分离,您就越增强了灵活性并且避免了今
  后的并行问题。

· 那些函数您需要显现出来作为在 COM对象中的方法以及那些将被用来
  作为属性。您需要添加更多的帮助方法和在新对象中的属性吗?

  当然,没有什么事情是一成不变的。您能按照您喜欢的方法任意去设
计对象,但是事先考虑上述问题将为您铺平道路。

  在这篇文章中,我将以 DLL导出的函数作为例子。在通常条件下,如
果一组您需要转化的函数在功能上是同步的,那么转化过程不会是太大的
问题。在某些情况下,它可能是直接映射。使用“同步”的意思是说这些
函数调用在功能完成之前不会返回。当功能在函数返回后完成时,引用一
个回调或消息处理程序,这个函数是异步的。

  如果您有一个异步函数,那么使用 DLL的客户端应用程序就会调用这
些函数来开始一个特定的任务。这些函数在完成工作后立即返回;然后,
客户会被告知任务完成。客户将会激发一个基于哪个通知的消息处理程序
函数。

这个通知是在转化过程中将遇见的最大困难之一。

COM连接点

在经典的WINDOWS编程中,COM的确提供了消息处理程序和回调通知的一个
等效物:连接点。COM详细说明了由COM对象通过连接点激发的事件必须被
另一个对象方法处理。那意味着需要处理由您的对象激发的事件的客户必
须提供实施特定界面的 COM对象,这些界面已为对象所知。当然,这些界
面应当是已知的标准界面,dispinterfaces、或者任何您的对象能理解的
界面。

  您的对象被称为源对象,处理这些事件的客户对象被称作汇对象。每
一个汇对象必须向源对象宣示,表明:“我乐于接收特定事件通知”。这
个协议被称为向源对象通知汇界面和向连接点更精确地通知汇界面。可以
说,尽管源界面需要了解哪个界面(以及在界面中的方法)会用来激发事
件,但是它并不关心函数实施的方法。因此,客户可以提供一个要求函数
的空执行,这个空执行也许和现在的操作或任务毫不相干。

  正如您所见到的,这限制了您使用对象。如果处理这些对象激发的事
件对完成对象功能非常重要的话,并且客户(汇对象)决定提供一个空执
行,或者,更糟的是,客户根本就不把它的汇界面向源对象发布――功能
无法完成。您的对象将会等待一个永远不可能被执行的活动,或者您的对
象处在一个不稳定的状态。

  同时,任何想要使用您的对象来执行正确的界面以及在界面回调中进
行正确工作的人必须对这些对象有相当的了解。

ASP 和 Web

  在这个WEB驱动的年代,许多复杂的应用程序都上网了。ASP的使用通
过结合WEB部件和脚本语言在服务器端WEB应用程序中带来了奇迹般的进步。
当您使用IIS,您能够在通用的服务器上创建独一无二的WEB服务程序。您
为每一个应用程序创建的默认行为是在IIS进程本身(它是inetinfo.exe)
的环境中运行,但是您能在一个分离的进程中运行特定的应用程序。系统
为您提供了非常强大的进程,它是mts。exe,Microsoft Transaction Se
rver的进程。应用程序将会在一个新的mts.exe进程中运行。在把WEB应用
程序和剩余的运行中WEB应用程序相互分离上提供了很大的便利。如果WEB
应用程序坏了,其它应用程序仍将安全运行。

ASP变量和对象有三个作用域:

·应用程序:您的WEB应用程序在WEB用户请求它的应用程序目录下任何网
页的第一时间启动。直到WEB服务程序停止时才停止运行。

·对话:当一个特定WEB用户第一次访问网站时,对话就启动了。几个对
话能立即进行。

·网页: 网页是仅仅在一个网页被载入的时候才存在的临时变量。
ASP能够在每一个作用域的开始和结束激发特定的标准事件。您能够用全局

  函数在global.asa文件中处理这些事件。

  ASP的COM对象与普通 COM对象毫无分别,但是它们具有可以访问内置
对象的优点,例如:应用程序、对话、服务器、应答和请求。这些为您的
部件提供了一个与浏览器和简便状态管理进行交互的宝贵机会。

  现在我们开始渐渐进入主题了。 ASP根据执行逻辑从上到下地开始执
行每一个网页。没有提供处理客户事件的无用户部件支持。因此,如果您
想转化您的 DLL导出函数或者其它任何依赖于回调和一个事件驱动模块的
函数组,您就不能够把这些模块转化成连接点COM事件模型。当然您可以,
但是您的函数却再也无法使用了。inetinfo.exe 和mts.exe都不了解汇界
面需要提供的任何东西,并且都不会把自己向源对象公布。忘掉ASPCOM的
对象连击点。

  同步才是解决的办法

  解决的办法在于转化您的函数,这样它们才会只有在整个任务完成时
才返回。那时,它告知调用任务已经成功完成或者任务失败。这就是为什
么了解您的代码功能性以及它是如何组织是如此重要的原因。

  把您的异步调用转化成一个同步调用的第一步是把所有的函数编组并
且把回调关联到函数上。需要客户为您的代码提供以后需要的所有信息,
来消除那些回调以便决断。这可以通过方法参数或预先设定的属性(在客
户调用您的对象的情况下,在设置参数之前提供默认值)。把回调逻辑从
客户转移到对象,或者让客户在后来操作那些任务,当对象工作完成后。

  第二步是分离内部消息,以便当您的调用等待内部事件完成时和平地
发生。

  这种等待技术的作用域可以是对象域,作为一个内部的未显现的对象
的方法。您通过一个为了某种目的而产生的普通 WINDOWS用户消息来将您
的应用程序同步化。您的对象将会在一个线程上执行任务。这个方法使您
能够避免使用多线程应用程序。如果您的应用程序是单线程的,那么试图
使用线程同步WaitForSingle Object()将阻止现在的线程,调用是在它
之上建立的。您是不想这样的。一个进行等待而不必阻止任何人的工作
(包括您自己的线程)更好的办法就是去等待一个普通同步用户窗口消息。
如下定义

一个消息号码:

#define WM_RESULT WM_USER+AnyReasonableValue
当WM_RESULT 同步消息被调用时,句柄会得到WPARAM 和LPARAM变量值。
这使您知道是否信息已被成功地传送了,包括一些附带的相关信息。
编写您自己的消息循环来等待这个消息(或者,当然可以是WM_QUIT):
BOOL CMyObj::WaitForResult(WPARAM *pwPar,
              LPARAM *plPar)

 BOOL bRet = FALSE;
 MSG Msg;
 while(TRUE)
 {
  while(::PeekMessage(&Msg,NULL, 0, 0,PM_REMOVE))
  {
   if((Msg。message==WM_RESULT)||
    (Msg。message==WM_QUIT))
   {
    *pwPar = Msg。wParam;
    *plPar = Msg。lParam;
    bRet = TRUE;
    if(Msg。message == WM_QUIT)
     PostQuitMessage(0);
    return bRet;
   }
   TranslateMessage(&Msg);
   ::DispatchMessage(&Msg);
  }
  //服从于其它的线程和进程的控制
  WaitMessage();
 }
 return bRet;


  只要您需要等待完成一个或多个异步活动时,就可以调用
WaitForResult()。一旦整个异步任务完成了,发送一个WM_RESULT消息,
这个消息以WPARAM 和LPARAM变量值形式包含了关于任务如何完成的消息。
当消息从WaitFor Result()在内部被捕捉时,函数将返回并且停止阻塞
执行线路。您可以用两种方法之一发送WM_RESULT到现在的线程:

· 如果您已经在现在的线程当中产生了一个窗口,您可以利用它的句柄。
注意不要在阻塞函数之外去处理WM_RESULT消息。使用消息要点是到消息
发送后再去阻止。如果您使用MFC和一个CWnd派生的对象去在消息发送后阻
止,那么不要为WM_RESULT创建消息映射入口和句柄。这将把消息从线程
消息队列中删除,并且WaitForResult()将永远也无法返回除非线程收到
WM_QUIT。
PostMessage(hWnd, Msg,wPar, lPar);

· 您可以把消息直接发送到线程上,同时允许任何线程中的实体进入,以
及从线程消息队列中取回它、处理它。这和PostMessage()是等效的,把
NULL作为窗口句柄放行。
PostThreadMessage(dwThreadId, Msg,wPar, lPar);
DwThreadId是您想发送消息到的线程的ID。由于我们需要使用现行的线程,
您可以使用GetCurrentThreadId() API

  为什么PostMessage()?当您调用PostMessage(),函数不阻塞。
它立即返回,相对于SendMessage(),SendMessage()在消息完全处理
后阻塞。这给了您操作进一步清除的时间,因为一旦WM_RESULT被处理,
您的原始函数将会返回。我建议您在从激发函数返回之前把WM_RESULT作
为您的最后一个行为发送。

您可以如下编写代码:
WPARAM wPar = 0; //成功
LPARAM lPar = 2; //例如,任务的第二步
PostMessage( NULL, WM_RESULT, wPar, lPar);
例如,这有一系列的伪代码显示了如何在Connect()函数中操作:
CMyObj::Connect(parameters)

 //在这里进行所有的素材操作
 …
 //在这里调用任何一个同步调用
 …
 //在这里调用任何一个同步调用
 WPARAM wPar = 0;
 LPARAM lPar = 0;
 WaitForResult(&wPar, &lPar);  //Waiting 
  //操作非常简略的素材
 …
 return;


  利用这些可靠技术包装您的对象、分离它的同步部分、把它们编组成
到逻辑函数的任务――您将把您原先的代码的DLL或EXE部分转化成一个有
效和可升级的ASP COM对象,并作为一个进程内服务器DLL执行。

可伸缩性和COM 单元模型

  多线程计算非常复杂而且并不总是增强功能,尤其是在一个单处理器
计算机中。在正确的环境下,一个应用程序能够从中得到许多的好处。多
线程以及它和 COM单元模型的关系需要比我们所能提供的大得多的空间,
并且甚至一个简单的处理都可能使我们脱离目标;但是,我仍然试图给出
一个概括以帮助驱除笼罩在主题上的神秘感并且澄清它们是如何和我们的
目标有关联。

  使用多线程主要考虑的问题是一种多个线程同时试图修改资源的情况。
这个资源可以是一个变量、一个文件、一个数据库或者其它任何东西。如
果两个线程访问一个文件对象,第一个线程关闭了文件然后过了一会儿另
一个线程想要把数据写进文件,那么应该怎么办呢?同步技术解决了这类
问题。当全局变量可以在线程间共享时,这种问题也是很明显的。正如我
在文章全面提到的,当转化到COM时,应该避免把全局DLL变量变成全局COM
变量。相反,尽量将这些全局变量转化成为代表您的对象的CoClass成员。
这样,您就把同步的麻烦减小到最少了。

  您也许知道,COM线程包含了单元原则。最普通的就是单线程单元
(STA),以及自由或多线程单元(MTA)。另一个模型,用COM+描述,是
出租的单元。COM利用这种原则来防止线程模型透明并不许COM对象或客户
程序使用它。一个单元是一个逻辑体,在这里一个 COM对象必须被执行,
每一个COM对象存在在一个单元中。当它仅仅有零个或一个MTA时,一个进
程有一个或多个STA单元。一个进程能够有一个 MTA和任意数量的STA。尽
管一个单元可以有多于一个的对象。

  您能够把单元模型看成一个客户或对象选择执行的规则设置。为了使
所有的用户意识到哪个设置正在使用,COM必须介入。在STA中,任何有赖
于对象的方法调用必须从一个或相同的线程中产生。任何跨单元的调用必
须整理。整理是一个标准的 COM机制,它把在不同单元间的变量和数据打
包。在STA模型中,COM使调用串行化,保证您在单元内的对象或者变量能
通过一个线程和一个调用在一个时间被访问。换句话说, COM将只会允许
那些创建对象来进行依赖对象的调用的线程。串行化在 STA中使用通常的
窗口消息和一个线程消息队列。数据在STAs中被编组排列。在MTA模型中,
对象能够从任意线程中被访问。在MTA单元模型中调用对象没有串行化。
COM假设您开始关心到您的全局变量的并行通道,因此在MTA中的线程不需
要排列。这将更加有效,如果您知道您要做什么。但是如果您不知道,这
就会很糟糕。

  当用CoInitializeEx()初始化一个 COM库时,单元模型就已经在一
个进程或一个线程中被确定了。为了声明 MTA成为整个进程的模型,您只
需一个CoInitializeEx()调用,把 COINIT_MULTITHREADED当成第二参
数传送。CoInitializeEx( NULL,COINIT_MULTITHREADED);为了创建
一个STA单元,您需要调用 CoInitialize()或者CoInitializeEx(),
把COINIT_APARTMENTTHREADED当作它的第二参数传送,使用COM库的每个
线程各一次。

CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
或者:
CoInitialize(NULL);

  这是一个EXE或进程外服务器选择它们的线程模型的非常清楚的方法。
现在,如何作过程内服务器控制呢?一个进程内服务器有一个变量称作
ThreadingModel,它在InprocServer32注册键下。这个注册键存在于对象
CLSID入口下。全部键可以按下面来编写:
HKEY_CLASSES_ROOT\CLSID\{Object 128-bit CLSID}
   \InprocServer32

  您需要把ThreadingModel变量设置成您的服务器对象想要利用的模型。
您可以把它设置成Apartment来声明STA或者设置成Free来声明 MTA。还有
一个推荐的可选方案,就是双选。使用双选模型,您告诉COM 您的进程内
服务器准备好对付STA或MTA模型任何一个,客户也准备好对付其中任何一
个模型。在这里,请让我告诉您一个很大的好处。当客户和服务器使用不
同的模型时(并且由于 COM将会透明地关照这东西),所有跨单元的信息
将被排序。排序通常是有代价的,那就是性能。那样会有更多的来回。如
果您的进程内服务器被标志成Free并且客户想要使 用STA,那么必须使用
编组同时您将接受性能降低的惩罚。另一方面,如果使用双选模型, COM
将检测到它并且把服务器当成STA 使用,就像客户;因此不需要编组。通
过这个方案,您会提高性能。

  记住,在使用COM 对象时的并行问题是非常重要的。但是,这是在这
里作为主题而进行考虑的唯一问题吗?答案是不。在前面部分中,我说过
我使用的同步技术是通线程。除非您动态的为在线程中创建的每个对象事
例分配一个新的同步消息,比如WM_RESULT。您把您的对象事例限制成了
每一个线程一个对象事例。终于明白了!否则,您可以想象到当执行将要
发送进对象1中的任务1时,在对象2中去捕捉WM_RESULT,所产生的混乱。
相信我吧,这种错误通常很难编辑和发现的,因为除非您期待某个相应,
您会搞不清这个错误的来源。

  因此,如果您的对象是设计成使用一个同步消息,您应当意识到您需
要为每一个事例分配一个线程,换句话说,一个线程不能够创建多于一个
的对象事例。当使用ASP,您的对象也许创建许多次,也许是因为许多WEB
网页被调用。一个像ASP的客户是一个MTA。但是如果您的 ASP COM线程模
型被标记成单元,COM将通过一个线程在一个STA中访问所有您的对象,即
便如果是ASP或任何客户在一个不同的线程中创建每一个对象。 在这种情
况下,所有对象事例将共享相同的消息队列,就像哪个线程。如果您使用
一个同步方法,您将遇到一个我在前面解释过的产生混乱的问题。如何在
不重新编写所有您的部件并且为每个事例添加新的消息的条件下来解决这
个局面呢?

  简单!只要修改变量ThreadingModel成Free或者BOTH。这样, COM不
必添加附加的编组,因为客户是一个MTA并且进程内服务器是MTA,或者将
会,如果标记成BOTH的话。因此,对象可以通过一个新的线程被访问,如
果客户决定这么做的话。

  ASP是一个真正的高级类型客户。它使用线程集合来处理WEB客户的请
求。例如,当第一个WEB客户请求Connect。htm网页时,ASP分配了一个新
线程,线程1,管理这个网页的任务。如果在处理前一个网页请求时ASP接
收一个新的请求来要求同一个网页,另一个线程——线程 2将被创建,来
处理这一个请求。如果第二个请求在第一个请求处理之后到达,现在已空
闲的线程1将接管并处理这一个请求。

  当客户和进程内服务器决定按MTA方式运行时,COM并不会介入,并且
仅客户决定是否在一个线程中或在另一个存在的线程中创建一个对象。这
是根据客户逻辑;因此,如果您在一个网页上示范两个事例,它们将会在
一个线程中。

  通常不会出现什么问题,因为执行是连续的并且这也是这篇文章的目
标。但是如果一个从第一次示范的对象中产生的杂散的未处理的消息在后
来被在第二个对象时刻中的某些任务捕获时,它将产生混淆。为了避免这
些问题,我将向您展示一个解决方案,它不仅对分离的任务有效并且可以
允许您在任务可能重叠的同一线程中使用多个对象。如果您联和您的对象
以及一个在新的对象事例创建的每个时刻所创建的窗口时,您能够发送同
步消息给它。您需要过滤那些直接指向现在事例的消息并且让其它离开。
您的代码可以像下面这样:
BOOL CMyObj::WaitForResult(WPARAM *pwPar,
              LPARAM *plPar)

 BOOL bRet = FALSE;
 MSG Msg;
while(TRUE)
 {
 while(::PeekMessage(&Msg,NULL, 0, 0, PM_REMOVE))
 {
  if((Msg。message==WM_RESULT)||
   (Msg。message==WM_QUIT))
  {
  *pwPar = Msg。wParam;
  *plPar = Msg。lParam;
  bRet = TRUE;
  if(Msg。message == WM_QUIT)
   PostQuitMessage(0);
   // 保证仅有这个事例是想要的事例
   else if(Msg。hwnd != m_hWnd)
   {
   bCont = TRUE;
   ::DispatchMessage(&Msg);
   continue;
  }
  return bRet;
  }
  TranslateMessage(&Msg);
  ::DispatchMessage(&Msg);
 }
 // 服从于其它的线程和进程的控制
 WaitMessage();
 }
 return bRet;


结合同步和异步

  您可以选择您的部件提供两种方式。您可以选择为VB 和 VC++客户
提供异步调用并且使用连接点机制来把事件通知它们。同时,您能够通过
一个阻塞或同步模式来让您的部件与 ASP脚本环境相匹配。(也许,在使
用对象决定回调是同步还是异步之前,使用客户设置的一个布尔属性)这
使您给客户提供了很大的灵活性和控制,并且这制造了一个十分流行的对
象。我的事例项目,Ftp Demo,显示了如何构造一个插座连接可以被看作
一个异步任务(去发现服务器并联上它;当连接完成时把我叫回来)或者
一个同步的任务(去发现服务器,连接,并且直到我们连接后再返回)。
一个对话框让您选择那一个方法。为了尽可能全看见,您必须在一个编译
器中运行项目;我已经在代码中添加了许多TRACE说明。


...全文
244 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
dianxian1 2001-11-07
  • 打赏
  • 举报
回复
学习
老老辉辉 2001-11-02
  • 打赏
  • 举报
回复
up,up,up
ZhenHua28 2001-04-16
  • 打赏
  • 举报
回复
太好了
蝈蝈俊 2001-02-23
  • 打赏
  • 举报
回复
aa
yzh 2001-02-17
  • 打赏
  • 举报
回复
好东西
kuhx 2001-02-04
  • 打赏
  • 举报
回复
听课
enigma 2001-01-11
  • 打赏
  • 举报
回复
谢谢

1,649

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 非技术类
社区管理员
  • 非技术类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧