【问题标题】:Create WCF service for unmanaged C++ clients为非托管 C++ 客户端创建 WCF 服务
【发布时间】:2010-10-15 17:55:13
【问题描述】:

我需要让非托管 Windows C++ 客户端与 WCF 服务通信。 C++ 客户端可以在 Win2000 及更高版本上运行。我可以控制 WCF 服务和正在使用的 C++ API。由于它是用于专有应用程序,因此最好尽可能使用 Microsoft 的东西,绝对不是 GNU 许可的 API。那些已经工作的人,你能分享一个如何让它工作的分步过程吗?

到目前为止,我已经研究了以下选项:

  • WWSAPI - 不好,不适用于 Win 2000 客户端。
  • ATL 服务器,使用following guide 作为参考。我遵循了概述的步骤(删除策略引用并展平 WSDL),但是生成的 WSDL 仍然不能被 sproxy 使用

还有什么想法吗?请仅在您自己实际使用时才回答。

Edit1:我为可能让我感到困惑的任何人道歉:我正在寻找一种从没有 .NET 的客户端调用 WCF 服务的方法框架已安装,因此不能使用基于 .NET 的帮助程序库,它必须是纯非托管 C++

【问题讨论】:

  • 抱歉耽搁了。我已经更新了我的答案。希望对您有所帮助。
  • 您可以修改 WCF 服务以同时提供 SOAP 和 REST 端点,然后使用 C++ 中的 REST 端点。 (只要您的数据类型在 C++ 中很容易解析)。见:stackoverflow.com/questions/186631/…

标签: c++ wcf web-services soap wsdl


【解决方案1】:

基本思想是用 C# 为您的客户端编写 WCF 代码(这样更容易),并使用 C++ 桥接 dll 来弥合非托管 C++ 代码和用 C# 编写的托管 WCF 代码之间的差距。

这是使用 Visual Studio 2008 和 .NET 3.5 SP1 的分步过程。

  1. 首先要做的是创建 WCF 服务和托管它的方法。如果您已经有这个,请跳到下面的第 7 步。否则,按照here 中的步骤创建一个Windows NT 服务。使用 VS2008 为项目和添加到项目中的任何类提供的默认名称。此 Windows NT 服务将托管 WCF 服务。

    • 将名为 HelloService 的 WCF 服务添加到项目中。为此,右键单击解决方案资源管理器窗口中的项目并选择添加|新项目...菜单项。在“添加新项”对话框中,选择 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);
        }
    }
  • 修改上面第 1 步中创建的 Service1 类,如下所示:

    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++ General 设置下,将 ..\HelloServiceClientBridge 添加到 Additional Include Directories。在 Linker General 设置下,将 ..\Debug 添加到 Additional Library Directories。点击确定按钮。
  • 从“文件”菜单中,选择“添加|新建项目...”菜单项。选择 C# 类库模板。将名称更改为 HelloServiceClient 并单击 OK 按钮。右键单击解决方案资源管理器中的项目,然后选择属性菜单选项。在 Build 选项卡中,将输出路径更改为 ..\bin\Debug,这样程序集和 app.config 文件将与 MFC 应用程序位于同一目录中。此库将包含对托管在 Windows NT 服务中的 WCF Hello 服务的服务引用,即 WCF 代理类。

  • 在解决方案资源管理器中,右键单击 HelloServiceClient 项目的 References 文件夹,然后选择 Add Service Reference... 菜单选项。在地址字段中,输入 Hello Service 的地址。这应该等于上面第 2 步中创建的 app.config 文件中的基地址。单击“开始”按钮。 Hello 服务应显示在服务列表中。点击确定按钮自动生成 Hello 服务的代理类。 注意: 我似乎总是遇到此过程生成的 Reference.cs 文件的编译问题。我不知道我做错了还是有错误,但解决这个问题的最简单方法是直接修改 Reference.cs 文件。这个问题通常是一个命名空间问题,可以用最少的努力来解决。请注意,这是一种可能性。对于此示例,我已将 HelloServiceClient.ServiceReference1 更改为简单的 HelloService(以及任何其他所需的更改)。

  • 为了允许 MFC 应用程序与 WCF 服务交互,我们需要构建一个托管 C++“桥”DLL。从 File 菜单中,选择 Add|New Project... 菜单项。选择 C++ Win32 项目模板。将名称更改为 HelloServiceClientBridge 并单击 OK 按钮。对于应用程序设置,将应用程序类型更改为 DLL 并选中空项目复选框。点击完成按钮。

  • 首先要做的是修改项目属性。右键单击解决方案资源管理器中的项目,然后选择属性菜单选项。在常规设置下,将输出目录更改为 ..\bin\Debug,并将公共语言运行时支持选项更改为公共语言运行时支持 (/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 应用程序以调用 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 (...) {
        }
    }
    
  • 运行应用程序并单击 Say Hello!按钮。这将导致应用程序 调用托管在 Windows NT 服务中的 WCF Hello 服务的 SayHello() 方法(顺便说一下,它应该仍在运行)。然后返回值显示在消息框中。

希望您可以从这个简单的示例中推断出满足您的需求。如果这不起作用,请告诉我,以便我修复帖子。

【讨论】:

  • Matt,首先我要感谢您为编写本指南所做的所有工作。它肯定对许多人有用,但遗憾的是对我没有用。我正在寻找的是 WCF 服务的纯非托管调用者,而不是 .NET 代理。客户端可能没有安装框架,抱歉我没说清楚
  • @Matt:很抱歉,如果有这么多的编辑。我认为我们中的一些人在尝试修复代码格式时遇到了对方。
  • 谢谢,这正是我所需要的 - 感谢您的出色工作!
  • 我认为应该是“在链接器常规设置下,将 ..\bin\Debug 添加到附加库目录。点击确定按钮。”
  • 谢谢 - 这种技术对我很有效。我非常感谢您分享这些知识。遗憾的是,sproxy 阻塞了 WCF 服务生成的 wsdl,让我们不得不求助于这种复杂的解决方案。
【解决方案2】:

对于那些感兴趣的人,我找到了一个半工作的 ATL Server 解决方案。以下是宿主代码,注意它使用的是 BasicHttpBinding,它是唯一一个与 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();

可以在 here 找到 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 分配给对象。

【讨论】:

  • 我尝试了这种方法,但 sproxy 仍然无法处理由我非常简单的服务生成的 wsdl。
【解决方案3】:

我将创建一个 C# 托管类来完成 WCF 工作并将该类作为 COM 对象公开给 C++ 客户端。

【讨论】:

    【解决方案4】:

    您可以使用已弃用的MS Soap Toolkit 轻松实现 SOAP 客户端。不幸的是,除了迁移到 .NET 之外,似乎没有替代品。

    【讨论】:

    • 您能发布一个可互操作的 WCF 和 soap 工具包项目的示例吗?我创建了一个简单的,soap 工具包 MSSoapInit 调用不喜欢 wsdl,并且不会告诉我它具体需要什么(一些虚假的“处理服务 Service1 未找到端口定义”消息)
    【解决方案5】:

    您能否发布 REST Web 服务并使用 MSXML COM 库 - 应该已经安装,具有 XML 解析器和 HTTP 库。

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多