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

gal*_*ets 59 c++ wcf soap wsdl web-services

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

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

  • WWSAPI - 不好,不适用于Win 2000客户端.
  • ATL Server,以下指南作为参考.我按照概述的步骤(删除策略引用并展平WSDL),但是生成的WSDL仍然不能被sproxy使用

还有什么想法吗?只有在你真正让自己工作的时候才能回答.

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

Mat*_*vis 58

基本思想是在C#中为客户端编写WCF代码(这种方式更简单)并使用C++桥接DLL来弥合非托管C++代码与用C#编写的托管WCF代码之间的差距.

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

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

    • 将名为HelloService的WCF服务添加到项目中.要执行此操作,请在"解决方案资源管理器"窗口中右键单击项目,然后选择"添加|新项..."菜单项.在"添加新项"对话框中,选择C#WCF服务模板,然后单击"添加"按钮.这会以接口文件(IHelloService.cs),类文件(HelloService.cs)和默认服务配置文件(app.config)的形式将HelloService添加到项目中.

    • 像这样定义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);
        }
    }
Run Code Online (Sandbox Code Playgroud)
  • 修改上面步骤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 {
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 建立项目.

  • 打开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代理类)到Windows NT服务中托管的WCF Hello Service.

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

  • 为了允许MFC应用程序与WCF服务交互,我们需要构建一个托管C++"桥"DLL.从"文件"菜单中,选择"添加"|"新建项目..."菜单项.选择C++ Win32项目模板.将名称更改为HelloServiceClientBridge,然后单击"确定"按钮.对于Application Settings,将Application Type更改为DLL并选中Empty project复选框.单击"完成"按钮.

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

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

  • 马特,首先,我想感谢你撰写本指南所花费的所有工作.它肯定对许多人有用,但遗憾的是对我不利.我正在寻找的是WCF服务的纯粹非托管调用者,而不是.NET代理.客户端可能没有安装框架,抱歉我没说清楚 (2认同)

gal*_*ets 12

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

可以在此处找到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);
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

下一步是生成C++标头:

sproxy.exe /wsdl http://localhost:8200/Service1?wsdl
Run Code Online (Sandbox Code Playgroud)

然后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;
Run Code Online (Sandbox Code Playgroud)

得到的C++代码非常合理地处理复杂类型,除了它不能为对象分配NULL.