2009-03-26 25 views
57

Necesito clientes Windows C++ no administrados para hablar con un servicio WCF. Los clientes C++ podrían estar ejecutándose en Win2000 y versiones posteriores. Tengo un control sobre ambos servicios WCF y sobre qué API C++ se está utilizando. Dado que se trata de una aplicación patentada, es preferible usar material de Microsoft siempre que sea posible, definitivamente no API con licencia de GNU. Aquellos de ustedes que lo tienen funcionando, ¿pueden compartir un proceso paso a paso sobre cómo hacerlo funcionar?Crear servicio WCF para clientes C++ no administrados

He investigado siguientes opciones hasta el momento:

  • WWSAPI - no es bueno, no funcionará en los clientes de Windows 2000.
  • ATL Server, se usó following guide como referencia. Seguí los pasos descritos (eliminé los refs de política y aplané WSDL); sin embargo, el WSDL resultante aún no se puede utilizar con sproxy

¿Alguna idea más? Por favor, responda solo si realmente lo está haciendo usted mismo.

Edit1: Me disculpo por cualquier persona que podría haber confundido: lo que estaba buscando era una forma de llamar al servicio de WCF desde el cliente (s) donde se ha instalado ningún marco .NET, por lo que el uso de .NET a biblioteca auxiliar basada en no es una opción, debe ser pura no administrada C++

+0

Disculpa las molestias. He actualizado mi respuesta. Espero eso ayude. –

+1

Puede modificar el servicio WCF para ofrecer extremos SOAP y REST, y luego usar el punto final REST de C++. (Siempre que sus tipos de datos sean fácilmente analizados en C++). Consulte: http://stackoverflow.com/questions/186631/rest-soap-endpoints-for-a-wcf-service –

Respuesta

11

Para aquellos que estén interesados, encontré una solución de servidor ATL semi-funcional. El siguiente es el código de host, observe que está utilizando BasicHttpBinding, que es la única que trabaja con servidor ATL:

código
 var svc = new Service1(); 
     Uri uri = new Uri("http://localhost:8200/Service1"); 
     ServiceHost host = new ServiceHost(typeof(Service1), uri); 

     var binding = new BasicHttpBinding(); 
     ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IService1), binding, uri); 
     endpoint.Behaviors.Add(new InlineXsdInWsdlBehavior()); 

     host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true }); 
     var mex = host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 
     host.Open(); 

     Console.ReadLine(); 

para InlineXsdInWsdlBehavior se pudo encontrar here. Es necesario realizar un cambio importante en InlineXsdInWsdlBehavior para que funcione correctamente con sproxy cuando se trate de tipos complejos. Es causado por el error en sproxy, que no abarca correctamente los alias del espacio de nombres, por lo que wsdl no puede tener alias de espacio de nombres repetidos o sproxy se va a destruir.He aquí las funciones que debe cambiar:

public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context) 
    { 
     int tnsCount = 0; 

     XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas; 

     foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments) 
     { 
      // 
      // Recursively find all schemas imported by this wsdl 
      // and then add them. In the process, remove any 
      // <xsd:imports/> 
      // 
      List<XmlSchema> importsList = new List<XmlSchema>(); 
      foreach (XmlSchema schema in wsdl.Types.Schemas) 
      { 
       AddImportedSchemas(schema, schemaSet, importsList, ref tnsCount); 
      } 
      wsdl.Types.Schemas.Clear(); 
      foreach (XmlSchema schema in importsList) 
      { 
       RemoveXsdImports(schema); 
       wsdl.Types.Schemas.Add(schema); 
      } 
     } 
    } 


    private void AddImportedSchemas(XmlSchema schema, XmlSchemaSet schemaSet, List<XmlSchema> importsList, ref int tnsCount) 
    { 
     foreach (XmlSchemaImport import in schema.Includes) 
     { 
      ICollection realSchemas = schemaSet.Schemas(import.Namespace); 
      foreach (XmlSchema ixsd in realSchemas) 
      { 
       if (!importsList.Contains(ixsd)) 
       { 
        var new_namespaces = new XmlSerializerNamespaces(); 
        foreach (var ns in ixsd.Namespaces.ToArray()) 
        { 
         var new_pfx = (ns.Name == "tns") ? string.Format("tns{0}", tnsCount++) : ns.Name; 
         new_namespaces.Add(new_pfx, ns.Namespace); 
        } 

        ixsd.Namespaces = new_namespaces; 
        importsList.Add(ixsd); 
        AddImportedSchemas(ixsd, schemaSet, importsList, ref tnsCount); 
       } 
      } 
     } 
    } 

El siguiente paso es generar encabezado de C++:

sproxy.exe /wsdl http://localhost:8200/Service1?wsdl 

y luego se programa en C++ es así:

using namespace Service1; 

CoInitializeEx(NULL, COINIT_MULTITHREADED ); 

{ 
    CService1T<CSoapWininetClient> cli; 
    cli.SetUrl(_T("http://localhost:8200/Service1")); 

    HRESULT hr = cli.HelloWorld(); //todo: analyze hr 
} 

CoUninitialize(); 
return 0; 

resultante código C++ maneja complejo tipos bastante decentemente, excepto que no puede asignar NULL a los objetos.

+0

Intenté este enfoque pero sproxy todavía no podía manejar el wsdl que fue generado por mi servicio bastante simple. – PIntag

1

Puede implementar un cliente SOAP de una manera más fácil usando el MS Soap Toolkit obsoleto. Desafortunadamente, no parece haber un reemplazo para esto fuera de mover a .NET.

+0

¿puede publicar una muestra de proyectos interoperables WCF y soap toolkit? Creé uno simple, y el kit de herramientas soap llamada MSSoapInit no me gusta wsdl, y no me dirá lo que necesita específicamente (un mensaje falso "Servicio de procesamiento Service1 no encontró definiciones de puertos") – galets

56

La idea básica es escribir el código WCF para sus clientes en C# (es más fácil de esta manera) y utilizar un dll de puente C++ para cerrar el espacio entre el código C++ no administrado y el código WCF administrado escrito en C#.

Aquí está el proceso paso a paso usando Visual Studio 2008 junto con .NET 3.5 SP1.

  1. Lo primero que debe hacer es crear el Servicio WCF y un medio para alojarlo. Si ya tiene esto, salte al paso 7 a continuación. De lo contrario, cree un Servicio de Windows NT siguiendo los pasos desde here. Use los nombres predeterminados que ofrece VS2008 para el proyecto y cualquier clase que se agregue al proyecto. Este servicio de Windows NT alojará el servicio WCF.

    • Agregue un servicio WCF llamado HelloService al proyecto. Para hacer esto, haga clic con el botón derecho en el proyecto en la ventana del Explorador de soluciones y seleccione la opción de menú Agregar | Nuevo elemento ... En el cuadro de diálogo Agregar nuevo elemento, seleccione la plantilla de servicio C# WCF y haga clic en el botón Agregar. Esto agrega HelloService al proyecto en forma de un archivo de interfaz (IHelloService.cs), un archivo de clase (HelloService.cs) y un archivo de configuración de servicio predeterminado (app.config).

    • Definir el HelloService así:

``

[ServiceContract] 
    public interface IHelloService 
    { 
     [OperationContract] 
     string SayHello(string name); 
    } 
    public class HelloService : IHelloService 
    { 
     public string SayHello(string name) 
     { 
      return String.Format("Hello, {0}!", name); 
     } 
    } 
  • Modificar la clase Service1 creado en el paso 1 anterior para tener este aspecto:

    using System.ServiceModel; 
    using System.ServiceProcess; 
    public partial class Service1 : ServiceBase 
    { 
        private ServiceHost _host; 
        public Service1() 
        { 
         InitializeComponent(); 
        } 
        protected override void OnStart(string [] args) 
        { 
         _host = new ServiceHost(typeof(HelloService)); 
         _host.Open(); 
        } 
        protected override void OnStop() 
        { 
         try { 
          if (_host.State != CommunicationState.Closed) { 
           _host.Close(); 
          } 
         } catch { 
         } 
        } 
    } 
    
  • construir el proyecto.

  • Abra el símbolo de Visual Studio 2008. Navegue al directorio de salida para el proyecto. Escriba lo siguiente: `installutil WindowsService1.exe 'Esto instala el servicio de Windows NT en su máquina local. Abra el panel de control de Servicios e inicie el servicio de Servicio1. Es importante hacer esto para que el Paso 9 a continuación funcione.

    1. Abra otra instancia de Visual Studio 2008 y cree una aplicación MFC, que está lo más lejos posible de WCF. Como ejemplo, simplemente creé una aplicación de diálogo MFC y agregué un ¡Hola! botón para ello. Haga clic con el botón derecho en el proyecto en el Explorador de soluciones y seleccione la opción del menú Propiedades. En la configuración general, cambie el directorio de salida a ... \ bin \ Debug. En la configuración general de C/C++, agregue .. \ HelloServiceClientBridge a los directorios de inclusión adicionales. En la configuración general de Linker, agregue ... \ Debug a los directorios adicionales de la biblioteca. Haga clic en el botón Aceptar.
  • En el menú Archivo, seleccione el elemento de menú Agregar | Nuevo proyecto .... Seleccione la plantilla de biblioteca de clase C#. Cambie el nombre a HelloServiceClient y haga clic en el botón Aceptar. Haga clic con el botón derecho en el proyecto en el Explorador de soluciones y seleccione la opción del menú Propiedades. En la pestaña Crear, cambiar la ruta de salida a .. \ bin \ Debug modo que el archivo app.config montaje y estará en el mismo directorio que la aplicación MFC. Esta biblioteca contendrá la referencia de servicio, es decir, la clase de proxy WCF, al servicio Hello de WCF alojado en el servicio de Windows NT.

  • En el Explorador de soluciones, haga clic en la carpeta Referencias para el proyecto HelloServiceClient y seleccione el complemento Servicio de Referencia ... opción de menú. En el campo Dirección, escriba la dirección de Hello Service. Esto debería ser igual a la dirección base en el archivo app.config creado en el Paso 2 anterior. Haga clic en el botón Ir. El Hello Service debería aparecer en la lista de Servicios. Haga clic en el botón OK para generar automáticamente la clase proxy (es) para el servicio Hola. NOTA:Me parece que siempre se ejecute en problemas de compilación con el archivo Reference.cs generado por este proceso. No sé si lo estoy haciendo mal o si hay un error, pero la forma más fácil de arreglar esto es modificar el archivo Reference.cs directamente. El problema suele ser un problema de espacio de nombres y se puede solucionar con un mínimo esfuerzo. Solo ten en cuenta que esta es una posibilidad. Para este ejemplo, he cambiado el HelloServiceClient.ServiceReference1 a simplemente HelloService (junto con cualquier otro cambio requerido).

  • Para permitir que la aplicación MFC interactúe con el servicio WCF, debemos compilar una DLL administrada de "puente" de C++. En el menú Archivo, seleccione el elemento de menú Agregar | Nuevo proyecto ... Seleccione la plantilla del proyecto C++ Win32. Cambie el nombre a HelloServiceClientBridge y haga clic en el botón Aceptar. Para la Configuración de la aplicación, cambie el Tipo de aplicación a DLL y marque la casilla de verificación Vaciar proyecto. Haga clic en el botón Finalizar.

  • Lo primero que debe hacer es modificar las propiedades del proyecto. Haga clic con el botón derecho en el proyecto en el Explorador de soluciones y seleccione la opción del menú Propiedades. En la configuración general, cambie el Directorio de salida a ... \ bin \ Debug y cambie la opción Compatibilidad de Common Language Runtime con Common Language Runtime Support (/ clr). En la configuración de Marco y Referencias, agregue una referencia a los ensamblados .NET System, System.ServiceModel y mscorlib. Haga clic en el botón Aceptar.

  • Agregue los siguientes archivos al proyecto HelloServiceClientBridge: HelloServiceClientBridge.h, IHelloServiceClientBridge.h y HelloServiceClientBridge.cpp.

  • Modificar el IHelloServiceClientBridge.h a tener este aspecto:

    #ifndef __IHelloServiceClientBridge_h__ 
    #define __IHelloServiceClientBridge_h__ 
    
    #include <string> 
    
    #ifdef HELLOSERVICECLIENTBRIDGE_EXPORTS 
    #define DLLAPI __declspec(dllexport) 
    #else 
    #define DLLAPI __declspec(dllimport) 
    #pragma comment (lib, "HelloServiceClientBridge.lib") // if importing, link also 
    #endif 
    
    class DLLAPI IHelloServiceClientBridge 
    { 
    public: 
        static std::string SayHello(char const *name); 
    }; 
    
    #endif // __IHelloServiceClientBridge_h__ 
    
  • Modificar el HelloServiceClientBridge.h a tener este aspecto:

    #ifndef __HelloServiceClientBridge_h__ 
    #define __HelloServiceClientBridge_h__ 
    
    #include <vcclr.h> 
    #include "IHelloServiceClientBridge.h" 
    
    #ifdef _DEBUG 
    #using<..\HelloServiceClient\bin\Debug\HelloServiceClient.dll> 
    #else 
    #using<..\HelloServiceClient\bin\Release\HelloServiceClient.dll> 
    #endif 
    
    class DLLAPI HelloServiceClientBridge : IHelloServiceClientBridge 
    { }; 
    
    #endif // __HelloServiceClientBridge_h__ 
    
  • La sintaxis para el archivo .cpp usos gestionados C++, lo que requiere cierto tiempo para acostumbrarse. Modificar el HelloServiceClientBridge.cpp a tener este aspecto:

    #include "HelloServiceClientBridge.h" 
    
    using namespace System; 
    using namespace System::Runtime::InteropServices; 
    using namespace System::ServiceModel; 
    using namespace System::ServiceModel::Channels; 
    
    std::string IHelloServiceClientBridge::SayHello(char const *name) 
    { 
        std::string rv; 
        gcroot<Binding^> binding = gcnew WSHttpBinding(); 
        gcroot<EndpointAddress^> address = gcnew EndpointAddress(gcnew String("http://localhost:8731/Design_Time_Addresses/WindowsService1/HelloService/")); 
        gcroot<HelloService::HelloServiceClient^> client = gcnew HelloService::HelloServiceClient(binding, address); 
        try { 
         // call to WCF Hello Service 
         String^ message = client->SayHello(gcnew String(name)); 
         client->Close(); 
         // marshal from managed string back to unmanaged string 
         IntPtr ptr = Marshal::StringToHGlobalAnsi(message); 
         rv = std::string(reinterpret_cast<char *>(static_cast<void *>(ptr))); 
         Marshal::FreeHGlobal(ptr); 
        } catch (Exception ^) { 
         client->Abort(); 
        } 
        return rv; 
    } 
    
  • El único que queda por hacer es actualizar la aplicación MFC para invocar el WCF llamada de servicio SayHello(). En el formulario MFC, haga doble clic en ¡Di Hola! para generar el manejador de eventos ButtonClicked. Hacer que el controlador de eventos siguiente aspecto:

    #include "IHelloServiceClientBridge.h" 
    #include <string> 
    void CMFCApplicationDlg::OnBnClickedButton1() 
    { 
        try { 
         std::string message = IHelloServiceClientBridge::SayHello("Your Name Here"); 
         AfxMessageBox(CString(message.c_str())); 
        } catch (...) { 
        } 
    } 
    
  • ejecutar la aplicación y haga clic en el Say Hello! botón. Esto causará que la aplicación invoque el método SayHello() del servicio Hello de WCF alojado en el servicio de Windows NT (que aún debe estar ejecutándose, dicho sea de paso). El valor de retorno se muestra en un cuadro de mensaje.

Esperamos que pueda extrapolar de este sencillo ejemplo para que se ajuste a sus necesidades. Si esto no funciona, házmelo saber para que pueda corregir la publicación.

+2

Matt, primero quería agradecerte por todo el trabajo que gastaste escribiendo esta guía. Seguramente sería útil para muchos, pero lamentablemente no para mí. Lo que estaba buscando era una llamada puramente no administrada al servicio WCF, no un proxy .NET. Es posible que el cliente no tenga un marco instalado, lo siento, no lo dejé claro – galets

+1

@Matt: Perdón si hay tantas ediciones. Creo que algunos de nosotros nos encontrábamos tratando de arreglar el formato del código. – gnovice

+1

Gracias, esto es * precisamente * lo que necesito, ¡gracias por el brillante trabajo! – Contango

2

Crearía una clase gestionada C# para hacer el trabajo WCF y exponer la clase como un objeto COM para los clientes C++.

0

Puede publicar un servicio web REST y utilizar la biblioteca COM MSXML, ya debe estar instalado, tiene un analizador XML y una biblioteca HTTP.

http://msdn.microsoft.com/en-us/library/ms763742.aspx

+0

Puede hacer que su servicio WCF ofrezca tanto SOAP como REST y utilice el punto final REST de C++. Ver: http://stackoverflow.com/questions/186631/rest-soap-endpoints-for-a-wcf-service –

Cuestiones relacionadas