Создание службы WCF для неуправляемых клиентов C ++

Мне нужно, чтобы неуправляемые клиенты Windows C ++ разговаривали со службой WCF. Клиенты C ++ могут работать на Win2000 и более поздних версиях. У меня есть контроль над службой WCF и используемым C ++ API. Поскольку это для проприетарного приложения, предпочтительно использовать там, где это возможно, материалы Microsoft, а не API, лицензированные GNU. Те из вас, у кого это работает, не могли бы вы поделиться пошаговым процессом, как заставить его работать?

Пока что я исследовал следующие варианты:

  • WWSAPI - не годится, на клиентах Win 2000 работать не будет.
  • Сервер ATL, используемый следующее руководство в качестве справки. Я выполнил описанные шаги (удалите ссылки на политику и сгладьте WSDL), однако полученный WSDL по-прежнему не может использоваться sproxy.

Есть еще идеи? Пожалуйста, отвечайте только в том случае, если он у вас действительно работает.

Edit1: прошу прощения за всех, кого я мог запутать: я искал способ вызвать службу WCF от клиента (ов), на котором не установлена ​​платформа .NET. , поэтому использование вспомогательной библиотеки на основе .NET не вариант, это должен быть чистый неуправляемый C ++


person galets    schedule 26.03.2009    source источник
comment
Извините за задержку. Я обновил свой ответ. Надеюсь, поможет.   -  person Matt Davis    schedule 28.03.2009
comment
Вы можете изменить службу WCF, чтобы предлагать конечные точки SOAP и REST, а затем использовать конечную точку REST из C ++. (Пока ваши типы данных легко анализируются в C ++). См .: stackoverflow.com/questions / 186631 /   -  person Jesse Chisholm    schedule 21.03.2013


Ответы (5)


Основная идея состоит в том, чтобы написать код WCF для ваших клиентов на C # (так это просто проще) и использовать мостовую dll C ++ для преодоления разрыва между вашим неуправляемым кодом C ++ и управляемым кодом WCF, написанным на C #.

Вот пошаговый процесс с использованием Visual Studio 2008 вместе с .NET 3.5 SP1.

  1. Первое, что нужно сделать, - это создать службу WCF и средство для ее размещения. Если он у вас уже есть, переходите к шагу 7 ниже. В противном случае создайте службу Windows NT, следуя инструкциям здесь . Используйте имена по умолчанию, предлагаемые VS2008 для проекта и любых классов, добавленных в проект. Эта служба Windows NT будет размещать службу WCF.

    • Добавьте в проект службу WCF с именем HelloService. Для этого щелкните проект правой кнопкой мыши в окне обозревателя решений и выберите пункт меню «Добавить | Новый элемент ...». В диалоговом окне «Добавить новый элемент» выберите шаблон службы C # WCF и нажмите кнопку «Добавить». Это добавляет HelloService в проект в виде файла интерфейса (IHelloService.cs), файла класса (HelloService.cs) и файла конфигурации службы по умолчанию (app.config).

    • Определите HelloService следующим образом:

``

    [ServiceContract]
    public interface IHelloService
    {
        [OperationContract]
        string SayHello(string name);
    }
    public class HelloService : IHelloService
    {
        public string SayHello(string name)
        {
            return String.Format("Hello, {0}!", name);
        }
    }
  • Измените класс Service1, созданный на шаге 1 выше, чтобы он выглядел так:

    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 {
            }
        }
    }
    
  • Постройте проект.

  • Откройте командную строку Visual Studio 2008. Перейдите в выходной каталог проекта. Введите следующее: `installutil WindowsService1.exe '. Это установит службу Windows NT на ваш локальный компьютер. Откройте панель управления службами и запустите службу Service1. Это важно сделать для того, чтобы шаг 9, приведенный ниже, работал.

    1. Open another instance of Visual Studio 2008 and create an MFC application, which is about as far away as you can get from WCF. As an example, I simply created a dialog MFC application and added a Say Hello! button to it. Right-click the project in the Solution Explorer and select the Properties menu option. Under the General settings, change the Output Directory to ..\bin\Debug. Under the C/C++ General settings, add ..\HelloServiceClientBridge to the Additional Include Directories. Under the Linker General settings, add ..\Debug to the Additional Library Directories. Click the OK button.
  • В меню "Файл" выберите пункт меню "Добавить | Новый проект ...". Выберите шаблон библиотеки классов C #. Измените имя на HelloServiceClient и нажмите кнопку ОК. Щелкните проект правой кнопкой мыши в обозревателе решений и выберите пункт меню «Свойства». На вкладке «Сборка» измените выходной путь на .. \ bin \ Debug, чтобы файл сборки и app.config находился в том же каталоге, что и приложение MFC. Эта библиотека будет содержать ссылку на службу, т. Е. Прокси-класс WCF, для службы WCF Hello, размещенной в службе Windows NT.

  • В обозревателе решений щелкните правой кнопкой мыши папку «Ссылки» для проекта HelloServiceClient и выберите пункт меню «Добавить ссылку на службу ...». В поле «Адрес» введите адрес службы Hello. Он должен быть равен базовому адресу в файле app.config, созданном на шаге 2 выше. Щелкните кнопку "Перейти". Служба Hello должна появиться в списке служб. Нажмите кнопку ОК, чтобы автоматически сгенерировать прокси-классы для службы Hello. ПРИМЕЧАНИЕ. Кажется, я всегда сталкиваюсь с проблемами компиляции файла Reference.cs, созданного этим процессом. Я не знаю, делаю ли я это неправильно или есть ошибка, но самый простой способ исправить это - напрямую изменить файл Reference.cs. Проблема обычно связана с пространством имен и может быть устранена с минимальными усилиями. Просто имейте в виду, что это возможность. В этом примере я изменил HelloServiceClient.ServiceReference1 на просто HelloService (вместе с любыми другими необходимыми изменениями).

  • Чтобы приложение MFC могло взаимодействовать со службой WCF, нам необходимо создать управляемую библиотеку DLL-моста C ++. В меню "Файл" выберите пункт меню "Добавить | Новый проект ...". Выберите шаблон проекта C ++ Win32. Измените имя на HelloServiceClientBridge и нажмите кнопку ОК. В разделе «Параметры приложения» измените тип приложения на «DLL» и установите флажок «Пустой проект». Щелкните кнопку Готово.

  • Первое, что нужно сделать, это изменить свойства проекта. Щелкните проект правой кнопкой мыши в обозревателе решений и выберите пункт меню «Свойства». В общих настройках измените выходной каталог на .. \ bin \ Debug и измените параметр Common Language Runtime Support на Common Language Runtime Support (/ clr). В настройках Framework и References добавьте ссылку на сборки .NET System, System.ServiceModel и mscorlib. Щелкните кнопку ОК.

  • Добавьте в проект HelloServiceClientBridge следующие файлы - HelloServiceClientBridge.h, IHelloServiceClientBridge.h и HelloServiceClientBridge.cpp.

  • Измените IHelloServiceClientBridge.h, чтобы он выглядел так:

    #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__
    
  • Измените HelloServiceClientBridge.h, чтобы он выглядел так:

    #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__
    
  • В синтаксисе файла .cpp используется управляемый C ++, к которому нужно привыкнуть. Измените HelloServiceClientBridge.cpp, чтобы он выглядел так:

    #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;
    }
    
  • Осталось только обновить приложение MFC для вызова вызова службы SayHello () WCF. В форме MFC дважды щелкните Say Hello! кнопку для создания обработчика события ButtonClicked. Сделайте так, чтобы обработчик событий выглядел так:

    #include "IHelloServiceClientBridge.h"
    #include <string>
    void CMFCApplicationDlg::OnBnClickedButton1()
    {
        try {
            std::string message = IHelloServiceClientBridge::SayHello("Your Name Here");
            AfxMessageBox(CString(message.c_str()));
        } catch (...) {
        }
    }
    
  • Запустите приложение и нажмите кнопку «Сказать привет!». кнопка. Это приведет к тому, что приложение вызовет метод SayHello () службы WCF Hello, размещенной в службе Windows NT (которая, кстати, все еще должна работать). Затем возвращаемое значение отображается в окне сообщения.

Надеюсь, вы сможете экстраполировать этот простой пример в соответствии со своими потребностями. Если это не сработает, дайте мне знать, и я могу исправить сообщение.

person Matt Davis    schedule 26.03.2009
comment
Мэтт, сначала я хотел поблагодарить вас за всю работу, которую вы потратили на написание этого руководства. Конечно, это было бы полезно для многих, но, к сожалению, не для меня. Я искал неуправляемого вызывающего абонента службы WCF, а не прокси-сервера .NET. У клиента может не быть установленного фреймворка, извините, я не разъяснил - person galets; 30.03.2009
comment
@Matt: Извините, если так много правок. Я думаю, что некоторые из нас столкнулись друг с другом, пытаясь исправить форматирование кода. - person gnovice; 09.01.2010
comment
Спасибо, это именно то, что мне нужно - спасибо за блестящую работу! - person Contango; 05.10.2010
comment
Я думаю, что это должно быть В общих настройках компоновщика добавьте .. \ bin \ Debug в дополнительные каталоги библиотеки. Щелкните кнопку ОК. - person jaccus; 14.11.2012
comment
Спасибо, эта техника мне понравилась. Я очень ценю, что вы делились этими знаниями. Жалко, что sproxy подавляется wsdl, созданным службой WCF, и заставляет нас прибегать к этому запутанному решению. - person PIntag; 12.08.2014
comment
Я сожалею, что у меня есть только один голос "за" за этот ответ. - person Mark Meuer; 15.05.2015

Для тех, кому интересно, я нашел одно наполовину рабочее решение ATL Server. Ниже приведен код хоста, обратите внимание, что он использует BasicHttpBinding, единственный, который работает с сервером ATL:

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

код для InlineXsdInWsdlBehavior можно найти здесь. В InlineXsdInWsdlBehavior необходимо внести одно важное изменение, чтобы он правильно работал со sproxy, когда задействованы сложные типы. Это вызвано ошибкой в ​​sproxy, которая неправильно определяет псевдонимы пространства имен, поэтому wsdl не может иметь повторяющиеся псевдонимы пространства имен, иначе sproxy выйдет из строя. Вот функции, которые нужно изменить:

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

Следующим шагом будет создание заголовка C ++:

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

и тогда программа на C ++ выглядит так:

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;

Результирующий код C ++ довольно прилично обрабатывает сложные типы, за исключением того, что он не может назначать NULL объектам.

person galets    schedule 26.03.2009
comment
Я пробовал этот подход, но sproxy по-прежнему не мог обрабатывать wsdl, сгенерированный моей довольно простой службой. - person PIntag; 12.08.2014

Я бы создал управляемый класс C # для выполнения работы WCF и предоставил бы класс как COM-объект клиентам C ++.

person kenny    schedule 26.03.2009

Вы можете легко реализовать клиент SOAP, используя устаревшую Набор инструментов MS Soap. К сожалению, замены этому, кроме перехода на .NET, нет.

person Eclipse    schedule 26.03.2009
comment
Можете ли вы опубликовать образцы совместимых проектов WCF и наборов инструментов мыла? Я создал простой, и вызов MSSoapInit инструментария мыла не любит wsdl и не скажет мне, что конкретно ему нужно (некоторая служба фиктивной обработки Service1 не обнаружила сообщения с определениями портов) - person galets; 26.03.2009

Можете ли вы опубликовать веб-службу REST и использовать библиотеку MSXML COM - она ​​уже должна быть установлена, имеет синтаксический анализатор XML и библиотеку HTTP.

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

person krisragh MSFT    schedule 16.04.2011
comment
Вы можете сделать так, чтобы ваша служба WCF предлагала как SOAP, так и REST, и использовать конечную точку REST из C ++. См .: stackoverflow.com/questions / 186631 / - person Jesse Chisholm; 21.03.2013