远程调用技术代码追踪(webservice)

    技术2022-05-11  83

    最近阅读了SocketConn的源码和WebService 的源码,把追踪的过程写了下来,方便大家学习。毕竟这需要精力,时间和毅力。感谢煮茶待英雄博志区和三层数据库讨论区兄弟们的支持,特别是julian兄弟,不是他,我可能没耐心继续下去。如果有时间,大家可以继续完善。从socket和Websevice的底层实现细节,我们发现BORLAND的工程师们的构思和实现的过程。我觉得这对我们的学习应该是非常重要的。学会思考。学会读源码,学会分析。 希望和我交往的朋友可通过QQ或Email联系我。Wu_yanan2003@yahoo.com.cn 另见:《远程调用技术代码追踪(socket) 》 关注我的:《远程调用技术代码追踪(第三方控件) 》       远程调用技术内幕 有关WebService的相关的知识,我就不说了,我直接分析源码。有问题的地方请参考李维的书。 initialization InvRegistry.RegisterInterface(TypeInfo(IMyFirstWS), 'urn:MyFirstWSIntf-IMyFirstWS', 'utf-8'); 看过李维的分布式架构的应该都知道,WEB服务端对类和接口进行了注册,客户端这里也进行了注册。然后客户端把数据通过HTTP传输到服务器端,服务器端通过拆包,去到注册管理的类中寻找相应的接口,并创建一个相应的对象,把客户端的数据压进去,调用后,把数据再传回来。 在调用这句的时候,TinvokableClassRegistry类已经创建了,由于inquire_v1也引用了InvRegistry注册,所以在哪里被引用的时候已经被创建了。 function InvRegistry: TInvokableClassRegistry; begin  if not Assigned(InvRegistryV) then     InitIR;  Result := InvRegistryV; end; 初次引用会调用InitIR方法。 procedure InitIR; begin  InvRegistryV := TInvokableClassRegistry.Create;  RemTypeRegistryV := TRemotableClassRegistry.Create;  RemClassRegistryV:= RemTypeRegistry;  InitBuiltIns; //定们到这一句:  InitXSTypes;  InitMoreBuiltIns; end;   先看InvRegistryV := TInvokableClassRegistry.Create;,这个类是用来注册,相应的接口及类, 并能够根据soap封包内容找到相应的接口及方法。 TRemotableClassRegistry        = TRemotableTypeRegistry; 所对应的是TremotableTypeRegistry, 这个类主要是对数据类型进行注册。   大致来了解一下这个类。 TInvokableClassRegistry = class(TInterfacedObject)  private     FLock: TRTLCriticalSection;     FRegClasses: array of InvRegClassEntry; FRegIntfs: array of InvRegIntfEntry; 这里可以看到,声明了两个动态数组。分别用来放接口注册,及类注册信息。 TCreateInstanceProc = procedure(out obj: TObject); InvRegClassEntry = record     ClassType: TClass;     Proc: TCreateInstanceProc;     URI: string;  end; 它包含了webservice实现类的指针,以建立实现类的factory函数指针。   InvRegIntfEntry = record     Name: string;                             { Native name of interface    }     ExtName: Widestring;                      { PortTypeName                }     UnitName: string;                         { Filename of interface       }     GUID: TGUID;                              { GUID of interface           }     Info: PTypeInfo;                          { Typeinfo of interface       }     DefImpl: TClass;                          { Metaclass of implementation }     Namespace: Widestring;                    { XML Namespace of type       }     WSDLEncoding: WideString;                 { Encoding                    }     Documentation: string;                    { Description of interface    }     SOAPAction: string;                       { SOAPAction of interface     }     ReturnParamNames: string;                 { Return Parameter names      }     InvokeOptions: TIntfInvokeOptions;        { Invoke Options              }     MethNameMap: array of ExtNameMapItem;             { Renamed methods     }     MethParamNameMap: array of MethParamNameMapItem; { Renamed parameters }     IntfHeaders: array of IntfHeaderItem;      { Headers                    }     IntfExceptions: array of IntfExceptionItem;{ Exceptions                 }     UDDIOperator: String;                      { UDDI Registry of this porttype }     UDDIBindingKey: String;                    { UDDI Binding key           }  end;   看到它里面有很多东西,接口名称,单元名,GUID等信息。    procedure InitBuiltIns; begin  { DO NOT LOCALIZE }  RemClassRegistry.RegisterXSInfo(TypeInfo(System.Boolean), XMLSchemaNameSpace, 'boolean'); 对于处理结构型数据,需要进行 SOAP 封包类型的转换 开发人员在使用这种自定义数据类型前必须对其进行注册,分别是 RegisterXSClass RegisterXSInfo 。前一个方法是注册从 Tremotable 继承下来的类,后一个不需要是从 TremotablXS 继承下来的类。   InitBuiltIns;     InitXSTypes;  InitMoreBuiltIns; 这三个函数类似,都是注册一些基本类型等。 看看到底怎么处理的,(这里注册一个BOOLEAN类型) RemClassRegistry.RegisterXSInfo(TypeInfo(System.Boolean), XMLSchemaNameSpace, 'boolean'); procedure TRemotableTypeRegistry.RegisterXSInfo(Info: PTypeInfo; const URI: WideString = '';                                                 const Name: WideString = '';                                                 const ExtName: WideString = '');  … Index := GetEntry(Info, Found, Name);       if Found then       Exit;     if AppNameSpacePrefix <> '' then       AppURI := AppNameSpacePrefix + '-';     if URI = '' then     begin       if Info.Kind = tkDynArray then       begin         UnitName := GetTypeData(Info).DynUnitName;         URIMap[Index].URI := 'urn:' + AppURI + UnitName;       end       else if Info.Kind = tkEnumeration then       begin         UnitName := GetEnumUnitName(Info);         URIMap[Index].URI := 'urn:' + AppURI + UnitName;       end       else if Info.Kind = tkClass then         URIMap[Index].URI := 'urn:' + AppURI + GetTypeData(Info).UnitName       else         URIMap[Index].URI := 'urn:' + AppURI;     end     else       URIMap[Index].URI := URI;     if Name <> '' then       URIMap[Index].Name := Name     else     begin       URIMap[Index].Name := Info.Name;     end;     URIMap[Index].ExtName := ExtName;     URIMap[Index].Info := Info;     if Info.Kind = tkClass then       URIMap[Index].ClassType := GetTypeData(Info).ClassType;  finally     UnLock;  end; end;   看研究一下GetEntry函数,这里以后多次用到,发现这个函数是TremotableClassRegistry类的,说明实际的注册还是在TremotableClassRegistry这个类完成的。   function TRemotableClassRegistry.GetEntry(Info: PTypeInfo; var Found: Boolean; const Name: WideString): Integer; begin  Result := FindEntry(Info, Found, Name);  if not Found then     SetLength(URIMap, Result + 1); end; 这个函数功能是搜索类型是否已注册,否则,动态数组加1,分配空间进行注册。   看看FindEntry (这里传进来的info是TypeInfo(System.Boolean), name: Boolean) function TRemotableClassRegistry.FindEntry(Info: PTypeInfo; var Found: Boolean; const Name: WideString): Integer; begin  Result := 0;  Found := False;  while Result < Length(URIMap) do  begin     if (Info <> nil) and (URIMap[Result].Info = Info) then     begin       if (Name = '') or (URIMap[Result].Name = Name) then       begin         Found := True;         Exit;       end;     end;     Inc(Result);  end; end; 这个函数的功能是遍历整个动态数组TremRegEntry,利用TypeInfo信息和名字进行搜索,查看是否已进行注册。   看看URIMAP的定义: URIMAP:    array of TRemRegEntry;  TObjMultiOptions = (ocDefault, ocMultiRef, ocNoMultiRef);  TRemRegEntry = record     ClassType: TClass; //类信息     Info: PtypeInfo;    // typeInfo信息(RTTL)     URI: WideString;   //     Name: WideString; //     ExtName: WideString; //     IsScalar: Boolean;    //     MultiRefOpt: TObjMultiOptions; //     SerializationOpt: TSerializationOptions;     PropNameMap: array of ExtNameMapItem;             { Renamed properties }  end; 继续RegisterXSInfo函数: 这是对动态数组的uri赋值: if AppNameSpacePrefix <> '' then       AppURI := AppNameSpacePrefix + '-';     if URI = '' then     begin       if Info.Kind = tkDynArray then       begin         UnitName := GetTypeData(Info).DynUnitName;         URIMap[Index].URI := 'urn:' + AppURI + UnitName;       end       else if Info.Kind = tkEnumeration then       begin         UnitName := GetEnumUnitName(Info);         URIMap[Index].URI := 'urn:' + AppURI + UnitName;       end       else if Info.Kind = tkClass then         URIMap[Index].URI := 'urn:' + AppURI + GetTypeData(Info).UnitName       else         URIMap[Index].URI := 'urn:' + AppURI;     end     else       URIMap[Index].URI := URI;     if Name <> '' then       URIMap[Index].Name := Name     else     begin       URIMap[Index].Name := Info.Name; end;   这句比较关键: URIMap[Index].Info := Info; 把RTTL信息保存在URL动态数组中。   总结一下:一些基本类型,都是通过这种方式,把URI,及INFO信息保存在动态数组中的。 为什么要进行登记,因为WEBSERVICE中的数据类型要转换成DELPHI的PAS类型,用URI标记的XML文件,传输之后,根据这张对照表,就可以分配相应的空间。另外这些类型的注册信息是放在:TremRegEntry动态数组中的。和我们自己定义的接口及类是不同的。 FRegClasses: array of InvRegClassEntry;  FRegIntfs: array of InvRegIntfEntry; 这是注册自己定义接口及类的动态数组。   再来分析: InitBuiltIns函数中的: RemClassRegistry.RegisterXSClass(TSOAPAttachment, XMLSchemaNamespace, 'base64Binary', '', False, ocNoMultiRef); 大致和基本类型差不多。 procedure TRemotableTypeRegistry.RegisterXSClass(AClass: TClass; const URI: WideString = '';                                                  const Name: WideString = '';                                                  const ExtName: WideString = '';                                                  IsScalar: Boolean = False;                                                  MultiRefOpt: TObjMultiOptions = ocDefault); var  Index: Integer;  Found: Boolean;  AppURI: WideString; begin  Lock;  try     Index := GetEntry(AClass.ClassInfo, Found, Name);     if not Found then     begin       if AppNameSpacePrefix <> '' then         AppURI := AppNameSpacePrefix + '-';       if URI = '' then         URIMap[Index].URI := 'urn:' + AppURI + GetTypeData(AClass.ClassInfo).UnitName { do not localize }       else         URIMap[Index].URI := URI;       if Name <> '' then         URIMap[Index].Name := Name       else       begin         URIMap[Index].Name := AClass.ClassName;        end;       URIMap[Index].ExtName := ExtName;       URIMap[Index].ClassType := AClass;       URIMap[Index].Info := AClass.ClassInfo;       URIMap[Index].IsScalar := IsScalar;       URIMap[Index].MultiRefOpt := MultiRefOpt;     end;  finally     UnLock;  end; end;     前面都是说系统类型的注册。下面看看我们自己定义的接口,是如何注册的: procedure TInvokableClassRegistry.RegisterInterface(Info: PTypeInfo; const Namespace: InvString;                     const WSDLEncoding: InvString; const Doc: string; const ExtName: InvString);       for I := 0 to Length(FRegIntfs) - 1 do       if FRegIntfs[I].Info = Info then         Exit;   Index := Length(FRegIntfs); SetLength(FRegIntfs, Index + 1);   GetIntfMetaData(Info, IntfMD, True);     FRegIntfs[Index].GUID := IntfMD.IID;     FRegIntfs[Index].Info := Info;     FRegIntfs[Index].Name := IntfMD.Name;     FRegIntfs[Index].UnitName := IntfMD.UnitName;     FRegIntfs[Index].Documentation := Doc;     FRegIntfs[Index].ExtName := ExtName;     FRegIntfs[Index].WSDLEncoding := WSDLEncoding;       if AppNameSpacePrefix <> '' then       URIApp := AppNameSpacePrefix + '-';       { Auto-generate a namespace from the filename in which the interface was declared and       the AppNameSpacePrefix }     if Namespace = '' then       FRegIntfs[Index].Namespace := 'urn:' + URIApp + IntfMD.UnitName + '-' + IntfMD.Name     else     begin       FRegIntfs[Index].Namespace := Namespace;       FRegIntfs[Index].InvokeOptions := FRegIntfs[Index].InvokeOptions + [ioHasNamespace];     end;       if FRegIntfs[Index].DefImpl = nil then     begin       { NOTE: First class that implements this interface wins!! }       for I := 0 to Length(FRegClasses) - 1 do       begin           Table := FRegClasses[I].ClassType.GetInterfaceTable;         if (Table = nil) then         begin           Table := FRegClasses[I].ClassType.ClassParent.GetInterfaceTable;         end;         for J := 0 to Table.EntryCount - 1 do         begin           if IsEqualGUID(IntfMD.IID, Table.Entries[J].IID) then           begin             FRegIntfs[Index].DefImpl := FRegClasses[I].ClassType;             Exit;           end;         end;       end;     end;  finally     Unlock;  end; end;   功能: for I := 0 to Length(FRegIntfs) - 1 do       if FRegIntfs[I].Info = Info then         Exit; 遍历FRegIntfs: array of InvRegIntfEntry;数组,根据TypeInfo信息判断该接口是否已注册。 Index := Length(FRegIntfs); SetLength(FRegIntfs, Index + 1); 新增一个数组元素。 GetIntfMetaData(Info, IntfMD, True); //得到接口的RTTL信息,然后动态增加到注册的动态数组中。     FRegIntfs[Index].GUID := IntfMD.IID;     FRegIntfs[Index].Info := Info;     FRegIntfs[Index].Name := IntfMD.Name;     FRegIntfs[Index].UnitName := IntfMD.UnitName;     FRegIntfs[Index].Documentation := Doc;     FRegIntfs[Index].ExtName := ExtName; FRegIntfs[Index].WSDLEncoding := WSDLEncoding;   DefImpl里存放的是classType信息: if FRegIntfs[Index].DefImpl = nil then     begin       for I := 0 to Length(FRegClasses) - 1 do       begin           Table := FRegClasses[I].ClassType.GetInterfaceTable;         if (Table = nil) then         begin           Table := FRegClasses[I].ClassType.ClassParent.GetInterfaceTable;         end;         for J := 0 to Table.EntryCount - 1 do         begin           if IsEqualGUID(IntfMD.IID, Table.Entries[J].IID) then           begin             FRegIntfs[Index].DefImpl := FRegClasses[I].ClassType;             Exit;           end;         end;       end;     end; 注意这里: FRegClasses: array of InvRegClassEntry; 到注册类的动态数组中去搜寻接口的实现类是否注册,如果注册,便把实现类的指针拷贝到DefImpl数据字段。   顺便看一下类是怎么注册的: procedure TInvokableClassRegistry.RegisterInvokableClass(AClass: TClass; CreateProc: TCreateInstanceProc); var  Index, I, J: Integer;  Table: PInterfaceTable;   begin  Lock;  try Table := AClass.GetInterfaceTable;      。。。。。。     Index := Length(FRegClasses);     SetLength(FRegClasses, Index + 1);     FRegClasses[Index].ClassType := AClass;     FRegClasses[Index].Proc := CreateProc;       for I := 0 to Table.EntryCount - 1 do     begin       for J := 0 to Length(FRegIntfs) - 1 do         if IsEqualGUID(FRegIntfs[J].GUID, Table.Entries[I].IID) then           if FRegIntfs[J].DefImpl = nil then             FRegIntfs[J].DefImpl := AClass;     end;  finally     UnLock;  end; end; 可以看到和注册接口非常相似。在调用上面方法时,会传入实现类的指针及factory函数指针,调用GetInterfaceTable判断是否实现接口。否则为NIL, 然后在FregClasses增加一元素,把值写入。最后再到FregIntfs是搜寻此实现类的接口是否已经注册。是的话,就把指针储存在FRegIntfs[J].DefImpl中。 继续: InvRegistry.RegisterDefaultSOAPAction(TypeInfo(IMyFirstWS), 'urn:MyFirstWSIntf-IMyFirstWS#%operationName%');   procedure TInvokableClassRegistry.RegisterDefaultSOAPAction(Info: PTypeInfo; const DefSOAPAction: InvString); var  I: Integer; begin     I := GetIntfIndex(Info);     if I >= 0 then     begin FRegIntfs[I].SOAPAction := DefSOAPAction;  //值为:urn:MyFirstWSIntf-IMyFirstWS#%operationName         FRegIntfs[I].InvokeOptions := FRegIntfs[I].InvokeOptions + [ioHasDefaultSOAPAction];       Exit;     end; end;   设置接口的SOAPAction, 及InvokeOptions属性。 上面讲了用户接口及自定义类注册的实现。   看看这几句为何如此神奇,竟然可以实现对象的远程调用? MyHTTPRIO := THTTPRIO.Create(nil); MyHTTPRIO.URL :='http://localhost/soap/MyCGI.exe/soap/IMyFirstWS'; ShowMessage(( MyHTTPRIO As IMyFirstWS ).GetObj);   研究一下客户端代码: constructor THTTPRIO.Create(AOwner: TComponent); begin  inherited Create(AOwner);  { Converter }   FDomConverter := GetDefaultConverter;  FConverter := FDomConverter as IOPConvert;  { WebNode }  FHTTPWebNode := GetDefaultWebNode;  FWebNode := FHTTPWebNode as IWebNode; end;   继续到父类中TRIO查看相应代码: constructor TRIO.Create(AOwner: TComponent); begin  inherited Create(AOwner);  FInterfaceBound := False;  FContext := TInvContext.Create;    FSOAPHeaders := TSOAPHeaders.Create(Self);  FHeadersInbound := THeaderList.Create;  FHeadersOutBound:= THeaderList.Create;  FHeadersOutbound.OwnsObjects := False;  (FSOAPHeaders as IHeadersSetter).SetHeadersInOut(FHeadersInbound, FHeadersOutBound); end;   创建了TinvContext,这个对象是用来创建一个和服务器端一样的调用环境。 客户端的参数信息一个个的填入这个环境中。 创建一个TSOAPHeaders头对象。   回到 constructor THTTPRIO.Create(AOwner: TComponent); begin  inherited Create(AOwner);  { Converter }  FDomConverter := GetDefaultConverter;  FConverter := FDomConverter as IOPConvert;  { WebNode }  FHTTPWebNode := GetDefaultWebNode;  FWebNode := FHTTPWebNode as IWebNode; end;   function THTTPRIO.GetDefaultConverter: TOPToSoapDomConvert; begin  if (FDefaultConverter = nil) then  begin     FDefaultConverter := TOPToSoapDomConvert.Create(Self);     FDefaultConverter.Name := 'Converter1';                 { do not localize }     FDefaultConverter.SetSubComponent(True);  end;  Result := FDefaultConverter; end; 而TOPToSoapDomConvert可以把Object Pascal的呼叫和參數自動轉換為SOAP封裝的格式資訊,再藉由THTTPReqResp傳送HTTP封包。   function THTTPRIO.GetDefaultWebNode: THTTPReqResp; begin  if (FDefaultWebNode = nil) then  begin     FDefaultWebNode := THTTPReqResp.Create(Self);     FDefaultWebNode.Name := 'HTTPWebNode1';                { do not localize }     FDefaultWebNode.SetSubComponent(True);  end;  Result := FDefaultWebNode; end; //用来传送HTTP的封包。     function THTTPRIO.GetDefaultConverter: TOPToSoapDomConvert; begin  if (FDefaultConverter = nil) then  begin     FDefaultConverter := TOPToSoapDomConvert.Create(Self);     FDefaultConverter.Name := 'Converter1';                 { do not localize }     FDefaultConverter.SetSubComponent(True);  end;  Result := FDefaultConverter; end;     FHTTPWebNode := GetDefaultWebNode; function THTTPRIO.GetDefaultWebNode: THTTPReqResp; begin  if (FDefaultWebNode = nil) then  begin     FDefaultWebNode := THTTPReqResp.Create(Self);     FDefaultWebNode.Name := 'HTTPWebNode1';                { do not localize }     FDefaultWebNode.SetSubComponent(True);  end;  Result := FDefaultWebNode; end;   创建了一个THTTPReqResp,用于HTTP通信。 MyHTTPRIO.URL :='http://localhost/soap/MyCGI.exe/soap/IMyFirstWS'; procedure THTTPRIO.SetURL(Value: string); begin  if Assigned(FHTTPWebNode) then  begin     FHTTPWebNode.URL := Value;     if Value <> '' then     begin       WSDLLocation := '';       ClearDependentWSDLView;     end;  end; end;   procedure THTTPReqResp.SetURL(const Value: string); begin  if Value <> '' then     FUserSetURL := True   else     FUserSetURL := False;  InitURL(Value);  Connect(False); end;   procedure THTTPReqResp.InitURL(const Value: string);       InternetCrackUrl(P, 0, 0, URLComp);     FURLScheme := URLComp.nScheme;     FURLPort := URLComp.nPort;     FURLHost := Copy(Value, URLComp.lpszHostName - P + 1, URLComp.dwHostNameLength);  FURL := Value; end; 设置THTTPReqResp的属性。和HTTP服务器通信。   procedure THTTPReqResp.Connect(Value: Boolean); if Assigned(FInetConnect) then       InternetCloseHandle(FInetConnect);     FInetConnect := nil;     if Assigned(FInetRoot) then       InternetCloseHandle(FInetRoot);     FInetRoot := nil; FConnected := False; Value 为FLASE。     ShowMessage(( MyHTTPRIO As IMyFirstWS ).GetObj); 利用AS转换成webservice的接口。用转换后的接口到客户端的InvRegInftEntry表格中搜寻WEBSERVICE服务接口,根据RTTL生成SOAP封包。   procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID); 先看这一句:CALL     DWORD PTR [EAX] + VMTOFFSET IInterface.QueryInterface   function THTTPRIO.QueryInterface(const IID: TGUID; out Obj): HResult; var  UDDIOperator, UDDIBindingKey: string; begin  Result := inherited QueryInterface(IID, Obj);  if Result = 0 then  begin     if IsEqualGUID(IID, FIID) then     begin       FHTTPWebNode.SoapAction := InvRegistry.GetActionURIOfIID(IID);       if InvRegistry.GetUDDIInfo(IID, UDDIOperator, UDDIBindingKey) then       begin         FHTTPWebNode.UDDIOperator := UDDIOperator;         FHTTPWebNode.UDDIBindingKey := UDDIBindingKey;       end;     end;  end; end;   Result := inherited QueryInterface(IID, Obj);//跟踪一下这一句: 这句比较重要,要重点分析。 这里创建了虚拟表格。   function TRIO.QueryInterface(const IID: TGUID; out Obj): HResult; begin  Result := E_NOINTERFACE;  { IInterface, IRIOAccess } //判断接口是不是IRIOAccess类型  if IsEqualGUID(IID, IInterface) or IsEqualGUID(IID, IRIOAccess) then  { ISOAPHeaders }//判断接口是不是ISOAPHeaders类型  if IsEqualGUID(IID, ISOAPHeaders) then …     if GenVTable(IID) then     begin       Result := 0;       FInterfaceBound := True;       Pointer(Obj) := IntfTableP;       InterlockedIncrement(FRefCount);     end;   看看GenVTable函数: function TRIO.GenVTable(const IID: TGUID): Boolean; Info := InvRegistry.GetInterfaceTypeInfo(IID); 这个函数是去到TinvokableClassRegistry中搜寻该接口是否注册,注册过的接口则返回typeinfo信息赋给指针。 function TInvokableClassRegistry.GetInterfaceTypeInfo(const AGUID: TGUID): Pointer; var  I: Integer; begin  Result := nil;  Lock;  try     for I := 0 to Length(FRegIntfs) - 1 do     begin       if IsEqualGUID(AGUID, FRegIntfs[I].GUID) then       begin         Result := FRegIntfs[I].Info;         Exit;       end;     end;  finally     UnLock;  end; end;   继续:通过infotype得到RTTL信息。  try     GetIntfMetaData(Info, IntfMD, True);  except     HasRTTI := False;     Exit;  end;   { TProc = procedure of object;  TObjFunc = function: Integer of Object; stdcall;  TQIFunc = function(const IID: TGUID; out Obj): HResult of object; stdcall;  PProc = ^TProc; TCracker = record     case integer of       0: (Fn: TProc);       1: (Ptr: Pointer);       2: (ObjFn: TObjFunc);       3: (QIFn: TQIFunc);     end;}  Crack.Fn := GenericStub;  StubAddr := Crack.Ptr;  地址指向函数TRIO.GenericStub函数。 Crack.Fn结构的指针指向 这段代码的意思是用C/stdcall等方式调用函数。 从左到右,从右到左压入堆栈。调整TRIO.IntfTable的指针,最后调用TRIO.Generic procedure TRIO.GenericStub; asm         POP     EAX { Return address in runtime generated stub }         POP     EDX { Is there a pointer to return structure on stack and which CC is used? }         CMP     EDX, 2         JZ      @@RETONSTACKRL          CMP     EDX, 1         JZ      @@RETONSTACKLR         POP     EDX           { Method # pushed by stub }         PUSH    EAX           { Push back return address }         LEA     ECX, [ESP+12] { Calc stack pointer to start of params }         MOV     EAX, [ESP+8] { Calc interface instance ptr }         JMP     @@CONT @@RETONSTACKLR:         POP     EDX           { Method # pushed by stub   }         PUSH    EAX           { Push back return address }         LEA     ECX, [ESP+12] { Calc stack pointer to start of params }         MOV     EAX, [ESP+8] { Calc interface instance ptr }         JMP     @@CONT @@RETONSTACKRL:         POP     EDX           { Method # pushed by stub }         PUSH    EAX           { Push back return address }         LEA     ECX, [ESP+8] { Calc stack pointer to start of params }         MOV     EAX, [ESP+12] { calc interface instance ptr } @@CONT:         SUB     EAX, OFFSET TRIO.IntfTable; { Adjust intf pointer to object pointer }         JMP     TRIO.Generic end;      Crack.Fn := ErrorEntry;  ErrorStubAddr := Crack.Ptr;   //首先分配vtable空间,接口数加3, 因为有Iunknown接口。  GetMem(IntfTable, (Length(IntfMD.MDA) + NumEntriesInIInterface) * 4);  IntfTableP := @IntfTable;  然后把地址赋给IntfTableP变量    GetMem(IntfStubs, (Length( IntfMD.MDA) + NumEntriesInIInterface) * StubSize );  分配存根接口空间。  这是解释  IntfTable: Pointer;              { Generated vtable for the object   }     IntfTableP: Pointer;            { Pointer to the generated vtable   }     IntfStubs: Pointer;             { Pointer to generated vtable thunks}   //Load the IUnknown vtable 分配指针,加入三个接口Iunknown  VTable := PPointer(IntfTable);  Crack.QIFn := _QIFromIntf;  QI查询指针赋值给 Crack结构体  VTable^ := Crack.Ptr; 赋给VT指针  IncPtr(VTable, 4);     增加一个指针。    Crack.ObjFn := _AddRefFromIntf;  VTable^ := Crack.Ptr;  IncPtr(VTable, 4);  Crack.ObjFn := _ReleaseFromIntf;  VTable^ := Crack.Ptr;  IncPtr(VTable, 4);        VTable := AddPtr(IntfTable, NumEntriesInIInterface * 4); //增加IunKnown指针的三个方法。压入IntfTable中。  Thunk := AddPtr(IntfStubs, NumEntriesInIInterface * StubSize);  //调整Thunk,加入IunKnown接口方法。   //遍历所有方法:产生机器相应的汇编机器代码。  for I := NumEntriesInIInterface to Length(IntfMD.MDA) - 1 do  begin     CallStubIdx := 0;       if not IntfMD.MDA[I].HasRTTI then     begin       GenByte($FF); { FF15xxxxxxxx Call [mem]    }       GenByte($15);       Crack.Fn := ErrorEntry;       GenDWORD(LongWord(@ErrorStubAddr));     end else     begin       { PUSH the method ID }       GenPushI(I);    //定位这里:看看函数做了什么: CallStub: array[0..StubSize-1] of Byte; I=3。CallStubIdx=2 procedure TRIO.GenPushI(I: Integer); begin  if I < 128 then  begin     CallStub[CallStubIdx] := $6A;     CallStub[CallStubIdx + 1] := I;     Inc(CallStubIdx, 2);  end  else  begin     CallStub[CallStubIdx] := $68;     PInteger(@CallStub[CallStubIdx + 1])^ := I;     Inc(CallStubIdx, 5);  end; end; 登记函数调用信息, 数组增加一元素。   遍历接口信息,函数ID号压入堆栈中。         { PUSH the info about return value location }       if RetOnStack(IntfMD.MDA[I].ResultInfo) then       begin         if IntfMD.MDA[I].CC in [ccStdcall, ccCdecl] then           GenPushI(2)         else           GenPushI(1);       end       else         GenPushI(0); 把返回值压入堆栈中。//把返回参数压入堆栈。       接着把GenericStub压入堆栈中。       { Generate the CALL [mem] to the generic stub }       GenByte($FF); { FF15xxxxxxxx Call [mem] }       GenByte($15); GenDWORD(LongWord(@StubAddr));   这几句是生成汇编的代码。可以产生这样的调用: ff15xxxxxx:地址: caa [mem]编号: //这里调用的。 //看看里面的内容是什么:         { Generate the return sequence }       if IntfMD.MDA[I].CC in [ccCdecl] then       begin         { For cdecl calling convention, the caller will do the cleanup, so }         { we convert to a regular ret. }         GenRet;       end       else       begin                 BytesPushed := 0;         for J := 0 to IntfMD.MDA[I].ParamCount - 1 do         begin            if IsParamByRef(IntfMD.MDA[I].Params[J].Flags, IntfMD.MDA[I].Params[J].Info, IntfMD.MDA[I].CC) then              Inc(BytesPushed, 4)            else Inc(BytesPushed, GetStackTypeSize(IntfMD.MDA[I].Params[J].Info, IntfMD.MDA[I].CC )); //每个参数分配空间。         end;             Inc(BytesPushed, GetStackTypeSize(IntfMD.MDA[I].SelfInfo, IntfMD.MDA[I].CC )); //压入函数本身信息:           { TODO: Investigate why not always 4 ?? }         if RetOnStack(IntfMD.MDA[I].ResultInfo) or (IntfMD.MDA[I].CC = ccSafeCall) then           Inc(BytesPushed, 4);           if BytesPushed > 252 then           raise Exception.CreateFmt(STooManyParameters, [IntfMD.MDA[I].Name]);           GenRET(BytesPushed);       end; end;   //GenRET(BytesPushed); 分配函数参数空间。     { Copy as much of the stub that we initialized over to the }     { block of memory we allocated. }     P := PByte(Thunk);     for J := 0 to CallStubIdx - 1 do     begin       P^ := CallStub[J];       IncPtr(P);     end; Thunk的指针,指向汇编代码相应的调用信息:       { And then fill the remainder with INT 3 instructions for             }     { cleanliness and safety. If we do the allocated more smartly, we    }     { can remove all the wasted space, except for maybe alignment.        }     for J := CallStubIdx to StubSize - 1 do     begin       P^ := $CC;       IncPtr(P);     end; 增加Thunk指向存根相应调用信息:       { Finally, put the new thunk entry into the vtable slot. }     VTable^ := Thunk; IncPtr(VTable, 4); 把thunk指针赋给vtable之后,压入堆栈。 IncPtr(Thunk, StubSize); 把存根相应调用信息压入堆栈。   然后继续下一个函数的相应操作。  end; end;   procedure IncPtr(var P; I: Integer = 1); asm         ADD     [EAX], EDX end;   总结一下GenVTable函数,这个函数,根据注册的接口,生成了内存表格。 首先遍历整个动态数组,然后,得到接口的RTTL信息,随后把Tcracker结构内存入相应的调用信息。然后再分配两块内存,一块放接口信息,一块放存根调用信息,再把接口内存的指针赋给TRIO的IntfTableP变量。IntfStubs存放存根指针IntfTable指接口信息后,又加入了Iunknown的指针空间。最近遍历接口函数,把函数信息写入CallStub数组之后(生成机器代码),再填入堆栈之中。 继续: THTTPRIO.QueryInterface TInvokableClassRegistry.GetActionURIOfInfo if InvRegistry.GetUDDIInfo(IID, UDDIOperator, UDDIBindingKey) then 调用之后: function TInvokableClassRegistry.GetUDDIInfo(const IntfInfo: PTypeInfo; var Operator, BindingKey: string): Boolean;   返回 procedure _IntfCast(var Dest: IInterface; const Source: IInterface; const IID: TGUID); 这里,继续: procedure TRIO.GenericStub; JMP      TRIO.Generic       //这里是最重要的地方:这个函数完成了。打包,传递,并返回服务器端结果。我们仔细研究一下。   function TRIO.Generic(CallID: Integer; Params: Pointer): Int64; 。。。。 MethMD := IntfMD.MDA[CallID]; //得到方法相应的属性。 FContext.SetMethodInfo(MethMD); // FContext 产生虚拟的表函数表格。   procedure TInvContext.SetMethodInfo(const MD: TIntfMethEntry); begin  SetLength(DataP, MD.ParamCount + 1);  SetLength(Data, (MD.ParamCount + 1) * MAXINLINESIZE); end;   if MethMd.CC <> ccSafeCall then  begin     if RetOnStack(MethMD.ResultInfo) then     begin       RetP := Pointer(PInteger(P)^);       if MethMD.ResultInfo.Kind = tkVariant then         IncPtr(P, sizeof(Pointer))       else         IncPtr(P, GetStackTypeSize(MethMD.ResultInfo, MethMD.CC));       if MethMD.CC in [ccCdecl, ccStdCall] then       begin         IncPtr(P, sizeof(Pointer));   { Step over self }       end;     end else       RetP := @Result;     FContext.SetResultPointer(RetP);  end; //把相应的返回信息压入Fcontext中。   for J := 0 to MethMD.ParamCount - 1 do  begin     FContext.SetParamPointer(ParamIdx, P);     with MethMD.Params[J] do     begin       if (Info.Kind = tkVariant) and          (MethMD.CC in [ccCdecl, ccStdCall, ccSafeCall]) and          not (pfVar in Flags) and          not (pfOut in Flags) then       begin         IncPtr(P, sizeof(TVarData)); { NOTE: better would be to dword-align!! }       end       else if IsParamByRef(Flags, Info, MethMD.CC) then         IncPtr(P, 4)       else         IncPtr(P, GetStackTypeSize(Info, MethMD.CC));     end;     Inc(ParamIdx, LeftRightOrder);  end; //把相应的参数压入Fcontext中。 //转换成XML封包,并写入流中,这里就是具体打包的地方: 大家看清楚了: Req := FConverter.InvContextToMsg(IntfMD, MethNum, FContext, FHeadersOutBound); 现在来好好研究一下它是怎么转换成XML封包的。 function TOPToSoapDomConvert.InvContextToMsg(const IntfMD: TIntfMetaData; MethNum: Integer;                                              Con: TInvContext; Headers: THeaderList): TStream;   MethMD := IntfMD.MDA[MethNum]; 首先得到方法的动态信息。 XMLDoc := NewXMLDocument; 看看这句:   function TOPToSoapDomConvert.NewXMLDocument: IXMLDocument; begin  Result := XMLDoc.NewXMLDocument;  Result.Options := Result.Options + [doNodeAutoIndent];  Result.ParseOptions := Result.ParseOptions + [poPreserveWhiteSpace]; end;   function NewXMLDocument(Version: DOMString = '1.0'): IXMLDocument; begin  Result := TXMLDocument.Create(nil);  Result.Active := True;  if Version <> '' then     Result.Version := Version; end; 创建了一个TXMLDocument对象用于读写XML。   procedure TXMLDocument.SetActive(const Value: Boolean); begin  。。。。       CheckDOM;       FDOMDocument := DOMImplementation.createDocument('', '', nil);       try         LoadData;       except         ReleaseDoc(False);         raise;       end;       DoAfterOpen;     end     else     begin       DoBeforeClose;       ReleaseDoc;       DoAfterClose;     end;  end; end;   procedure TXMLDocument.CheckDOM; begin  if not Assigned(FDOMImplementation) then     if Assigned(FDOMVendor) then       FDOMImplementation := FDOMVendor.DOMImplementation     else       FDOMImplementation := GetDOM(DefaultDOMVendor); end; 在TXMLDocument内部使用了Abstract Factory模式 Abstract Factory 希望不用指定具体的类,但为了找到它们,在TXMLDocument是通过指定一个字符串,也就是我们点击DOMVendor时出现的哪几个字符串.   GetDOM函数如下: Result := GetDOMVendor(VendorDesc).DOMImplementation;   //根据传递进去的名字,创建相应在的实例: function GetDOMVendor(VendorDesc: string): TDOMVendor; begin  if VendorDesc = '' then     VendorDesc := DefaultDOMVendor;  if (VendorDesc = '') and (DOMVendorList.Count > 0) then     Result := DOMVendorList[0]  else     Result := DOMVendorList.Find(VendorDesc);  if not Assigned(Result) then     raise Exception.CreateFmt(SNoMatchingDOMVendor, [VendorDesc]); end;   最后取得一个IDOMImplementation,它有一个createDocument(….):IDOMDocument;函数,这个函数将返回一个IDOMDocument;接口让IXMLDoucment使用。 //由此可见,默认状态下是创建DOM,微软的XML解析器。 function DOMVendorList: TDOMVendorList; begin  if not Assigned(DOMVendors) then     DOMVendors := TDOMVendorList.Create;  Result := DOMVendors; end; function TDOMVendorList.GetVendors(Index: Integer): TDOMVendor; begin  Result := FVendors[Index]; end; 如果为空,就返回默认的。 function TMSDOMImplementationFactory.DOMImplementation: IDOMImplementation; begin  Result := TMSDOMImplementation.Create(nil); end;   再返回到函数: procedure TXMLDocument.SetActive(const Value: Boolean);    FDOMDocument := DOMImplementation.createDocument('', '', nil); 继续: function TMSDOMImplementation.createDocument(const namespaceURI,  qualifiedName: DOMString; doctype: IDOMDocumentType): IDOMDocument; begin  Result := TMSDOMDocument.Create(MSXMLDOMDocumentCreate); end;   在如果使用MSXML,接口对应的是TMSDOMDocument,TMSDOMDocument是实际上是调用MSXML技术,下面是调用MS COM的代码   function CreateDOMDocument: IXMLDOMDocument; begin  Result := TryObjectCreate([CLASS_DOMDocument40, CLASS_DOMDocument30,     CLASS_DOMDocument26, msxml.CLASS_DOMDocument]) as IXMLDOMDocument;  if not Assigned(Result) then     raise DOMException.Create(SMSDOMNotInstalled); end;   再返回到函数: procedure TXMLDocument.SetActive(const Value: Boolean); .. LoadData   //因为是新建的TXMLDocument,所以装内空数据,立即返回。 procedure TXMLDocument.LoadData; const  UnicodeEncodings: array[0..2] of string = ('UTF-16', 'UCS-2', 'UNICODE'); var  Status: Boolean;  ParseError: IDOMParseError;  StringStream: TStringStream;  Msg: string; begin  … Status := True; { No load, just create empty doc. } 创建空的文档:    if not Status then  begin     DocSource := xdsNone;     ParseError := DOMDocument as IDOMParseError;     with ParseError do       Msg := Format('%s%s%s: %d%s%s', [Reason, SLineBreak, SLine,         Line, SLineBreak, Copy(SrcText, 1, 40)]);     raise EDOMParseError.Create(ParseError, Msg);  end;  SetModified(False); end; 设置不能修改。因为空文档。   继续返回到 function NewXMLDocument(Version: DOMString = '1.0'): IXMLDocument; begin  if Version <> '' then     Result.Version := Version; end; procedure TXMLDocument.SetVersion(const Value: DOMString); begin  SetPrologValue(Value, xpVersion); end; procedure TXMLDocument.SetPrologValue(const Value: Variant; ….     PrologNode := GetPrologNode;     PrologAttrs := InternalSetPrologValue(PrologNode, Value, PrologItem);     NewPrologNode := CreateNode('xml', ntProcessingInstr, PrologAttrs);     if Assigned(PrologNode) then       Node.ChildNodes.ReplaceNode(PrologNode, NewPrologNode)     else       ChildNodes.Insert(0, NewPrologNode);  end;     NewPrologNode := CreateNode('xml', ntProcessingInstr, PrologAttrs); 这句调用了: function TXMLDocument.CreateNode(const NameOrData: DOMString;  NodeType: TNodeType = ntElement; const AddlData: DOMString = ''): IXMLNode; begin  Result := TXMLNode.Create(CreateDOMNode(FDOMDocument, NameOrData,     NodeType, AddlData), nil, Self); end;     在返回到这个函数中: function TOPToSoapDomConvert.InvContextToMsg(const IntfMD: TIntfMetaData; MethNum: Integer;                                              Con: TInvContext; Headers: THeaderList): TStream; BodyNode := Envelope.MakeBody(EnvNode);   if not (soLiteralParams in Options) then  begin     SoapMethNS := GetSoapNS(IntfMD);     ExtMethName := InvRegistry.GetMethExternalName(IntfMD.Info, MethMD.Name);   ;;;;;   //创建一个SOAP的body: function TSoapEnvelope.MakeBody(ParentNode: IXMLNode): IXMLNode; begin    Result := ParentNode.AddChild(SSoapNameSpacePre + ':' + SSoapBody, SSoapNameSpace); end;     SoapMethNS := GetSoapNS(IntfMD); 返回:'urn:MyFirstWSIntf-IMyFirstWS' ExtMethName := InvRegistry.GetMethExternalName(IntfMD.Info, MethMD.Name); 得到调用方法名。剩下的部分就是把参数打包。生成SOAP的源文件。然后写到内存流中。     再回到函数中:InvContextToMsg  Result := TMemoryStream.Create();  DOMToStream(XMLDoc, Result); 把内存块的数据,转化成XML。 具体的函数如下: procedure TOPToSoapDomConvert.DOMToStream(const XMLDoc: IXMLDocument; Stream: TStream); var  XMLWString: WideString;  StrStr: TStringStream; begin      if (FEncoding = '') or (soUTF8EncodeXML in Options) then  begin     XMLDoc.SaveToXML(XMLWString);     StrStr := TStringStream.Create(UTF8Encode(XMLWString));     try       Stream.CopyFrom(StrStr, 0);     finally       StrStr.Free;     end;  end else     XMLDoc.SaveToStream(Stream); end; 我们跟踪之后StrStr的结果如下:   '<?xml version="1.0"?>'#$D#$A'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">'#$D#$A' <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'#$D#$A'     <NS1:GetObj xmlns:NS1="urn:MyFirstWSIntf-IMyFirstWS">'#$D#$A'      <a xsi:type="xsd:int">3</a>'#$D#$A'      <b xsi:type="xsd:int">4</b>'#$D#$A'    </NS1:GetObj>'#$D#$A' </SOAP-ENV:Body>'#$D#$A'</SOAP-ENV:Envelope>'#$D#$A     转化后继续调用Generic函数: 。。。。 FWebNode.BeforeExecute(IntfMD, MethMD, MethNum-3, nil);   if (BindingType = btMIME) then begin 。。。 FWebNode.BeforeExecute(IntfMD, MethMD, MethNum-3, nil);   THTTPReqResp.BeforeExecute 。。。。。 MethName := InvRegistry.GetMethExternalName(IntfMD.Info, MethMD.Name); FSoapAction := InvRegistry.GetActionURIOfInfo(IntfMD.Info, MethName, MethodIndex); 得到方法名和FsoapAction FBindingType := btSOAP   DoBeforeExecute // TRIO. if Assigned(FOnBeforeExecute) then 退出:   继续: Resp := GetResponseStream(RespBindingType);       继续返回到TRIO.Generic函数中执行: try    FWebNode.Execute(Req, Resp); 比较重要的部分:   这个函数就是THTTPReqResp向IIS发出请求。并返回信息:   procedure THTTPReqResp.Execute(const Request: TStream; Response: TStream); begin  …     Context := Send(Request);     try       try         Receive(Context, Response);         Exit;       except         on Ex: ESOAPHTTPException do         begin           Connect(False);           if not CanRetry or not IsErrorStatusCode(Ex.StatusCode) then             raise;           { Trigger UDDI Lookup }           LookUpUDDI := True;           PrevError := Ex.Message;         end;         else         begin           Connect(False);           raise;         end;       end;     finally       if Context <> 0 then         InternetCloseHandle(Pointer(Context));     end;  end; {$ENDIF} end;   现在看看Send函数,看看到底如何发送数据给WEB服务器的。 function THTTPReqResp.Send(const ASrc: TStream): Integer; var  Request: HINTERNET;  RetVal, Flags: DWord;  P: Pointer;  ActionHeader: string;  ContentHeader: string;  BuffSize, Len: Integer;  INBuffer: INTERNET_BUFFERS;  Buffer: TMemoryStream;  StrStr: TStringStream; begin  { Connect }  Connect(True);    Flags := INTERNET_FLAG_KEEP_CONNECTION or INTERNET_FLAG_NO_CACHE_WRITE;  if FURLScheme = INTERNET_SCHEME_HTTPS then  begin     Flags := Flags or INTERNET_FLAG_SECURE;     if (soIgnoreInvalidCerts in InvokeOptions) then       Flags := Flags or (INTERNET_FLAG_IGNORE_CERT_CN_INVALID or                          INTERNET_FLAG_IGNORE_CERT_DATE_INVALID);  end;    Request := nil;  try      Request := HttpOpenRequest(FInetConnect, 'POST', PChar(FURLSite), nil,                                nil, nil, Flags, 0{Integer(Self)});     Check(not Assigned(Request));       { Timeouts }     if FConnectTimeout > 0 then       Check(InternetSetOption(Request, INTERNET_OPTION_CONNECT_TIMEOUT, Pointer(@FConnectTimeout), SizeOf(FConnectTimeout)));     if FSendTimeout > 0 then       Check(InternetSetOption(Request, INTERNET_OPTION_SEND_TIMEOUT, Pointer(@FSendTimeout), SizeOf(FSendTimeout)));     if FReceiveTimeout > 0 then       Check(InternetSetOption(Request, INTERNET_OPTION_RECEIVE_TIMEOUT, Pointer(@FReceiveTimeout), SizeOf(FReceiveTimeout)));       { Setup packet based on Content-Type/Binding }     if FBindingType = btMIME then     begin       ContentHeader := Format(ContentHeaderMIME, [FMimeBoundary]);       ContentHeader := Format(ContentTypeTemplate, [ContentHeader]);       HttpAddRequestHeaders(Request, PChar(MIMEVersion), Length(MIMEVersion), HTTP_ADDREQ_FLAG_ADD);         { SOAPAction header }       { NOTE: It's not really clear whether this should be sent in the case               of MIME Binding. Investigate interoperability ?? }       if not (soNoSOAPActionHeader in FInvokeOptions) then       begin         ActionHeader:= GetSOAPActionHeader;         HttpAddRequestHeaders(Request, PChar(ActionHeader), Length(ActionHeader), HTTP_ADDREQ_FLAG_ADD);       end;       end else { Assume btSOAP }     begin       { SOAPAction header }       if not (soNoSOAPActionHeader in FInvokeOptions) then       begin         ActionHeader:= GetSOAPActionHeader;         HttpAddRequestHeaders(Request, PChar(ActionHeader), Length(ActionHeader), HTTP_ADDREQ_FLAG_ADD);       end;         if UseUTF8InHeader then         ContentHeader := Format(ContentTypeTemplate, [ContentTypeUTF8])       else         ContentHeader := Format(ContentTypeTemplate, [ContentTypeNoUTF8]);     end;       { Content-Type }     HttpAddRequestHeaders(Request, PChar(ContentHeader), Length(ContentHeader), HTTP_ADDREQ_FLAG_ADD);       { Before we pump data, see if user wants to handle something - like set Basic-Auth data?? }     if Assigned(FOnBeforePost) then       FOnBeforePost(Self, Request);       ASrc.Position := 0;     BuffSize := ASrc.Size;     if BuffSize > FMaxSinglePostSize then     begin       Buffer := TMemoryStream.Create;       try         Buffer.SetSize(FMaxSinglePostSize);           { Init Input Buffer }         INBuffer.dwStructSize := SizeOf(INBuffer);         INBuffer.Next := nil;         INBuffer.lpcszHeader := nil;         INBuffer.dwHeadersLength := 0;         INBuffer.dwHeadersTotal := 0;         INBuffer.lpvBuffer := nil;         INBuffer.dwBufferLength := 0;         INBuffer.dwBufferTotal := BuffSize;         INBuffer.dwOffsetLow := 0;         INBuffer.dwOffsetHigh := 0;           { Start POST }         Check(not HttpSendRequestEx(Request, @INBuffer, nil,                                     HSR_INITIATE or HSR_SYNC, 0));         try           while True do           begin             { Calc length of data to send }             Len := BuffSize - ASrc.Position;             if Len > FMaxSinglePostSize then               Len := FMaxSinglePostSize;             { Bail out if zip.. }             if Len = 0 then               break;             { Read data in buffer and write out}             Len := ASrc.Read(Buffer.Memory^, Len);             if Len = 0 then               raise ESOAPHTTPException.Create(SInvalidHTTPRequest);               Check(not InternetWriteFile(Request, @Buffer.Memory^, Len, RetVal));               RetVal := InternetErrorDlg(GetDesktopWindow(), Request, GetLastError,               FLAGS_ERROR_UI_FILTER_FOR_ERRORS or FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS or               FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, P);             case RetVal of               ERROR_SUCCESS: ;               ERROR_CANCELLED: SysUtils.Abort;               ERROR_INTERNET_FORCE_RETRY: {Retry the operation};             end;               { Posting Data Event }             if Assigned(FOnPostingData) then               FOnPostingData(ASrc.Position, BuffSize);           end;         finally           Check(not HttpEndRequest(Request, nil, 0, 0));         end;       finally         Buffer.Free;       end;     end else     begin       StrStr := TStringStream.Create('');       try         StrStr.CopyFrom(ASrc, 0);         while True do         begin           Check(not HttpSendRequest(Request, nil, 0, @StrStr.DataString[1], Length(StrStr.DataString)));           RetVal := InternetErrorDlg(GetDesktopWindow(), Request, GetLastError,             FLAGS_ERROR_UI_FILTER_FOR_ERRORS or FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS or             FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, P);           case RetVal of             ERROR_SUCCESS: break;             ERROR_CANCELLED: SysUtils.Abort;             ERROR_INTERNET_FORCE_RETRY: {Retry the operation};           end;         end;       finally         StrStr.Free;       end;     end;  except     if (Request <> nil) then       InternetCloseHandle(Request);     Connect(False);     raise;  end;  Result := Integer(Request); end;       function THTTPReqResp.Send(const ASrc: TStream): Integer; 先调用了: procedure THTTPReqResp.Connect(Value: Boolean); …… if InternetAttemptConnect(0) <> ERROR_SUCCESS then       SysUtils.Abort; 这个函数可以说非常 简单,只是尝试计算机连接到网络。   FInetRoot := InternetOpen(PChar(FAgent), AccessType, PChar(FProxy), PChar(FProxyByPass), 0); 创建HINTERNET句柄,并初始化WinInet的API函数:   Check(not Assigned(FInetRoot));     try       FInetConnect := InternetConnect(FInetRoot, PChar(FURLHost), FURLPort, PChar(FUserName),         PChar(FPassword), INTERNET_SERVICE_HTTP, 0, Cardinal(Self));     //创建一个特定的会话:       Check(not Assigned(FInetConnect));       FConnected := True;     except       InternetCloseHandle(FInetRoot);       FInetRoot := nil;       raise;     end; 这里已经创建了一个会话: 继续返回function THTTPReqResp.Send(const ASrc: TStream): Integer;函数之中: 。。。。 Request := HttpOpenRequest(FInetConnect, 'POST', PChar(FURLSite), nil,                                nil, nil, Flags, 0{Integer(Self)}); Check(not Assigned(Request));。 打开一个HTTP的请求。向WEB服务器提出请求: 。。 if not (soNoSOAPActionHeader in FInvokeOptions) then       begin         ActionHeader:= GetSOAPActionHeader;         HttpAddRequestHeaders(Request, PChar(ActionHeader), Length(ActionHeader), HTTP_ADDREQ_FLAG_ADD); end; 。。。 为请求添加一个或多个标头。可以看到标点的信息为: 'SOAPAction: "urn:MyFirstWSIntf-IMyFirstWS#GetObj"'     HttpAddRequestHeaders(Request, PChar(ContentHeader), Length(ContentHeader), HTTP_ADDREQ_FLAG_ADD); 继续加入标头'Content-Type: text/xml'信息:         StrStr := TStringStream.Create('');       try         StrStr.CopyFrom(ASrc, 0);         while True do         begin           Check(not HttpSendRequest(Request, nil, 0, @StrStr.DataString[1], Length(StrStr.DataString))); 建立到internet 的连接,并将请求发送到指定的站点。 这句执行完后的图如下(用工具跟踪的结果):   看看前面的soap生成的字符 StrStr的结果如下,发现后半部分是一样的。   继续 function THTTPReqResp.Execute(const Request: TStream): TStream; Receive(Context, Response);     procedure THTTPReqResp.Receive(Context: Integer; Resp: TStream; IsGet: Boolean); var  Size, Downloaded, Status, Len, Index: DWord;  S: string; begin    .. //获取请求信息:  HttpQueryInfo(Pointer(Context), HTTP_QUERY_CONTENT_TYPE, @FContentType[1], Size, Index);    repeat     Check(not InternetQueryDataAvailable(Pointer(Context), Size, 0, 0));     if Size > 0 then     begin       SetLength(S, Size); Check(not InternetReadFile(Pointer(Context), @S[1], Size, Downloaded)); //下载数据:       Resp.Write(S[1], Size);         { Receiving Data event }       if Assigned(FOnReceivingData) then         FOnReceivingData(Size, Downloaded)     end;  until Size = 0;   S的结果如下和刚才跟踪器里的是一模一样的: '<?xml version="1.0"?>'#$D#$A'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">'#$D#$A' <SOAP-ENV:Body SOAP-ENC:encodingStyle="http://schemas.xmlsoap.org/soap/envelope/">'#$D#$A'     <NS1:GetObjResponse xmlns:NS1="urn:MyFirstWSIntf-IMyFirstWS">'#$D#$A'      <return xsi:type="xsd:string">12</return>'#$D#$A'    </NS1:GetObjResponse>'#$D#$A' </SOAP-ENV:Body>'#$D#$A'</SOAP-ENV:Envelope>'#$D#$A   最后关闭HTTP会话句柄:  InternetCloseHandle(Pointer(Context));   在返回function TRIO.Generic(CallID: Integer; Params: Pointer): Int64;函数中继续查看:   RespXML := Resp; 返回信息的内存流 FConverter.ProcessResponse(RespXML, IntfMD, MethMD, FContext, FHeadersInbound);   再次把SOAP封包转换成PASCEL调用: procedure TOPToSoapDomConvert.ProcessResponse(const Resp: TStream;                                               const IntfMD: TIntfMetaData;                                               const MD: TIntfMethEntry;                                               Context: TInvContext;                                               Headers: THeaderList); var  XMLDoc: IXMLDocument; begin  XMLDoc := NewXMLDocument;  XMLDoc.Encoding := FEncoding;  Resp.Position := 0;  XMLDoc.LoadFromStream(Resp);  ProcessResponse(XMLDoc, IntfMD, MD, Context, Headers); end;   procedure TOPToSoapDomConvert.ProcessResponse(const XMLDoc: IXMLDocument;                                               const IntfMD: TIntfMetaData;                                               const MD: TIntfMethEntry;                                               Context: TInvContext;                                               Headers: THeaderList); var  ProcessSuccess(RespNode, IntfMD, MD, Context);   ProcessSuccess函数如下: ….  for I := 0 to RespNode.childNodes.Count - 1 do     begin       Node := RespNode.childNodes[I];       { Skip non-valid nodes }       if Node.NodeType <> ntElement then         continue;     // 处理返回值:       if I = RetIndex then       begin         InvData := InvContext.GetResultPointer;         ByRef := IsParamByRef([pfOut], MD.ResultInfo, MD.CC);         ConvertSoapToNativeData(InvData, MD.ResultInfo, InvContext, RespNode, Node, True, ByRef, 1);     ConvertSoapToNativeData(InvData, MD.ResultInfo, InvContext, RespNode, Node, True, ByRef, 1); 把SOAP的结果,写入返回区地址空间。       procedure TSOAPDomConv.ConvertSoapToNativeData(DataP: Pointer; TypeInfo: PTypeInfo;  Context: TDataContext; RootNode, Node: IXMLNode; Translate, ByRef: Boolean;  NumIndirect: Integer); var  TypeUri, TypeName: InvString;  IsNull: Boolean;  Obj: TObject;  P: Pointer;  I: Integer;  ID: InvString; begin  Node := GetDataNode(RootNode, Node, ID);  IsNull := NodeIsNull(Node);  if TypeInfo.Kind = tkVariant then  begin     if NumIndirect > 1 then       DataP := Pointer(PInteger(DataP)^);     if IsNull then     begin       Variant(PVarData(DataP)^) := NULL;     end else       ConvertSoapToVariant(Node, DataP);  end else  if TypeInfo.Kind = tkDynArray then  begin     P := DataP;     for I := 0 to NumIndirect - 2 do       P := Pointer(PInteger(P)^);     P := ConvertSoapToNativeArray(P, TypeInfo, RootNode, Node);     if NumIndirect = 1 then       PInteger(DataP)^ := Integer(P)     else if NumIndirect = 2 then     begin       DataP := Pointer(PInteger(DataP)^);       PInteger(DataP)^ := Integer(P);     end;  end else  if TypeInfo.Kind = tkClass then  begin     Obj := ConvertSOAPToObject(RootNode, Node, GetTypeData(TypeInfo).ClassType, TypeURI, TypeName, DataP, NumIndirect);     if NumIndirect = 1 then       PTObject(DataP)^ := Obj     else if NumIndirect = 2 then     begin       DataP := Pointer(PInteger(DataP)^);       PTObject(DataP)^ := Obj;     end;  end else  begin     if Translate then     begin       if NumIndirect > 1 then         DataP := Pointer(PInteger(DataP)^);       if not TypeTranslator.CastSoapToNative(TypeInfo, GetNodeAsText(Node), DataP, IsNull) then         raise ESOAPDomConvertError.CreateFmt(STypeMismatchInParam, [node.nodeName]);     end;  end; end;   作为整型数据,处理方式为: if not TypeTranslator.CastSoapToNative(TypeInfo, GetNodeAsText(Node), DataP, IsNull) then   function TTypeTranslator.CastSoapToNative(Info: PTypeInfo; const SoapData: WideString; NatData: Pointer; IsNull: Boolean): Boolean; var  ParamTypeData: PTypeData; begin  DecimalSeparator := '.';  Result := True;  if IsNull and (Info.Kind = tkVariant) then  begin     Variant(PVarData(NatData)^) := NULL;     Exit;  end;  ParamTypeData := GetTypeData(Info);  case Info^.Kind of     tkInteger:       case ParamTypeData^.OrdType of         otSByte,         otUByte:           PByte(NatData)^ := StrToInt(Trim(SoapData));         otSWord,         otUWord:           PSmallInt(NatData)^ := StrToInt(Trim(SoapData));         otSLong,         otULong:           PInteger(NatData)^ := StrToInt(Trim(SoapData));       end;     tkFloat:       case ParamTypeData^.FloatType of         ftSingle:           PSingle(NatData)^ := StrToFloatEx(Trim(SoapData));         ftDouble:         begin           if Info = TypeInfo(TDateTime) then             PDateTime(NatData)^ := XMLTimeToDateTime(Trim(SoapData))           else             PDouble(NatData)^ := StrToFloatEx(Trim(SoapData));         end;           ftComp:           PComp(NatData)^ := StrToFloatEx(Trim(SoapData));         ftCurr:           PCurrency(NatData)^ := StrToFloatEx(Trim(SoapData));         ftExtended:           PExtended(NatData)^ := StrToFloatEx(Trim(SoapData));       end;     tkWString:       PWideString(NatData)^ := SoapData;     tkString:       PShortString(NatData)^ := SoapData;     tkLString:       PString(NatData)^ := SoapData;     tkChar:       if SoapData <> '' then         PChar(NatData)^ := Char(SoapData[1]);     tkWChar:       if SoapData <> '' then         PWideChar(NatData)^ := WideChar(SoapData[1]);     tkInt64:       PInt64(NatData)^ := StrToInt64(Trim(SoapData));       tkEnumeration:       { NOTE: Here we assume enums to be byte-size; make sure (specially for C++)               that enums have generated with the proper size }       PByte(NatData)^ := GetEnumValueEx(Info, Trim(SoapData));     tkClass:       ;     tkSet,     tkMethod,       tkArray,     tkRecord,     tkInterface,       tkDynArray:       raise ETypeTransException.CreateFmt(SUnexpectedDataType, [ KindNameArray[Info.Kind]] );     tkVariant:       CastSoapToVariant(Info, SoapData, NatData);  end; end;   PWideString(NatData)^ := SoapData; 通过把值赋给了相应的指针地址:   另外在看一下传对象时的情况: Obj := ConvertSOAPToObject(RootNode, Node, GetTypeData(TypeInfo).ClassType, TypeURI, TypeName, DataP, NumIndirect);     if Assigned(Obj) and LegalRef then     begin       if (NodeClass <> nil) and (NodeClass <> Obj.ClassType) then         Obj := NodeClass.Create;     end else     begin     if (NodeClass <> nil) and NodeClass.InheritsFrom(AClass) then       Obj := TRemotableClass(NodeClass).Create     else       Obj := TRemotableClass(AClass).Create;     end; Result := Obj;   可以理解,经过双边注册过之后,才可以传递对象。   现在研究一下服务器端的代码: 先大概简单介绍一下WEB服务器应用程序的工作模式:  这里的WEB服务器就是IIS。   也就是说WEB服务器会把客户的HTTP请求消息,传递给CGI程序。然后由CGI进行处理:   CGIApp单元中的: procedure InitApplication; begin  Application := TCGIApplication.Create(nil); end; //创建一个CGI的应用程序   constructor TWebApplication.Create(AOwner: TComponent); begin  WebReq.WebRequestHandlerProc := WebRequestHandler;  inherited Create(AOwner);    Classes.ApplicationHandleException := HandleException;  if IsLibrary then  begin     IsMultiThread := True;     OldDllProc := DLLProc;     DLLProc := DLLExitProc;  end  else     AddExitProc(DoneVCLApplication); end;   constructor TWebRequestHandler.Create(AOwner: TComponent); begin  inherited Create(AOwner);  FCriticalSection := TCriticalSection.Create;  FActiveWebModules := TList.Create;  FInactiveWebModules := TList.Create;  FWebModuleFactories := TWebModuleFactoryList.Create;  FMaxConnections := 32;  FCacheConnections := True; end;   procedure TCGIApplication.Run; var  HTTPRequest: TCGIRequest;  HTTPResponse: TCGIResponse; begin  inherited Run;  if IsConsole then  begin     Rewrite(Output);     Reset(Input);  end;  try     HTTPRequest := NewRequest;     try       HTTPResponse := NewResponse(HTTPRequest);       try         HandleRequest(HTTPRequest, HTTPResponse);       finally         HTTPResponse.Free;       end;     finally       HTTPRequest.Free;     end;  except     HandleServerException(ExceptObject, FOutputFileName);  end; end; HTTPResponse := NewResponse(HTTPRequest); 调用: function TCGIApplication.GetFactory: TCGIFactory; begin  if not Assigned(FFactory) then     FFactory := TCGIFactory.Create;  Result := FFactory; end;     function TCGIFactory.NewRequest: TCGIRequest;     Result := TCGIRequest.Create    。。。 end; //创建TCGIRequest HTTPResponse := NewResponse(HTTPRequest); Result := TCGIResponse.Create(CGIRequest) HandleRequest(HTTPRequest, HTTPResponse);调用   现在看看是怎么响应客户端的:   function TWebRequestHandler.HandleRequest(Request: TWebRequest;  Response: TWebResponse): Boolean; var  I: Integer;  WebModules: TWebModuleList;  WebModule: TComponent;  WebAppServices: IWebAppServices;  GetWebAppServices: IGetWebAppServices; begin  Result := False;  WebModules := ActivateWebModules; 继续: function TWebRequestHandler.ActivateWebModules: TWebModuleList; begin ……………… FWebModuleFactories.AddFactory(TDefaultWebModuleFactory.Create(WebModuleClass)); 把TWebModule1加入工厂中,并创建TwebModuleList对象。           if FWebModuleFactories.ItemCount > 0 then         begin           Result := TWebModuleList.Create(FWebModuleFactories); ………………..   继续:  if Assigned(WebModules) then  try WebModules.AutoCreateModules;   procedure TWebModuleList.AutoCreateModules ….... AddModule(Factory.GetModule);   调用:TWebModule1.create并加入TwebModuleList中。 function TDefaultWebModuleFactory.GetModule: TComponent; begin  Result := FComponentClass.Create(nil); end;   constructor TWebModule.Create(AOwner: TComponent);调用 constructor TCustomWebDispatcher.Create(AOwner: TComponent);   之后又创建了THTTPSoapDispatcher,创建是在Treader类中创建的,有兴趣的朋友就追踪一下吧,这里实在是太麻烦。我也追了很久才发现。就懒得贴上来了。内容太多。 继续创建了TWSDLHTMLPublish   在回到TWebRequestHandler.HandleRequest函数中: 。。。 Result := WebAppServices.HandleRequest;   最后调用了: function TCustomWebDispatcher.HandleRequest(  Request: TWebRequest; Response: TWebResponse): Boolean; begin  FRequest := Request;  FResponse := Response;  Result := DispatchAction(Request, Response); end; 注意HandleRequest函数,这里是关键部分:   function TCustomWebDispatcher.DispatchAction(Request: TWebRequest;  Response: TWebResponse): Boolean; ………………… while not Result and (I < FDispatchList.Count) do  begin     if Supports(IInterface(FDispatchList.Items[I]), IWebDispatch, Dispatch) then     begin       Result := DispatchHandler(Self, Dispatch,         Request, Response, False);     end;     Inc(I);  end; 继续: function DispatchHandler(Sender: TObject; Dispatch: IWebDispatch; Request: TWebRequest; Response: TWebResponse;  DoDefault: Boolean): Boolean; begin  Result := False;  if (Dispatch.Enabled and ((Dispatch.MethodType = mtAny) or     (Dispatch.MethodType = Dispatch.MethodType)) and     Dispatch.Mask.Matches(Request.InternalPathInfo)) then  begin     Result := Dispatch.DispatchRequest(Sender, Request, Response);  end; end;     http调用在到达服务器后,WebModule父类TCustomWebDispatcher 会对其进行分析,抽取参数等信息。然后在TCustomWebDispatcher.HandleRequest 方法中调用TCustomWebDispatcher.DispatchAction方法,将调用 根据其path info重定向到相应的处理方法去。而DispatchAction方法将 Action重定向到FDispatchList字段中所有的实现了IWebDispatch接口的组件。 而THTTPSoapDispatcher正是实现了IWebDispatch,其将在 TCustomWebDispatcher.InitModule方法中被自动检测到并加入FDispatchList字段 具体如下: procedure TCustomWebDispatcher.InitModule(AModule: TComponent);var  I: Integer;  Component: TComponent;  DispatchIntf: IWebDispatch;begin  if AModule <> nil then    for I := 0 to AModule.ComponentCount - 1 do    begin      Component := AModule.Components[I];      if Supports(IInterface(Component), IWebDispatch, DispatchIntf) then        FDispatchList.Add(Component);    end;end;...  THTTPSoapDispatcher = class(THTTPSoapDispatchNode, IWebDispatch)  因此 Web Service 程序的 http 请求处理实际上是由 THTTPSoapDispatcher 进行的。     我们接着看看THTTPSoapDispatcher.DispatchRequest方法中对SOAP 协议的处理,关键代码如下 function THTTPSoapDispatcher.DispatchRequest(Sender: TObject;  Request: TWebRequest; Response: TWebResponse): Boolean; var …..  http信息被封装在TwebRequest里:我们来看是怎么进行分析的:   SoapAction := Request.GetFieldByName(SHTTPSoapAction); 首先得到SOAPAction信息, 这个SOAPAction大家应该比较熟悉了,前面讲过,这里主要是根据相应信息调用方法:() 具体的内容例如:urn:MyFirstWSIntf-IMyFirstWS ….           if SoapAction = '' then           SoapAction := Request.GetFieldByName('HTTP_' + UpperCase(SHTTPSoapAction)); { do not localize } CGI或者Apache的处理方式。如果不是SOAP请求,就默认HTTP请求。   记录请求的路径。 Path := Request.PathInfo; XMLStream := TMemoryStream.Create; //把客户端的请求流化。 ReqStream := TWebRequestStream.Create(Request); 创建一个响应的流信息,以例把结果返回客户端   RStream := TMemoryStream.Create; 创建返回信息的流。 try        FSoapDispatcher.DispatchSOAP(Path, SoapAction, XMLStream, RStream, BindingTypeIn); 这句是最重要的: 它把HTTP的调用方法,委托给THTTPSoapPascalInvoker.DispatchSOAP来处理。   FSoapDispatcher.DispatchSOAP(Path, SoapAction, XMLStream, RStream, BindingTypeIn);   IHTTPSoapDispatch = interface  ['{9E733EDC-7639-4DAF-96FF-BCF141F7D8F2}']     procedure DispatchSOAP(const Path, SoapAction: WideString; const Request: TStream;                            Response: TStream; var BindingType: TWebServiceBindingType);  end; 父类实现的接口: THTTPSoapDispatchNode = class(TComponent)  private     procedure SetSoapDispatcher(const Value: IHTTPSoapDispatch);  protected     FSoapDispatcher: IHTTPSoapDispatch;     procedure Notification(AComponent: TComponent; Operation: TOperation); override;  public     procedure DispatchSOAP(const Path, SoapAction: WideString; const Request: TStream;       Response: TStream); virtual;  published     property Dispatcher: IHTTPSoapDispatch read FSoapDispatcher write SetSoapDispatcher;  end;   也被THTTPSoapPascalInvoker实现。所以THTTPSoapDispatcher中的Dispatcher接口的实例其实是:THTTPSoapPascalInvoker   THTTPSoapPascalInvoker = class(TSoapPascalInvoker, IHTTPSoapDispatch)  public     procedure DispatchSOAP(const Path, SoapAction: WideString; const Request: TStream;                            Response: TStream; var BindingType: TWebServiceBindingType); virtual;  end;   FSoapDispatcher.DispatchSOAP(Path, SoapAction, XMLStream, RStream, BindingTypeIn); 相应于调用了:   procedure THTTPSoapPascalInvoker.DispatchSOAP(const Path, SoapAction: WideString; const Request: TStream;                                               Response: TStream; var BindingType: TWebServiceBindingType); var  IntfInfo: PTypeInfo;  PascalBind: IHTTPSOAPToPasBind;  InvClassType: TClass;  ActionMeth: String;   MD: TIntfMetaData;   if not PascalBind.BindToPascalByPath(Path, InvClassType, IntfInfo, ActionMeth) or (InvClassType = nil) then 调用: function THTTPSOAPToPasBind.BindToPascalByPath(Path: String;  var AClass: TClass; var IntfInfo: PTypeInfo; var AMeth: String): Boolean; begin  Result := InvRegistry.GetInfoForURI(Path, '', AClass, IntfInfo, AMeth); end; 由InvRegistry的注册信息,返回相应的类名,接口信息等信息。 这了这些准备信息,下步才是真正的调用。 Invoke(InvClassType, IntfInfo, ActionMeth, Request, Response, BindingType); 函数最后一句:调用了父类:这里是真正工作的地方: 这里了仔细认真研究一下:   procedure TSoapPascalInvoker.Invoke(AClass: TClass; IntfInfo: PTypeInfo; MethName: string; const Request: TStream;                                      Response: TStream; var BindingType: TWebServiceBindingType); var  Inv: TInterfaceInvoker;  Obj: TObject;  InvContext: TInvContext;  IntfMD: TIntfMetaData;  MethNum: Integer;  SOAPHeaders: ISOAPHeaders;  Handled: Boolean; begin  try   GetIntfMetaData(IntfInfo, IntfMD, True); 得到接口RTTL信息; InvContext := TInvContext.Create;    构造调用堆栈。    { Convert XML to Invoke Context }           FConverter.MsgToInvContext(Request, IntfMD, MethNum, InvContext, FHeadersIn); 这个函数请见前面的参考InvContextToMsg, 把TinvContext内容转化成XML封包。   这个函数是逆操作,把XML内容转化成Context。       try Obj := InvRegistry.GetInvokableObjectFromClass(AClass); 搜寻注册信息,创建实例:             if Obj = nil then raise Exception.CreateFmt(SNoClassRegistered, [IntfMD.Name]); …………….. Inv := TInterfaceInvoker.Create; Inv.Invoke(Obj, IntfMD, MethNum, InvContext); 真正调用的地方: 源代码为: 这段代码,就是根据对象,接口信息等,把CONtext的信息压入相应参数,应调用。 有时间再仔细研究。   procedure TInterfaceInvoker.Invoke(const Obj: TObject;       IntfMD: TIntfMetaData; const MethNum: Integer;       const Context: TInvContext); var  MethPos: Integer;  Unk: IUnknown;  IntfEntry: PInterfaceEntry;  IntfVTable: Pointer;  RetIsOnStack, RetIsInFPU, RetInAXDX: Boolean;  I: Integer;  RetP : Pointer;  MD : TIntfMethEntry;  DataP: Pointer;  Temp, Temp1: Integer;  RetEAX: Integer;  RetEDX: Integer;  TotalParamBytes: Integer;  ParamBytes: Integer; begin {$IFDEF LINUX}  try {$ENDIF}  TotalParamBytes := 0;  MD := IntfMD.MDA[MethNUm]; //得到方法的动态数组信息:  if not Obj.GetInterface(IntfMD.IID, Unk) then     raise Exception.CreateFmt(SNoInterfaceGUID,       [Obj.ClassName, GUIDToString(IntfMD.IID)]);  IntfEntry := Obj.GetInterfaceEntry(IntfMD.IID); //得到接口的动态数组信息  IntfVTable := IntfEntry.VTable; //指向VTB表的指针  MethPos := MD.Pos * 4; { Pos is absolute to whole VMT } //定位  if MD.ResultInfo <> nil then  begin     RetIsInFPU := RetInFPU(MD.ResultInfo);     RetIsOnStack := RetOnStack(MD.ResultInfo);     RetInAXDX := IsRetInAXDX(MD.ResultInfo);     RetP := Context.GetResultPointer;     //根据context  得到返回参数的地址。  end else  begin     RetIsOnStack := False;     RetIsInFPU := False;     RetInAXDX := False;  end;    if MD.CC in [ccCDecl, ccStdCall, ccSafeCall] then  begin     if (MD.ResultInfo <> nil) and (MD.CC = ccSafeCall) then       asm PUSH DWORD PTR [RetP] end;    //把函数返回参数压入堆栈中。     for I := MD.ParamCount - 1 downto 0 do   //遍历参数。     begin       DataP := Context.GetParamPointer(I);    //指向一个参数地址:       if IsParamByRef(MD.Params[I].Flags,MD.Params[I].Info, MD.CC) then {基本类型}       asm         PUSH DWORD PTR [DataP]       //压入堆栈。       end       else       begin         ParamBytes := GetStackTypeSize(MD.Params[I].Info, MD.CC);    {特殊类型}         PushStackParm(DataP, ParamBytes);         Inc(TotalParamBytes, ParamBytes);       end;     end;     asm PUSH DWORD PTR [Unk] end;         //压入Iunknown指针     if RetIsOnStack and (MD.CC <> ccSafeCall) then       asm PUSH DWORD PTR [RetP] end;  end  else if MD.CC = ccPascal then  begin     for I := 0 to MD.ParamCount - 1 do     begin       DataP := Context.GetParamPointer(I);       if IsParamByRef(MD.Params[I].Flags,MD.Params[I].Info, MD.CC) then       asm          PUSH DWORD PTR [DataP]       end       else       begin //         PushStackParm(DataP, GetStackTypeSize(MD.Params[I].Info, MD.CC));         ParamBytes := GetStackTypeSize(MD.Params[I].Info, MD.CC);         PushStackParm(DataP, ParamBytes);         Inc(TotalParamBytes, ParamBytes);       end;     end;     if RetIsOnStack then       asm PUSH DWORD PTR [RetP] end;     asm PUSH DWORD PTR [Unk] end;  end else      raise Exception.CreateFmt(SUnsupportedCC, [CallingConventionName[MD.CC]]);    if MD.CC <> ccSafeCall then  begin     asm       MOV DWORD PTR [Temp], EAX   //把EAX保存到临时变量中       MOV DWORD PTR [Temp1], ECX //把ECX保存到临时变量中         MOV EAX, MethPos     //函数定位的地方       MOV ECX, [IntfVtable]   //虚拟表的入口       MOV ECX, [ECX + EAX]   //真正调用的地址       CALL ECX       MOV DWORD PTR [RetEAX], EAX //把结果返回的信息保存在变量RetEAX(低位)       MOV DWORD PTR [RetEDX], EDX //把结果返回的信息保存在变量RetEDX(高位)       MOV EAX, DWORD PTR [Temp]    //恢复寄存器EAX       MOV ECX, DWORD PTR [Temp1]   //恢复寄存器ECX       end;  end else  begin     asm       MOV DWORD PTR [Temp], EAX       MOV DWORD PTR [Temp1], ECX       MOV EAX, MethPos       MOV ECX, [IntfVtable]       MOV ECX, [ECX + EAX]       CALL ECX       CALL System.@CheckAutoResult       MOV DWORD PTR [RetEAX], EAX       MOV DWORD PTR [RetEDX], EDX       MOV EAX, DWORD PTR [Temp]       MOV ECX, DWORD PTR [Temp1]     end;  end;    if MD.CC = ccCDecl then /如果是CCDECL方式,必须自己清除使用的堆栈。  asm     MOV EAX, DWORD PTR [TotalParamBytes]     ADD ESP, EAX  end;   //调用后,返回参数的处理:  if MD.ResultInfo <> nil then   begin     if MD.CC <> ccSafeCall then //返回类型不为ccSafeCall时,必须进行处理。     begin       if RetIsInFPU then //tkFloat类型:       begin         GetFloatReturn(RetP, GetTypeData(MD.ResultInfo).FloatType);       end else if not RetIsOnStack then        begin         if RetInAXDX then //tkInt64整型64位类型处理:         asm             PUSH EAX             PUSH ECX             MOV EAX, DWORD PTR [RetP]             MOV ECX, DWORD PTR [RetEAX]             MOV [EAX], ECX             MOV ECX, DWORD PTR [RetEDX]             MOV [EAX + 4], ECX             POP ECX             POP EAX         end         else         asm                     //堆栈类型:             PUSH EAX                      //EAX入栈             PUSH ECX                      //ECX入栈             MOV EAX, DWORD PTR [RetP]    //返回地址MOV到EAX             MOV ECX, DWORD PTR [RetEAX] // RetEAX中是调用后得到的值             MOV [EAX], ECX        //把调用后的结果写入返回的地址中              POP ECX                        //ECX出栈             POP EAX                        //EAX出栈 (先入后出)           end;       end;     end;  end; {$IFDEF LINUX}  except     // This little bit of code is required to reset the stack back to a more     // resonable state since the exception unwinder is completely unaware of     // the stack pointer adjustments made in this function.     asm       MOV EAX, DWORD PTR [TotalParamBytes]       ADD ESP, EAX     end;     raise;  end; {$ENDIF} end; 

    最新回复(0)