web-dev-qa-db-fra.com

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

J'ai besoin d'obtenir des clients Windows C++ non gérés pour parler à un service WCF. Les clients C++ peuvent s'exécuter sur Win2000 et versions ultérieures. J'ai un contrôle sur le service WCF et sur l'API C++ utilisée. Comme il s'agit d'une application propriétaire, il est préférable d'utiliser des trucs Microsoft dans la mesure du possible, certainement pas GNU API sous licence. Ceux d'entre vous qui l'ont fait fonctionner, pouvez-vous partager un processus étape par étape comment le faire fonctionner?

Jusqu'à présent, j'ai recherché les options suivantes:

  • WWSAPI - pas bon, ne fonctionnera pas sur les clients Win 2000.
  • Serveur ATL, utilisé guide suivant comme référence. J'ai suivi les étapes décrites (supprimer les références de stratégie et aplatir le WSDL), mais le WSDL résultant n'est toujours pas utilisable par sproxy

Avez-vous d'autres idées? Veuillez répondre uniquement si vous le faites fonctionner vous-même.

Edit1: Je m'excuse pour tous ceux que je pourrais avoir confondus: ce que je cherchais était un moyen d'appeler WCF service à partir de client (s) où aucun framework .NET n'est installé, donc l'utilisation de la bibliothèque d'assistance basée sur .NET n'est pas une option, elle doit être pure C++ non managée

59
galets

Pour ceux qui sont intéressés, j'ai trouvé une solution ATL Server semi-fonctionnelle. Voici le code hôte, notez 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();

du code pour InlineXsdInWsdlBehavior a pu être trouvé ici . Une modification importante doit être apportée au comportement InlineXsdInWsdlBe afin qu'il fonctionne correctement avec sproxy lorsque des types complexes sont impliqués. Elle est causée par le bogue dans sproxy, qui n'étend pas correctement les alias d'espaces de noms, donc wsdl ne peut pas avoir d'alias d'espaces de noms répétitifs, sinon sproxy s'arrêtera. Voici les fonctions qui doivent changer:

    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

puis 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 assez décemment les types complexes, sauf qu'il ne peut pas affecter NULL aux objets.

12
galets

L'idée de base est d'écrire le code WCF pour vos clients en C # (c'est plus simple de cette façon) et d'utiliser une DLL de pontage C++ pour combler l'écart entre votre code C++ non managé et le code WCF géré écrit en C #.

Voici le processus étape par étape à l'aide de Visual Studio 2008 avec .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 l'avez déjà, passez à l'étape 7 ci-dessous. Sinon, 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 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 sur le projet dans la fenêtre Explorateur de solutions et sélectionnez l'élément de menu Ajouter | Nouvel élément ... Dans la boîte de dialogue Ajouter un nouvel élément, sélectionnez le modèle de service WCF C # et cliquez sur le bouton Ajouter. 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 de 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 ressembler à 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 {
            }
        }
    }
    
  • Générez le projet.

  • Ouvrez l'invite de commandes Visual Studio 2008. Accédez au répertoire de sortie du projet. Tapez ce qui suit: `installutil WindowsService1.exe 'Cela installe le service Windows NT sur votre ordinateur local. Ouvrez le panneau de configuration 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 possible de WCF. Par exemple, j'ai simplement créé une application de dialogue MFC et ajouté un Say Hello! bouton. Cliquez avec le bouton droit 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, remplacez le répertoire de sortie par ..\bin\Debug. Sous les paramètres généraux C/C++, ajoutez ..\HelloServiceClientBridge aux répertoires d'inclusion supplémentaires. Sous les paramètres généraux de l'éditeur de liens, ajoutez ..\Debug aux répertoires de bibliothèque 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 de bibliothèque de classes C #. Remplacez le nom par HelloServiceClient et cliquez sur le bouton OK. Cliquez avec le bouton droit sur le projet dans l'Explorateur de solutions et sélectionnez l'option de menu Propriétés. Dans l'onglet Générer, modifiez le chemin de sortie en ..\bin\Debug pour que le fichier Assembly et app.config se trouvent dans le même répertoire que l'application MFC. Cette bibliothèque contiendra la référence de 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 sur le dossier Références pour le projet HelloServiceClient et sélectionnez l'option de menu Ajouter une référence de service .... Dans le champ Adresse, saisissez l'adresse de Hello Service. Cela doit être égal à l'adresse de base dans le fichier app.config créé à l'étape 2 ci-dessus. Cliquez sur le bouton Aller. Le service Hello doit apparaître dans la liste des services. Cliquez sur le bouton OK pour générer automatiquement la ou les classes proxy pour le service Hello. REMARQUE: 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 me trompe 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'espace de noms et peut être résolu avec un minimum d'effort. Sachez simplement que c'est une possibilité. Pour cet exemple, j'ai changé le HelloServiceClient.ServiceReference1 en simplement HelloService (avec toutes les autres modifications requises).

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

  • La première chose à faire est de modifier les propriétés du projet. Cliquez avec le bouton droit 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, remplacez le répertoire de sortie par ..\bin\Debug et modifiez l'option Prise en charge du Common Language Runtime par Common Language Runtime Support (/ clr). Sous les paramètres Framework et références, ajoutez une référence aux assemblys .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 IHelloServiceClientBridge.h pour ressembler à 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 HelloServiceClientBridge.h pour ressembler à 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++ managé, ce qui prend un certain temps pour s'y habituer. Modifiez le HelloServiceClientBridge.cpp pour ressembler à 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;
    }
    
  • Il ne reste plus qu'à mettre à jour l'application MFC pour appeler l'appel de service WCF SayHello (). Sur le formulaire MFC, double-cliquez sur le message Dites bonjour! pour générer le gestionnaire d'événements ButtonClicked. Faites en sorte que le gestionnaire d'événements 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! bouton. Cela entraînera l'application à appeler la méthode SayHello () du service WCF Hello hébergé dans le service Windows NT (qui doit toujours être en cours d'exécution, soit dit en passant). 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, faites-le moi savoir afin que je puisse corriger le message.

58
Matt Davis

Je voudrais créer une classe gérée C # pour faire le travail WCF et exposer la classe en tant qu'objet COM aux clients C++.

2
kenny

Vous pouvez implémenter un client SOAP assez facilement en utilisant le obsolète MS Soap Toolkit . Malheureusement, il ne semble pas y avoir de remplacement pour cela en dehors du passage à .NET .

1
Eclipse

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

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

0
krisragh MSFT