60 votes

Créer un service WCF pour les clients C++ non gérés

Je dois faire en sorte que des clients Windows C++ non gérés puissent parler à un service WCF. Les clients C++ pourraient fonctionner sur Win2000 et plus. J'ai un contrôle à la fois sur le service WCF et sur l'API C++ qui est utilisée. Puisqu'il s'agit d'une application propriétaire, il est préférable d'utiliser le matériel Microsoft si possible, et certainement pas les API sous licence GNU. Ceux d'entre vous qui l'ont fait fonctionner peuvent-ils partager un processus étape par étape pour le faire fonctionner ?

J'ai étudié les options suivantes jusqu'à présent :

  • WWSAPI - pas bon, ne fonctionne pas sur les clients Win 2000.
  • ATL Server, utilisé guide suivant comme référence. J'ai suivi les étapes décrites (suppression des références de politique et aplatissement du WSDL), mais le WSDL résultant n'est toujours pas utilisable par sproxy.

D'autres idées ? Veuillez répondre uniquement si vous avez réussi à faire fonctionner le système vous-même.

Edit1 : Je m'excuse pour tous ceux que j'ai pu confondre : ce que je cherchais, c'était un moyen d'appeler un service WCF à partir d'un ou plusieurs clients où aucun cadre .NET n'est installé, donc l'utilisation d'une bibliothèque d'aide basée sur .NET n'est pas une option, elle doit être du pur C++ non géré.

60voto

Matt Davis Points 22019

L'idée de base est d'écrire le code WCF pour vos clients en C# (c'est plus facile de cette façon) et d'utiliser une dll de pont C++ pour combler le fossé entre votre code C++ non géré et le code WCF géré écrit en C#.

Voici la procédure à suivre, étape par étape, en utilisant Visual Studio 2008 et .NET 3.5 SP1.

  1. La première chose à faire est de créer le service WCF et un moyen de l'héberger. Si vous disposez déjà de ces éléments, passez à l'étape 7 ci-dessous. Autrement, créez un Service Windows NT en suivant les étapes de ici . Utilisez les noms par défaut proposés par VS2008 pour le projet et toutes les classes qui sont ajoutées au projet. Ce service Windows NT hébergera le service WCF.

    • Ajoutez un service WCF nommé HelloService au projet. Pour ce faire, cliquez avec le bouton droit de la souris sur le projet dans la fenêtre de l'explorateur de solutions et sélectionnez l'élément de menu Add|New Item... (Ajouter un nouvel élément). Dans la boîte de dialogue Add New Item, sélectionnez le modèle C# WCF Service et cliquez sur le bouton Add. Cela ajoute le HelloService au projet sous la forme d'un fichier d'interface (IHelloService.cs), d'un fichier de classe (HelloService.cs) et d'un fichier de configuration du service par défaut (app.config).

    • Définissez le HelloService comme ceci :

``

    [ServiceContract]
    public interface IHelloService
    {
        [OperationContract]
        string SayHello(string name);
    }
    public class HelloService : IHelloService
    {
        public string SayHello(string name)
        {
            return String.Format("Hello, {0}!", name);
        }
    }
  • Modifiez la classe Service1 créée à l'étape 1 ci-dessus pour qu'elle ressemble à ceci :

    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 {
            }
        }
    }
  • Construire le projet.

  • Ouvrez l'invite de commande de Visual Studio 2008. Naviguez vers le répertoire de sortie du projet. Tapez ce qui suit : `installutil WindowsService1.exe' Ceci installe le Service Windows NT sur votre machine locale. Ouvrez le panneau de configuration des services et démarrez le service Service1. Il est important de le faire pour que l'étape 9 ci-dessous fonctionne.

    1. Ouvrez une autre instance de Visual Studio 2008 et créez une application MFC, qui est à peu près aussi éloignée que vous pouvez l'être de WCF. À titre d'exemple, j'ai simplement créé une application MFC de dialogue et j'y ai ajouté un bouton Say Hello ! Cliquez avec le bouton droit de la souris sur le projet dans l'explorateur de solutions et sélectionnez l'option de menu Propriétés. Sous les paramètres généraux, changez le répertoire de sortie en . \bin\Debug. Sous les paramètres généraux de C/C++, ajoutez . \HelloServiceClientBridge dans les répertoires d'inclusion supplémentaires. Sous les paramètres généraux du linker, ajoutez \Debug dans les répertoires de bibliothèques supplémentaires. Cliquez sur le bouton OK.
  • Dans le menu Fichier, sélectionnez l'élément de menu Ajouter|Nouveau projet.... Sélectionnez le modèle C# Class Library. Modifiez le nom en HelloServiceClient et cliquez sur le bouton OK. Cliquez à droite sur le projet dans l'explorateur de solutions et sélectionnez l'option de menu Propriétés. Dans l'onglet Build, changez le chemin de sortie en .... \bin\Debug ainsi l'assembly et le fichier app.config seront dans le même répertoire que l'application MFC. Cette bibliothèque contiendra la référence du service, c'est-à-dire la classe proxy WCF, au service WCF Hello hébergé dans le service Windows NT.

  • Dans l'explorateur de solutions, cliquez avec le bouton droit de la souris sur le dossier Références du projet HelloServiceClient et sélectionnez l'option de menu Ajouter une référence de service.... Dans le champ Address, saisissez l'adresse de Hello Service. Elle doit être égale à l'adresse de base du fichier app.config créé à l'étape 2 ci-dessus. Cliquez sur le bouton "Go". Le service Hello devrait apparaître dans la liste des services. Cliquez sur le bouton OK pour générer automatiquement la ou les classe(s) proxy pour le Service Hello. NOTE : Il semble que je rencontre toujours des problèmes de compilation avec le fichier Reference.cs généré par ce processus. Je ne sais pas si je m'y prends mal ou s'il y a un bogue, mais le moyen le plus simple de résoudre ce problème est de modifier directement le fichier Reference.cs. Le problème est généralement un problème d'espacement des noms et peut être résolu avec un effort minimal. Sachez simplement que c'est une possibilité. Pour cet exemple, j'ai remplacé HelloServiceClient.ServiceReference1 par HelloService (ainsi que toutes les autres modifications nécessaires).

  • Pour permettre à l'application MFC d'interagir avec le service WCF, nous devons construire une DLL "pont" en C++ géré. Dans le menu Fichier, sélectionnez l'élément de menu Ajouter|Nouveau Projet.... Sélectionnez le modèle de projet C++ Win32. Changez le nom en HelloServiceClientBridge et cliquez sur le bouton OK. Pour les paramètres de l'application, changez le type d'application en DLL et cochez la case Empty project. Cliquez sur le bouton Finish.

  • La première chose à faire est de modifier les propriétés du projet. Cliquez avec le bouton droit de la souris sur le projet dans l'explorateur de solutions et sélectionnez l'option de menu Propriétés. Dans les paramètres généraux, changez le répertoire de sortie en . \bin\Debug et changez l'option Common Language Runtime Support en Common Language Runtime Support (/clr). Sous les paramètres Framework et Références, ajoutez une référence aux ensembles .NET System, System.ServiceModel et mscorlib. Cliquez sur le bouton OK.

  • Ajoutez les fichiers suivants au projet HelloServiceClientBridge - HelloServiceClientBridge.h, IHelloServiceClientBridge.h, et HelloServiceClientBridge.cpp.

  • Modifiez le fichier IHelloServiceClientBridge.h pour qu'il ressemble à ceci :

    #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__
  • Modifiez le fichier HelloServiceClientBridge.h pour qu'il ressemble à ceci :

    #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 syntaxe du fichier .cpp utilise le C++ géré, auquel il faut s'habituer. Modifiez le fichier HelloServiceClientBridge.cpp pour qu'il ressemble à ceci :

    #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;
    }
  • La seule chose qui reste à faire est de mettre à jour l'application MFC pour invoquer l'appel au service WCF SayHello(). pour invoquer l'appel de service SayHello(). Sur le formulaire MFC, double-cliquez sur le bouton Say Hello ! pour générer le gestionnaire d'événement ButtonClicked. Faites en sorte que le gestionnaire d'événement ressemble à ceci :

    #include "IHelloServiceClientBridge.h"
    #include <string>
    void CMFCApplicationDlg::OnBnClickedButton1()
    {
        try {
            std::string message = IHelloServiceClientBridge::SayHello("Your Name Here");
            AfxMessageBox(CString(message.c_str()));
        } catch (...) {
        }
    }
  • Exécutez l'application et cliquez sur le bouton Say Hello ! Cela aura pour effet que l'application invoquer la méthode SayHello() du service WCF Hello hébergé dans le service Windows NT (qui devrait d'ailleurs toujours être en cours d'exécution). La valeur de retour est ensuite affichée dans une boîte de message.

J'espère que vous pourrez extrapoler à partir de cet exemple simple pour répondre à vos besoins. Si cela ne fonctionne pas, veuillez me le faire savoir afin que je puisse corriger l'article.

12voto

galets Points 4119

Pour ceux qui sont intéressés, j'ai trouvé une solution semi-fonctionnelle pour ATL Server. Voici le code de l'hôte, remarquez qu'il utilise BasicHttpBinding, c'est le seul qui fonctionne avec ATL Server :

        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();

le code pour InlineXsdInWsdlBehavior a pu être trouvé ici . Une modification importante doit être apportée au comportement de InlineXsdInWsdlBehavior afin qu'il fonctionne correctement avec sproxy lorsque des types complexes sont impliqués. Cela est dû à un bogue dans sproxy, qui n'étend pas correctement les alias d'espace de noms, donc le wsdl ne peut pas avoir d'alias d'espace de noms répétitifs ou sproxy se plantera. Voici les fonctions qui doivent être modifiées :

    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);
                }
            }
        }
    }

L'étape suivante consiste à générer l'en-tête C++ :

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

et alors le programme C++ ressemble à ceci :

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;

Le code C++ résultant gère les types complexes de manière assez décente, sauf qu'il ne peut pas assigner NULL aux objets.

2voto

kenny Points 9150

Je créerais une classe gérée C# pour effectuer le travail WCF et j'exposerais la classe comme un objet COM aux clients C++.

1voto

Eclipse Points 27662

Vous pouvez implémenter un client SOAP assez facilement en utilisant l'outil obsolète Boîte à outils MS Soap . Malheureusement, il ne semble pas y avoir de solution de remplacement à part le passage à .NET.

0voto

krisragh MSFT Points 1520

Pouvez-vous publier un service Web REST et utiliser la bibliothèque COM MSXML ? Elle devrait être déjà installée, possède un analyseur XML et une bibliothèque HTTP.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X