为非托管 C++ 客户端创建 WCF 服务

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/686452/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 16:45:03  来源:igfitidea点击:

Create WCF service for unmanaged C++ clients

c++wcfweb-servicessoapwsdl

提问by galets

I need to get unmanaged Windows C++ clients to talk to a WCF service. C++ clients could be running on Win2000 and later. I have a control over both WCF service and which C++ API is being used. Since it's for a proprietary application, it is preferable to use Microsoft stuff where possible, definitely not GNU licensed APIs. Those of you who have it working, can you share a step-by-step process how to make it working?

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

I have researched following options so far:

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

  • WWSAPI - not good, will not work on Win 2000 clients.
  • ATL Server, used following guideas a reference. I followed the steps outlined (remove policy refs and flatten WSDL), however the resulting WSDL is still not usable by sproxy
  • WWSAPI - 不好,不适用于 Win 2000 客户端。
  • ATL Server,使用以下指南作为参考。我遵循了概述的步骤(删除策略引用并扁平化 WSDL),但是生成的 WSDL 仍然不能被 sproxy 使用

Any more ideas? Please answer only if you actually have it working yourself.

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

Edit1: I apologize for anyone who I might have confused: what I was looking for was a way to call WCF service from client(s) where no .NET framework is installed, so using .NET-based helper library is not an option, it must be pure unmanaged C++

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

采纳答案by galets

For those who are interested, I found one semi-working ATL Server solution. Following is the host code, notice it is using BasicHttpBinding, it's the only one which works with ATL Server:

对于那些感兴趣的人,我找到了一个半工作的 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();

code for InlineXsdInWsdlBehavior could be found here. One important change needs to be done to the InlineXsdInWsdlBehavior in order for it to work properly with sproxy when complex types are involved. It is caused by the bug in sproxy, which does not properly scope the namespace aliases, so wsdl cannot have repeating namespace aliases or sproxy will crap out. Here's the functions which needs to change:

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

Next step is to generate C++ header:

下一步是生成 C++ 头文件:

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

and then C++ program looks like this:

然后 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;

Resulting C++ code handles complex types pretty decently, except that it cannot assign NULL to the objects.

生成的 C++ 代码可以很好地处理复杂类型,只是它不能将 NULL 分配给对象。

回答by Matt Davis

The basic idea is to write the WCF code for your clients in C# (it's just easier this way) and use a C++ bridge dll to bridge the gap between your unmanaged C++ code and the managed WCF code written in C#.

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

Here is the step-by-step process using Visual Studio 2008 along with .NET 3.5 SP1.

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

  1. The first thing to do is create the WCF Service and a means to host it. If you already have this, skip to Step 7 below. Otherwise, create a Windows NT Service following the steps from here. Use the default names offered by VS2008 for the project and any classes that are added to the project. This Windows NT Service will host the WCF Service.

    • Add a WCF Service named HelloService to the project. To do this, right-click the project in the Solution Explorer window and select the Add|New Item... menu item. In the Add New Item dialog, select the C# WCF Service template and click the Add button. This adds the HelloService to the project in the form of an interface file (IHelloService.cs), a class file (HelloService.cs), and a default service configuration file (app.config).

    • Define the HelloService like this:

  1. 首先要做的是创建 WCF 服务和托管它的方法。如果您已经有了这个,请跳到下面的第 7 步。否则,按照此处的步骤创建 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);
        }
    }
  • Modify the Service1 class created in Step 1 above to look like this:

    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 {
            }
        }
    }
    
  • Build the project.

  • Open the Visual Studio 2008 command prompt. Navigate to the output directory for the project. Type the following: `installutil WindowsService1.exe' This installs the Windows NT Service on your local machine. Open the Services control panel and start the Service1 service. It is important to do this in order for Step 9 below to work.

    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.
  • From the File menu, select the Add|New Project... menu item. Select the C# Class Library template. Change the name to HelloServiceClient and click the OK button. Right-click the project in the Solution Explorer and select the Properties menu option. In the Build tab, change the output path to ..\bin\Debug so the assembly and app.config file will be in the same directory as the MFC application. This library will contain the service reference, i.e., the WCF proxy class, to the WCF Hello Service hosted in the Windows NT Service.

  • In the Solution Explorer, right-click the References folder for the HelloServiceClient project and select the Add Service Reference... menu option. In the Address field, type the address of Hello Service. This should be equal to the base address in the app.config file created in Step 2 above. Click the Go button. The Hello Service should show up in the Services list. Click the OK button to automatically generate the proxy class(es) for the Hello Service. NOTE:I seem to always run into compilation problems with the Reference.cs file generated by this process. I don't know if I'm doing it wrong or if there is a bug, but the easiest way to fix this is modify the Reference.cs file directly. The problem is usually a namespacing issue and can be fixed with minimal effort. Just be aware that this is a possibility. For this example, I've changed the HelloServiceClient.ServiceReference1 to simply HelloService (along with any other required changes).

  • To allow the MFC Application to interact with the WCF service, we need to build a managed C++ "bridge" DLL. From the File menu, select the Add|New Project... menu item. Select the C++ Win32 Project template. Change the name to HelloServiceClientBridge and click the OK button. For the Application Settings, change the Application Type to DLL and check the Empty project checkbox. Click the Finish button.

  • The first thing to do is modify the project properties. 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 and change the Common Language Runtime Support option to Common Language Runtime Support (/clr). Under the Framework and References settings, add a reference to the .NET System, System.ServiceModel, and mscorlib assemblies. Click the OK button.

  • Add the following files to the HelloServiceClientBridge project - HelloServiceClientBridge.h, IHelloServiceClientBridge.h, and HelloServiceClientBridge.cpp.

  • Modify the IHelloServiceClientBridge.h to look like this:

    #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__
    
  • Modify the HelloServiceClientBridge.h to look like this:

    #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__
    
  • The syntax for the .cpp file uses managed C++, which takes some getting used to. Modify the HelloServiceClientBridge.cpp to look like this:

    #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;
    }
    
  • The only thing left to do is update the MFC application to invoke the SayHello() WCF service call. On the MFC form, double-click the Say Hello! button to generate the ButtonClicked event handler. Make the event handler look like this:

    #include "IHelloServiceClientBridge.h"
    #include <string>
    void CMFCApplicationDlg::OnBnClickedButton1()
    {
        try {
            std::string message = IHelloServiceClientBridge::SayHello("Your Name Here");
            AfxMessageBox(CString(message.c_str()));
        } catch (...) {
        }
    }
    
  • Run the application and click the Say Hello! button. This will cause the application to invoke the SayHello() method of the WCF Hello Service hosted in the Windows NT Service (which should still be running, by the way). The return value is then displayed in a message box.

  • 将上面第 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++ 常规设置下,将 ..\HelloServiceClientBridge 添加到附加包含目录。在链接器常规设置下,将 ..\Debug 添加到其他库目录。单击确定按钮。
  • 从文件菜单中,选择添加|新建项目...菜单项。选择 C# 类库模板。将名称更改为 HelloServiceClient 并单击 OK 按钮。在解决方案资源管理器中右键单击项目并选择属性菜单选项。在“生成”选项卡中,将输出路径更改为 ..\bin\Debug,以便程序集和 app.config 文件与 MFC 应用程序位于同一目录中。该库将包含对承载在 Windows NT 服务中的 WCF Hello 服务的服务引用,即 WCF 代理类。

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

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

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

Hopefully you can extrapolate from this simple example to fit your needs. If this does not work, please let me know so I can fix the post.

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

回答by kenny

I would create a C# managed class to do the WCF work and expose the class as a COM object to the C++ clients.

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

回答by Eclipse

You can implement a SOAP client somewhat easily using the deprecated MS Soap Toolkit. Unfortunately, there doesn't seem to be a replacement for this outside of moving to .NET.

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

回答by krisragh MSFT

Can you publish a REST Web Service and use the MSXML COM library -- should be already installed, has an XML parser, and an HTTP library.

你能发布一个 REST Web 服务并使用 MSXML COM 库吗——应该已经安装了,有一个 XML 解析器和一个 HTTP 库。

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

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