Создать службу WCF для неуправляемых клиентов C ++ - PullRequest
59 голосов
/ 26 марта 2009

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

Я исследовал следующие варианты:

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

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

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

Ответы [ 5 ]

58 голосов
/ 27 марта 2009

Основная идея - написать код 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. Откройте другой экземпляр Visual Studio 2008 и создайте приложение MFC, которое находится на таком же расстоянии, как вы можете получить от WCF. В качестве примера я просто создал диалоговое приложение MFC и добавил Say Hello! кнопка к нему. Щелкните правой кнопкой мыши проект в обозревателе решений и выберите пункт меню «Свойства». В общих настройках измените каталог вывода на .. \ bin \ Debug. Под общими настройками C / C ++ добавьте .. \ HelloServiceClientBridge в дополнительные каталоги включения. Под общими настройками компоновщика добавьте .. \ Debug в каталоги дополнительных библиотек. Нажмите кнопку ОК.
  • В меню Файл выберите пункт меню Добавить | Новый проект .... Выберите шаблон библиотеки классов C #. Измените имя на HelloServiceClient и нажмите кнопку ОК. Щелкните правой кнопкой мыши проект в обозревателе решений и выберите пункт меню «Свойства». На вкладке «Сборка» измените путь вывода на .. \ bin \ Debug, чтобы файл сборки и файл app.config находились в том же каталоге, что и приложение MFC. Эта библиотека будет содержать ссылку на службу, то есть прокси-класс WCF, на службу Hello WCF, размещенную в службе Windows NT.

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

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

  • Первое, что нужно сделать, это изменить свойства проекта. Щелкните правой кнопкой мыши проект в обозревателе решений и выберите пункт меню «Свойства». В общих настройках измените выходной каталог на .. \ bin \ Debug и измените опцию Common Language Runtime Support на Common Language Runtime Support (/ clr). В рамках и Настройки ссылок, добавьте ссылку на сборки .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 для вызова WCF SayHello () сервисный звонок. В форме 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 (...) {
        }
    }
    
  • Запустите приложение и нажмите Say Hello! кнопка. Это приведет к тому, что приложение вызовите метод SayHello () службы Hello WCF, размещенной в службе Windows NT (кстати, она все еще должна работать). Возвращаемое значение затем отображается в окне сообщения.

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

12 голосов
/ 27 марта 2009

Для тех, кто заинтересован, я нашел одно полуработающее решение 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.

2 голосов
/ 27 марта 2009

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

1 голос
/ 26 марта 2009

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

0 голосов
/ 17 апреля 2011

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

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

...