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

Posted

技术标签:

【中文标题】为非托管 C++ 客户端创建 WCF 服务【英文标题】:Create WCF service for unmanaged C++ clients 【发布时间】: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++ 中很容易解析)。见:***.com/questions/186631/… 【参考方案1】:

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

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

    首先要做的是创建 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 步起作用,这样做很重要。

    打开另一个 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("tns0", 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

【讨论】:

您可以让您的 WCF 服务同时提供 SOAP 和 REST,并使用 C++ 中的 REST 端点。见:***.com/questions/186631/…

以上是关于为非托管 C++ 客户端创建 WCF 服务的主要内容,如果未能解决你的问题,请参考以下文章

在 c++ 中为非托管 c# dll 使用 std::string

WCF 服务调用具有复杂数据类型的非托管 c++ dll

混合模式进程与托管到非托管 IPC

为非托管 (C++) 代码编写托管包装器 - 自定义类型/结构

使用 ATL/COM 将托管字节 [] 转换为非托管字节数组

为非托管代码提供托管控制句柄 - 访问冲突