WSDL 和服务合约

Wsutil.exe 实用工具会根据提供的 WSDL 元数据以及用户创作的 XML 架构描述的数据类型定义和说明生成 C 语言存根。

下面是一个示例 WSDL 文档和 XML 架构,作为后面讨论的基础:

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:types>
  <xs:schema xmlns:tns="http://Example.org" elementFormDefault="qualified" 
  targetNamespace="http://Example.org" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:element name="SimpleMethod">
    <xs:complexType>
     <xs:sequence>
      <xs:element name="a" type="xs:int" />
      <xs:element name="b" type="xs:int" />
     </xs:sequence>
    </xs:complexType>
   </xs:element>
   <xs:element name="SimpleMethodResponse">
    <xs:complexType>
     <xs:sequence>
      <xs:element name="b" type="xs:int" />
      <xs:element name="c" type="xs:int" />
     </xs:sequence>
    </xs:complexType>
   </xs:element>
  </xs:schema>
 </wsdl:types>
 <wsdl:message name="ISimpleService_SimpleMethod_InputMessage">
  <wsdl:part name="parameters" element="tns:SimpleMethod" />
 </wsdl:message>
 <wsdl:message name="ISimpleService_SimpleMethod_OutputMessage">
  <wsdl:part name="parameters" element="tns:SimpleMethodResponse" />
 </wsdl:message>
 <wsdl:portType name="ISimpleService">
  <wsdl:operation name="SimpleMethod">
   <wsdl:input wsaw:Action="http://Example.org/ISimpleService/SimpleMethod" 
   message="tns:ISimpleService_SimpleMethod_InputMessage" />
   <wsdl:output wsaw:Action="http://Example.org/ISimpleService/SimpleMethodResponse" 
   message="tns:ISimpleService_SimpleMethod_OutputMessage" />
  </wsdl:operation>
 </wsdl:portType>
 <wsdl:binding name="DefaultBinding_ISimpleService" type="tns:ISimpleService">
  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
  <wsdl:operation name="SimpleMethod">
   <soap:operation soapAction="http://Example.org/ISimpleService/SimpleMethod" 
   style="document" />
   <wsdl:input>
    <soap:body use="literal" />
   </wsdl:input>
   <wsdl:output>
    <soap:body use="literal" />
   </wsdl:output>
  </wsdl:operation>
 </wsdl:binding>
 <wsdl:service name="SimpleService">
  <wsdl:port name="ISimpleService" binding="tns:DefaultBinding_ISimpleService">
   <soap:address location="http://Example.org/ISimpleService" />
  </wsdl:port>
 </wsdl:service>
</wsdl:definitions>

此示例使用单个方法 SimpleMethod 提供合约 ISimpleService。 “SimpleMethod”包含类型整数的两个输入参数(ab),这些参数从客户端发送到服务。 同样的,SimpleMethod 包含类型整数的两个输出参数(bc),这些参数在成功完成后会返回到客户端。 在 SAL 批注的 C 语法中,方法定义如下所示:

void SimpleMethod(__in int a, __inout int *  b, __out int * c );

在此定义中,ISimpleService 是包含单个服务操作的服务合约:SimpleMethod。

输出标头文件包含外部引用的定义和说明。 这包括:

  • 全局元素类型的 C 结构定义。
  • 当前文件中定义的操作原型。
  • WSDL 文件中指定的合约的函数表原型。
  • 当前文件中指定的所有函数的客户端代理和服务存根原型。
  • 当前文件中定义的全局架构元素的 WS_ELEMENT_DESCRIPTION 数据结构。
  • 当前文件中指定的所有消息的 WS_MESSAGE_DESCRIPTION 数据结构。
  • 当前文件中指定的所有合约的 WS_CONTRACT_DESCRIPTION 数据结构。

生成一个全局结构,以封装应用程序可以引用的架构类型和服务模型类型的所有全局说明。 该结构以规范化文件名命名。 在此示例中,Wsutil.exe 生成了一个名为“example_wsdl”的全局定义结构,其中包含所有 Web 服务说明。 结构定义在存根文件中生成。

typedef struct _example_wsdl
{
  struct {
    WS_ELEMENT_DESCRIPTION SimpleMethod;
    WS_ELEMENT_DESCRIPTION SimpleMethodResponse;
  } elements;
  struct {
    WS_MESSAGE_DESCRIPTION ISimpleService_SimpleMethod_InputMessage;
    WS_MESSAGE_DESCRIPTION ISimpleService_SimpleMethod_OutputMessage;
  } messages;
  struct {
    WS_CONTRACT_DESCRIPTION DefaultBinding_ISimpleService;
  } contracts;
} _example_wsdl;

extern const _stockquote_wsdl stockquote_wsdl;

对于 XML 架构文档 (XSD) 中的全局元素定义,会为每个元素生成一个 WS_ELEMENT_DESCRIPTION 原型以及相应的 C 类型定义。 SimpleMethod 和 SimpleMethodResponse 的元素说明的原型会生成为上述结构中的成员。 C 结构按如下方式生成:

typedef struct SimpleMethod
{
  int   a;
  int   b;
} SimpleMethod;

typedef struct SimpleMethodResponse
{
  int   b;
  int   c;
} SimpleMethodResponse;

与全局复杂类型类似,Wsutil.exe 会生成类型 C 结构定义(如上所述),而无需匹配元素说明。

对于 WSDL 输入,Wsutil.exe 会生成以下原型和定义:

  • 为消息说明生成 WS_MESSAGE_DESCRIPTION 原型。 服务模型和消息层可以使用此说明。 消息说明结构是全局结构中名为“messagename”的字段。 在此示例中,消息说明会生成为 WSDL 文件中指定的ISimpleService_SimpleMethod_InputMessage 结构中的 ISimpleService_SimpleMethod_InputMessage 字段。
  • WS_CONTRACT_DESCRIPTION 原型是为合约说明生成的。 服务模型使用此说明。 消息说明结构是全局结构中名为“contractname”的字段。 在此示例中,合约说明会生成为结构“_example_wsdl”中的 DefaultBinding_ISimpleService 字段。

对于代理和存根,操作和类型规范都是常见内容,它们都是在这两个文件中生成的。 仅当代理和存根是在同一文件中生成时,Wsutil.exe 才会只生成一个副本。

标识符生成

上面列出的自动生成的 C 结构是根据 WSDL 文件中指定的名称创建的。 XML NCName 通常不被视为有效的 C 标识符,并且名称会根据需要进行规范化。 不转换十六进制值,并且为了提高可读性,会将“:”、“/”和“.”等常见字符转换为下划线“_”字符。

存根的标头

对于服务合约中的每个操作,将生成一个名为“<operationname> Callback”的回调例程。 (例如,示例服务合约中的操作“SimpleMethod”具有名为“SimpleMethodCallback”的生成回调。)

typedef HRESULT (CALLBACK *SimpleMethodCallback) (
  const WS_OPERATION_CONTEXT * context,
  int a, int *b, int *c,
  const WS_ASYNC_CONTEXT *asyncContext,
  WS_ERROR * error);

对于每个 WSDL portType,Wsutil.exe 都会生成一个表示 portType 的函数表。 PortType 上的每个操作都有一个对应的函数指针,指向函数表中存在的回调。

struct ISimpleServiceMethodTable
{
  ISimpleService_SimpleMethodCallback SimpleMethod;
};

将为所有操作生成代理原型。 原型名称是在服务合约的 WSDL 文件中指定的操作名称(在本例中为“SimpleMethod”)。

HRESULT WINAPI SimpleMethod(WS_CHANNEL *channel,
  WS_HEAP *heap,
  int a,
  int *b,
  int *c,
  const WS_ASYNC_CONTEXT * asyncContext,
  WS_ERROR * error );

仅生成本地说明原型

代理和存根文件包含全局定义结构的定义,包括包含仅限本地说明的结构和客户端代理/服务存根实现的原型和定义。

存根文件本地的所有原型和定义都作为封装结构的一部分生成。 此总体本地说明结构提供序列化层和服务模型所需的说明的清晰层次结构。 本地说明结构具有类似于以下任一示例的原型:

struct _filenameLocalDefinitions
{
  struct {
  // schema related output for all the embedded 
  // descriptions that needs to describe global complex types.
  } globalTypes;
  // global elements.
  struct {
  // schema related output, like field description
  // structure description, element description etc.
  ...
  } globalElements;
  struct {
  // messages and related descriptions
  } messages;
  struct {
  // contract and related descriptions.
  } contracts;
  struct {
  // XML dictionary entries.
  } dictionary;
} _filenameLocalDefinitions;

引用其他文件中的定义

本地定义可以引用另一个文件中生成的说明。 例如,可以在从 WSDL 文件生成的 C 代码文件中定义消息,但可以在从 XSD 文件生成的 C 代码文件中的其他地方定义消息元素。 在这种情况下,Wsutil.exe 会从包含消息定义的文件中生成对全局元素的引用,如下所示:

{  // WS_MESSAGE_DESCRIPTION
...
(WS_ELEMENT_DESRIPTION *)b_xsd.globalElement.<elementname>
  };

全局元素说明

对于 wsdl:type 或 XSD 文件中定义的每个全局元素,GlobalElement 字段中有一个名为 elementName 的匹配字段。 在此示例中,将生成名为 SimpleMethod 的结构:

typedef struct _SimpleServiceLocal
{
  struct  // global elements
  {
    struct // SimpleMethod
    {
    ...
    WS_ELEMENT_DESCRIPTION SimpleMethod;
    } SimpleMethod;
    ...
  } globalElements;
}

元素说明所需的其他说明将作为包含结构的一部分生成。 如果元素是简单的类型元素,则只有一个 WS_ELEMENT_DESCRIPTION 字段。 如果元素类型为结构,则所有相关字段和结构说明都将作为元素结构的一部分生成。 在此示例中,SimpleMethod 元素是包含两个字段(ab)的结构。 Wsutil.exe 会生成如下所示的结构:

...
struct // SimpleMethod
{
  struct // SimpleMethod structure
  {
    WS_FIELD_DESCRIPTION a;
    WS_FIELD_DESCRIPTION b;
    WS_FIELD_DESCRIPTION * SimpleMethodFields [2];
    WS_STRUCT_DESCRIPTION structDesc;
  } SimpleMethoddescs; // SimpleMethod
  WS_ELEMENT_DESCRIPTION elementDesc;
} SimpleMethod;
...

嵌入式结构和嵌入元素会根据需要生成为子结构。

Wsutil.exe 为在指定的 wsdl:service 下定义的每个 portType 值,在 WSDL 节下生成一个字段。

...
struct { // WSDL
    struct { // portTypeName
        struct { // operationName
        } operationName;
    ...
    WS_OPERATION_DESCRIPTION* operations[numOperations];
    WS_CONTRACT_DESCRIPTION contractDesc;
    } portTypeName;
}
...

Wsutil.exe 会生成一个字段 f,其中包含操作所需的所有说明、指向每个方法的每个操作说明的指针的符号数组,以及指定的 portType 的一个 WS_CONTRACT_DESCRIPTION

操作所需的所有说明均在指定的 portType 下的 operationName 字段中生成。 其中包含 WS_ELEMENT_DESCRIPTION 字段以及输入和输出参数的子结构。 同样的,还包含输入消息和可选输出消息的 WS_MESSAGE_DESCRIPTION 字段以及一些字段:所有操作参数的 WS_PARAMETER_DESCRIPTION 列表字段和操作本身的 WS_OPERATION_DESCRIPTION 字段。 在此示例中,会生成 SimpleMethod 说明的代码结构,如下所示:

...
struct // messages
{
  WS_MESSAGE_DESCRIPTION ISimpleService_SimpleMethod_InputMessage;
  WS_MESSAGE_DESCRIPTION ISimpleService_SimpleMethod_OutputMessage;
} messages;
struct // contracts
{
  struct // DefaultBinding_ISimpleService
  {
    struct // SimpleMethod
    {
      WS_PARAMETER_DESCRIPTION params[3];
      WS_OPERATION_DESCRIPTION SimpleMethod;
    } SimpleMethod;
    WS_OPERATION_DESCRIPTION* operations[1];
    WS_CONTRACT_DESCRIPTION contractDesc;
  } DefaultBinding_ISimpleService;
} contracts;
...

各种说明中使用的名称和命名空间作为类型 WS_XML_STRING 的字段生成。 所有这些字符串都是作为每个文件常量字典的一部分生成的。 字符串列表和 WS_XML_DICTIONARY 字段(在以下示例中称为字典)生成为 fileNameLocal 结构的字典字段的一部分。

struct { // fileNameLocal
...
  struct { // dictionary
    struct { // XML string list
      WS_XML_STRING firstFieldName;
      WS_XML_STRING firstFieldNS;
      ...
    } xmlStrings;
  WS_XML_DICTIONARY dict;
  } dictionary;
}; // fileNameLocal;

WS_XML_STRING 数组生成为 WS_XML_STRING 类型的一系列字段,并以易记名称命名。 生成的存根在各种说明中使用易记名称来提高可读性。

WSDL 操作的客户端代理

Wsutil.exe 会为所有操作生成客户端代理。 应用程序可以使用前缀命令行选项覆盖方法签名。

HRESULT WINAPI bindingName_SimpleMethod(WS_SERVICE_PROXY *serviceProxy,
  WS_HEAP *heap,
  int a,
  int *b,
  int *c,
  const WS_CALL_PROPERTY* callProperties,
  ULONG callPropertyCount,
  const WS_ASYNC_CONTEXT * asyncContext,
  WS_ERROR * error )
{
  void* argList[] = {&a, &b, &c};
  return WsCall(_serviceProxy,
    (WS_OPERATION_DESCRIPTION*)&example_wsdlLocalDefinitions.contracts.DefaultBinding_ISimpleService.SimpleMethod.SimpleMethod,
    (void **)&_argList,
    callProperties,
    callPropertyCount,
    heap,
    asyncContext,
    error
  );      
}

操作调用方必须传入有效的参数。 使用参数中指定的 WS_HEAP 值分配输出参数。 调用函数可以重置或释放堆以释放所有输出参数的内存。 如果操作失败,则可以从可选错误对象(如果可用)中检索其他详细的错误信息。

Wsutil.exe 会为绑定中所述的所有操作生成服务存根。

HRESULT CALLBACK ISimpleService_SimpleMethodStub(
  const WS_OPERATION_CONTEXT *context,
  void * stackStruct,
  void * callback,
  const WS_ASYNC_CONTEXT * asyncContext,
  WS_ERROR *error )
{
  SimpleMethodParamStruct *pstack = (SimpleMethodParamStruct *) stackstruct;
  SimpleMethodOperation operation = (SimpleMethodOperation)callback;
  return operation(context, pstack->a, &(pstack->b), &(pstack->c ), asyncContext, error );
}

以上部分介绍了仅包含存根文件的所有本地定义的本地结构的原型。 后续部分介绍了说明的定义。

WSDL 定义生成

Wsutil.exe 会生成类型“*<service_name>*Local”的一个名为 *<file_name>*LocalDefinitions 的常量静态 (const static) 结构,该结构包含所有仅限本地的定义。

const static _SimpleServiceLocal example_wsdlLocalDefinitions =
{
  {  // global types
  ...
  }, // global types
  {  // global elements
  ...
  }, // global elements
  {  // messages
  ...
  }, //messages
  ...
  {  // dictionary
  ...
  }, // dictionary
},

支持以下 WSDL 说明:

  • wsdl:service
  • wsdl:binding
  • wsdl:portType
  • wsdl:operation
  • wsdl:message

正在处理 wsdl:operation 和 wsdl:message

WSDL 文档中指定的每个操作都由 Wsutil.exe 映射到服务操作。 该工具会为服务器和客户端生成服务操作的单独定义。

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:portType name="ISimpleService">
  <wsdl:operation name="SimpleMethod">
   <wsdl:input wsaw:Action="http://Example.org/ISimpleService/SimpleMethod" 
   message="tns:ISimpleService_SimpleMethod_InputMessage" />
   <wsdl:output wsaw:Action="http://Example.org/ISimpleService/SimpleMethodResponse" 
   message="tns:ISimpleService_SimpleMethod_OutputMessage" />
  </wsdl:operation>
 </wsdl:portType>
 <wsdl:message name="ISimpleService_SimpleMethod_InputMessage">
  <wsdl:part name="parameters" element="tns:SimpleMethod" />
 </wsdl:message>
 <wsdl:message name="ISimpleService_SimpleMethod_OutputMessage">
  <wsdl:part name="parameters" element="tns:SimpleMethodResponse" />
 </wsdl:message>
</wsdl:definitions>

输入和输出消息数据元素的布局由该工具评估,以生成基础结构的序列化元数据,以及与输入和输出消息关联的生成的服务操作的实际签名。

特定 portType 中每个操作的元数据都有输入消息和/或输出消息,其中每个消息都映射到 WS_MESSAGE_DESCRIPTION。 在此示例中,portType 中的操作的输入和输出消息分别映射到 WS_OPERATION_DESCRIPTION 上的 inputMessageDescription 和 outputMessageDescription(可选)。

对于每个 WSDL 消息,该工具会生成引用 WS_ELEMENT_DESCRIPTION 定义的 WS_MESSAGE_DESCRIPTION,如下所示:

... 
{    // message description for ISimpleService_SimpleMethod_InputMessage
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.DefaultBinding_ISimpleServiceISimpleService_SimpleMethod_InputMessageactionName,
  (WS_ELEMENT_DESCRIPTION*)&(WS_ELEMENT_DESCRIPTION*)&example_wsdl.globalElements.SimpleMethodReponse
},  // message description for ISimpleService_SimpleMethod_InputMessage
...

消息说明是指输入元素说明。 由于元素是全局定义的,因此消息说明会引用全局定义而不是本地静态元素。 同样的,如果元素是在另一个文件中定义的,Wsutil.exe 会生成对该文件中全局定义的结构的引用。 例如,如果在另一个 example.xsd 文件中定义了 SimpleMethodResponse,则 Wsutil.exe 会生成以下内容:

...
{    // message description for ISimpleService_SimpleMethod_InputMessage
(WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.DefaultBinding_ISimpleServiceISimpleService_SimpleMethod_InputMessageactionName,
(WS_ELEMENT_DESCRIPTION*)&(WS_ELEMENT_DESCRIPTION*)&example_xsd.globalElements.SimpleMethodReponse
},  // message description for ISimpleService_SimpleMethod_InputMessage
...

每个消息说明都包含所有消息数据元素的操作和特定元素说明(类型 WS_ELEMENT_DESCRIPTION 的字段)。 对于 RPC 样式的消息或包含多个部分的消息,将创建包装元素来封装其他信息。

RPC 样式支持

Wsutil.exe 会根据 SOAP 1.2 规范的 WSDL 1.1 绑定扩展支持文档样式和 RPC 样式操作。 RPC 和文本样式操作标记为 WS_RPC_LITERAL_OPERATION。 服务模型会忽略 RPC/文本操作中响应正文包装元素的名称。

Wsutil.exe 本身不支持编码样式操作。 为编码消息生成 WS_XML_BUFFER 参数,开发人员必须直接填充不透明缓冲区。

多个消息部分支持

Wsutil.exe 支持一条消息中的多个消息部分。 可以按如下所示指定多部分消息:

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:message name="ISimpleService_MutipleParts_InputMessage">
  <wsdl:part name="part1" element="tns:SimpleElement1" />
  <wsdl:part name="part2" element="tns:SimpleElement2" />
 </wsdl:message>
</wsdl:definitions>

如果消息包含多个部分,Wsutil.exe 会为消息元素生成 WS_STRUCT_TYPE 字段。 如果使用文档样式表示消息,Wsutil.exe 会生成具有结构类型的包装元素。 包装元素没有名称或特定命名空间,包装结构包含所有部分中的所有元素(作为字段)。 包装元素仅供内部使用,不会在消息正文中序列化它。

如果消息使用 RPC 或文本样式表示形式,则 Wsutil.exe 会根据 WSDL SOAP 扩展规范创建一个包装元素,其操作名称为元素名称,并将指定的命名空间创建为服务命名空间。 元素的结构包含字段数组,这些字段表示各消息部件中指定的类型。 包装元素会映射到消息正文中的实际顶部元素,如 SOAP 规范中所示。

在服务器端,每个操作都会导致生成的服务器服务操作的 typedef。 此 typedef 用于引用函数表中的操作,如前文所述。 每个操作还会导致生成存根函数,该存根函数代表代理调用实际方法。

typedef HRESULT (CALLBACK *SimpleMethodCallback) (
  const WS_OPERATION_CONTEXT* context,
  unsigned int  a,
  unsigned int * b,
  unsigned int * c,
  const WS_ASYNC_CONTEXT* asyncContext,
  WS_ERROR* error
  );

对于 SimpleMethod 操作,上文定义了 SimpleMethodOperation typedef。 请注意,生成的方法具有一个展开的参数列表,其中 SimpleMethod 操作的输入和输出消息的消息部分用作命名参数。

在客户端上,每个操作都会映射到代理服务操作。

HRESULT WINAPI SimpleMethod (
  WS_SERVICE_PROXY* serviceProxy,
  ws_heap *heap,
  unsigned int  a,
  unsigned int * b,
  unsigned int * c,
  const WS_ASYNC_CONTEXT* asyncContext,
  WS_ERROR* error);

正在处理 wsdl:binding

WWSAPI 服务模型支持 SOAP 绑定扩展。 对于每个绑定,都有一个关联的 portType

Soap 绑定扩展中指定的传输仅供咨询使用。 应用程序需要在创建通道时提供传输信息。 目前,我们支持 WS_HTTP_BINDING 和 WS_TCP_BINDING 绑定。

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:binding name="DefaultBinding_ISimpleService" type="tns:ISimpleService">
  <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
  <wsdl:operation name="SimpleMethod">
   <soap:operation soapAction="http://Example.org/ISimpleService/SimpleMethod" 
   style="document" />
   <wsdl:input>
    <soap:body use="literal" />
   </wsdl:input>
   <wsdl:output>
    <soap:body use="literal" />
   </wsdl:output>
  </wsdl:operation>
 </wsdl:binding>
</wsdl:definitions>

在我们的示例 WSDL 文档中,ISimpleService 只有一个 portType。 提供的 SOAP 绑定指示 HTTP 传输,该传输指定为 WS_HTTP_BINDING。 请注意,此结构没有静态修饰,因为此结构需要供应用程序使用。

正在处理 wsdl:portType

WSDL 中的每个 portType 由一个或多个操作组成。 该操作应与 wsdl:binding 中指示的 SOAP 绑定扩展保持一致。

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:portType name="ISimpleService">
  <wsdl:operation name="SimpleMethod">
   ...
  </wsdl:operation>
 </wsdl:portType>
</wsdl:definitions>

在此示例中,ISimpleService portType 仅包含 SimpleMethod 操作。 这与绑定这部分一致,其中只有一个 WSDL 操作映射到 SOAP 操作。

由于 ISimpleService portType 只有一个操作 (SimpleMethod),相应的函数表仅包含 SimpleMethod,即服务操作。

就元数据而言,每个 portType 都由 Wsutil.exe 映射到 WS_CONTRACT_DESCRIPTIONPortType 中的每个操作都映射到 WS_OPERATION_DESCRIPTION

在此示例中,工具 portType 为 ISimpleService 生成 WS_CONTRACT_DESCRIPTION。 此合约说明包含 ISimpleService portType 上可用的特定操作数,以及表示在 ISimpleService 的 portType 上定义的单个操作的 WS_OPERATION_DESCRIPTION 数组。 由于 ISimpleService 的 ISimpleService portType 只有一个操作,因此只有一个 WS_OPERATION_DESCRIPTION 定义。

...  part of LocalDefinitions structure
{    // array of operations for DefaultBinding_ISimpleService
(WS_OPERATION_DESCRIPTION*)&example_wsdlLocalDefinitions.contracts.DefaultBinding_ISimpleService.SimpleMethod.SimpleMethod,
},    // array of operations for DefaultBinding_ISimpleService
{    // contract description for DefaultBinding_ISimpleService
1,
(WS_OPERATION_DESCRIPTION**)example_wsdlLocalDefinitions.contracts.DefaultBinding_ISimpleService.operations,
WS_HTTP_CHANNEL_BINDING,
},  // end of contract description for DefaultBinding_ISimpleService
},    // DefaultBinding_ISimpleService       ...

正在处理 wsdl:service

WsUtil.exe 使用服务查找绑定/端口类型,并生成描述类型、消息、端口类型定义等的合约结构。 合约说明需从外部访问,它们作为通过生成的标头指定的全局定义结构的一部分生成。

WsUtil.exe 支持在 wsdl:port 中定义的 EndpointReference 扩展。 终结点引用是在 WS-ADDRESSING 中定义的,作为描述服务的终结点信息的方法。 作为 WS_XML_STRING 保存的输入终结点引用扩展文本和匹配的 WS_ENDPOINT_ADDRESS_DESCRIPTION 是在全局结构的 endpointReferences 这一节中生成的。

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:service name="SimpleService">
  <wsdl:port name="ISimpleService" binding="tns:DefaultBinding_ISimpleService">
   <soap:address location="http://Example.org/ISimpleService" />
   <wsa:EndpointReference>
    <wsa:Address>http://example.org/wcfmetadata/WSHttpNon</wsa:Address>
   </wsa:EndpointReference> 
  </wsdl:port>
 </wsdl:service>
</wsdl:definitions>
  const _example_wsdl example_wsdl =
  {
  ... // global element description
  {// messages
  {    // message description for ISimpleService_SimpleMethod_InputMessage
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.ISimpleService_SimpleMethod_InputMessageactionName,
  (WS_ELEMENT_DESCRIPTION*)&example_wsdl.globalElements.SimpleMethod,
},    // message description for ISimpleService_SimpleMethod_InputMessage
{    // message description for ISimpleService_SimpleMethod_OutputMessage
(WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.ISimpleService_SimpleMethod_OutputMessageactionName,
  (WS_ELEMENT_DESCRIPTION*)&example_wsdl.globalElements.SimpleMethodResponse,
},    // message description for ISimpleService_SimpleMethod_OutputMessage
}, // messages
{// contracts
{   // DefaultBinding_ISimpleService
1,
(WS_OPERATION_DESCRIPTION**)example_wsdlLocalDefinitions.contracts.DefaultBinding_ISimpleService.operations,
WS_HTTP_CHANNEL_BINDING,
},    // end of DefaultBinding_ISimpleService
    }, // contracts
    {
        {
            {   // endpointAddressDescription
                WS_ADDRESSING_VERSION_0_9,
            },                    
            (WS_XML_STRING*)&xml_string_generated_in_stub // endpointReferenceString
        }, //DefaultBinding_ISimpleService
    }, // endpointReferences
}

使用 WsUtil 生成的元数据创建 WS_ENDPOINT_ADDRESS

WsCreateReader      // Create a WS_XML_READER
Initialize a WS_XML_READER_BUFFER_INPUT
WsSetInput          // Set the encoding and input of the reader to generate endpointReferenceString
WsReadType        // Read WS_ENDPOINT_ADDRESS from the reader
    // Using WS_ELEMENT_TYPE_MAPPING, WS_ENDPOINT_ADDRESS_TYPE and generated endpointAddressDescription, 

客户端代理或服务存根中的常量字符串是作为 WS_XML_STRING 类型的字段生成的,并且代理或存根文件中的所有字符串都有一个常量字典。 字典中的每个字符串都是作为本地结构的字典部分的字段生成的,以提高可读性。

... // dictionary part of LocalDefinitions structure
{    // xmlStrings
  { // xmlStrings
    WS_XML_STRING_DICTIONARY_VALUE("a",&example_wsdlLocalDefinitions.dictionary.dict, 0), 
    WS_XML_STRING_DICTIONARY_VALUE("http://Sapphire.org",&example_wsdlLocalDefinitions.dictionary.dict, 1), 
    WS_XML_STRING_DICTIONARY_VALUE("b",&example_wsdlLocalDefinitions.dictionary.dict, 2), 
    WS_XML_STRING_DICTIONARY_VALUE("SimpleMethod",&example_wsdlLocalDefinitions.dictionary.dict, 3),
    ...
  },  // end of xmlStrings
  {   // SimpleServicedictionary
    // 45026280-d5dc-4570-8195-4d66d13bfa34
    { 0x45026280, 0xd5dc, 0x4570, { 0x81, 0x95, 0x4d,0x66, 0xd1, 0x3b, 0xfa, 0x34 } },
    (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings,
    stringCount,
    TRUE,
  },
}
...

正在处理 wsdl:type

Wsutil.exe 仅支持 wsdl:type 规范中的 XML 架构 (XSD) 文档。 一种特殊情况是消息端口指定全局元素定义。 有关在这些情况下使用的启发法的更多详细信息,请参阅以下部分。

参数处理启发法

在服务模型中,WSDL 消息会映射到某个方法中的特定参数。 Wsutil.exe 具有两种参数生成样式:在第一个样式中,操作有一个输入消息参数和一个输出消息参数(如有必要);在第二种样式中,Wsutil.exe 使用启发法将输入消息和输出消息的字段映射到操作中的不同参数并扩展结构中的字段。 输入和输出消息都需要具有结构类型消息元素才能生成此第二种方法。

从输入和输出消息生成操作参数时,Wsutil.exe 使用以下规则:

  • 对于具有多个消息部分的输入和输出消息,每个消息部分都是操作中的单独参数,其中消息部分的名称为参数名称。
  • 对于包含一个消息部分的 RPC 样式消息,该消息部分是操作中的参数,其中消息部分的名称为参数名称。
  • 对于包含一个消息部分的文档样式输入和输出消息:
    • 如果消息部分的名称为“parameters”,并且元素类型为结构,则该结构中的每个字段都被视为一个单独的参数,且字段名称为参数名称。
    • 如果消息部分的名称不是“parameters”,则消息是操作中的参数,且消息名称用作相应的参数名称。
  • 对于具有 nillable 元素的文档样式输入和输出消息,消息将映射到一个参数,且消息部分的名称为参数名称。 添加了一个额外的间接级别,以指示指针可以为 NULL
  • 如果字段仅出现在输入消息元素中,则该字段将被视为 [in] 参数。
  • 如果字段仅出现在输出消息元素中,则该字段将被视为 [out] 参数。
  • 如果输入消息和输出消息中有一个具有相同名称和相同类型的字段,则该字段将被视为 [in,out] 参数。

以下工具可用于确定参数的方向:

  • 如果字段仅出现在输入消息元素中,则该字段将被视为仅 in 参数。
  • 如果字段仅出现在输出消息元素中,则该字段将被视为仅 out 参数。
  • 如果输入消息和输出消息中有一个具有相同名称和相同类型的字段,则该字段将被视为 in、out 参数。

Wsutil.exe 仅支持已排序的元素。 如果 Wsutil.exe 无法将 in 参数和 out 参数合并到一个参数列表中,则它会拒绝对 [in,out] 参数的无效排序。 后缀可能会添加到参数名称,以避免名称冲突。

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:message name="ISimpleService_SimpleMethod_InputMessage">
  <wsdl:part name="parameters" element="tns:SimpleMethod" />
 </wsdl:message>
 <wsdl:message name="ISimpleService_SimpleMethod_OutputMessage">
  <wsdl:part name="parameters" element="tns:SimpleMethodResponse" />
 </wsdl:message>
</wsdl:definitions>

Wsutil.exe 会将 tns:SimpleMethod 和 tns:SimpleMethodResponse 中的字段视为参数,如以下参数定义所示:

<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Example.org" 
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" targetNamespace="http://Example.org" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
 <wsdl:types>
  <xs:schema xmlns:tns="http://Example.org" elementFormDefault="qualified" 
  targetNamespace="http://Example.org" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:import namespace="http://Example.org" />
   <xs:element name="SimpleMethod">
    <xs:complexType>
     <xs:sequence>
      <xs:element name="a" type="xs:unsignedInt" />
      <xs:element name="b" type="xs:unsignedInt" />
     </xs:sequence>
    </xs:complexType>
   </xs:element>
   <xs:element name="SimpleMethodResponse">
    <xs:complexType>
     <xs:sequence>
      <xs:element name="b" type="xs:unsignedInt" />
      <xs:element name="c" type="xs:unsignedInt" />
     </xs:sequence>
    </xs:complexType>
   </xs:element>
  </xs:schema>
 </wsdl:types>
</wsdl:definitions>

Wsutil.exe 会从上述列表的字段展开参数列表,并在以下代码示例中生成 ParamStruct 结构。 服务模型运行时可以使用此结构将参数传递给客户端和服务器存根。

typedef struct SimpleMethodParamStruct {
  unsigned int   a;  
  unsigned int   b;
  unsigned int   c;
} ;

此结构仅用于描述客户端和服务器端的堆栈帧。 消息说明或消息说明引用的元素说明没有发生更改。

  // following are local definitions for the complex type
  { // field description for a
  WS_ELEMENT_FIELD_MAPPING,
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.aLocalName,
(WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.aNamespace,
  WS_INT32_TYPE,
  0,
  WsOffsetOf(_SimpleMethod, a),
  0,
  0,
  },    // end of field description for a
  { // field description for b
  WS_ELEMENT_FIELD_MAPPING,
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.bLocalName,
(WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.aNamespace,
  WS_INT32_TYPE,
  0,
  WsOffsetOf(_SimpleMethod, b),
  0,
  0,
  },    // end of field description for b
  {    // fields description for _SimpleMethod
  (WS_FIELD_DESCRIPTION *)&example_wsdlLocalDefinitions.globalElements.SimpleMethod._SimpleMethoddescs.a,
  (WS_FIELD_DESCRIPTION *)&example_wsdlLocalDefinitions.globalElements.SimpleMethod._SimpleMethoddescs.b,
  },
  {  // structure definition
  sizeof(_SimpleMethod),
  __alignof(_SimpleMethod),
  (WS_FIELD_DESCRIPTION**)&example_wsdlLocalDefinitions.globalElements.SimpleMethod._SimpleMethoddescs._SimpleMethodFields,
  WsCountOf(example_wsdlLocalDefinitions.globalElements.SimpleMethod._SimpleMethoddescs._SimpleMethodFields),
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings._SimpleMethodTypeName,
(WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.aNamespace,
  0,
  },   // struct description for _SimpleMethod
  // following are global definitions for the out parameter
  ...
  {  // element description
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings._SimpleMethodTypeName,
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.aNamespace,
  WS_STRUCT_TYPE,
  (void *)&example_wsdlLocalDefinitions.globalElements.SimpleMethod._SimpleMethoddescs.structDesc,
  },
  {    // message description for ISimpleService_SimpleMethod_InputMessage
  (WS_XML_STRING*)&example_wsdlLocalDefinitions.dictionary.xmlStrings.ISimpleService_SimpleMethod_InputMessageactionName,
(WS_ELEMENT_DESCRIPTION*)&example_wsdl.globalElements.SimpleMethod,
  },    // message description for ISimpleService_SimpleMethod_InputMessage

作为一般规则,为所有 [out] 和 [in,out] 参数添加一个间接级别。

无参数操作

对于文档和文本操作,如果出现以下情况,Wsutil.exe 会将该操作视为具有一个输入参数和一个输出参数:

  • 输入或输出消息包含多个部分。
  • 只有一个消息部分,且消息部分的名称不是“parameters”。

.. 在上面的示例中,假设消息部分名为 ParamInParamOut,则方法签名将变为以下代码:

typedef struct SimpleMethod{
unsigned int a;
unsigned int b;
};

typedef struct SimpleMethodResponse {
unsigned int b;
unsigned int c;
};

typedef  struct ISimpleService_SimpleMethodParamStruct
{
SimpleMethod  * SimpleMethod;
SimpleMethodResponse  * SimpleMethodResponse;
} ISimpleService_SimpleMethodParamStruct;

Wsutil.exe 会为操作说明生成版本签名,以便 WsCall 和服务器端服务模型引擎可以检查生成的说明是否适用于当前平台。

此版本信息是作为 WS_OPERATION_DESCRIPTION 结构的一部分生成的。 版本号可以视为联合臂选择器,以使结构可扩展。 目前,versionID 设置为 1,且没有后续字段。 未来的版本可能会递增版本号,并根据需要添加更多字段。 例如,Wsutil.exe 当前会基于版本 ID 生成以下代码:

{ // SimpleMethod
{ // parameter descriptions for SimpleMethod
{ WS_PARAMETER_TYPE_NORMAL, (USHORT)0, (USHORT)-1 },
{ WS_PARAMETER_TYPE_NORMAL, (USHORT)1, (USHORT)-1 },
{ WS_PARAMETER_TYPE_NORMAL, (USHORT)-1, (USHORT)1 },
},    // parameter descriptions for SimpleMethod
{    // operation description for SimpleMethod
1,
(WS_MESSAGE_DESCRIPTION*)&example_wsdl.messages.ISimpleService_SimpleMethod_InputMessage,
(WS_MESSAGE_DESCRIPTION*)&example_wsdl.messages.ISimpleService_SimpleMethod_OutputMessage,
3,
(WS_PARAMETER_DESCRIPTION*)example_wsdlLocalDefinitions.contracts.DefaultBinding_ISimpleService.SimpleMethod.params,
SimpleMethodOperationStub
}, //operation description for SimpleMethod
},  // SimpleMethod

将来,它可以按下方所示进行扩展:

WS_OPERATION_DESCRIPTION simpleMethodOperationDesc =
{
  2,
  &ISimpleService_SimpleMethod_InputputMessageDesc,
  &ISimpleService_SimpleMethod_OutputMessageDesc,
  WsCountOf(SimpleMethodParameters),
  SimpleMethodParameters,
  ISimpleService_SimpleMethod_Stub,
  &forwardToString;   // just as an example.
};

安全性

请参阅 Wsutil 编译器工具主题的安全部分。