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

h5qlskok  于 2022-11-15  发布在  其他
关注(0)|答案(5)|浏览(182)

我需要让非托管的Windows C客户端与WCF服务进行对话。C客户端可以在Win2000或更高版本上运行。我可以控制WCF服务和正在使用的C++ API。由于它是用于专有应用程序,因此最好尽可能使用Microsoft的东西,而不是GNU许可的API。那些使用它的人,您能否分享一个如何使其工作的逐步过程?
到目前为止,我已经研究了以下选项:

  • WWSAPI -不好,无法在Win 2000客户端上工作。
  • ATL Server,使用following guide作为引用。我遵循了概述的步骤(删除策略引用并扁平化WSDL),但是得到的WSDL仍然不能被sproxy使用

还有其他想法吗?只有在你自己真的有效果的情况下才能回答。

编辑1:我为我可能混淆的人道歉:我所寻找的是一种从未安装.NET框架的客户端调用WCF服务的方法,因此不能使用基于. NET的帮助器库,它必须是纯非托管C++

yv5phkfx

yv5phkfx1#

基本思想是用C#为客户端编写WCF代码(这种方式更简单),并使用C桥DLL在非托管C代码和用C#编写的托管WCF代码之间架起差距。
下面是使用Visual Studio 2008和.NET 3.5 SP1的分步过程。
1.首先要做得是创建WCF服务与承载它得方法.如果您已经有了WCF服务与承载它得方法,请跳到下面得步骤7.否则,请按照此处得步骤创建Windows NT服务.使用VS 2008为项目与添加到项目中得任何类提供得默认名称.此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中创建的Service 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服务。请打开“服务”控制面板并启动Service 1服务。要使下面的步骤9正常工作,执行此操作非常重要。

1.打开Visual Studio 2008的另一个示例并创建一个MFC应用程序,这与WCF几乎是一样的。例如,我只是创建了一个对话框MFC应用程序,并向其添加了一个Say Hello!按钮。在解决方案资源管理器中右键单击该项目,然后选择“属性”菜单选项。在“常规”设置下,将输出目录更改为..\bin\Debug。在“C/C++常规”设置下,将..\HelloServiceClientBridge添加到其他包含目录。在“链接器常规”设置下,将..\Debug添加到其他库目录。单击“确定”按钮。

  • 从“文件”菜单中,选择“添加|“新建项目...”菜单项。选择C#类库模板。将名称更改为HelloServiceClient,然后单击“确定”按钮。在解决方案资源管理器中右键单击该项目,然后选择“属性”菜单选项。在“生成”选项卡中,将输出路径更改为..\bin\Debug,以便程序集和app.config文件与MFC应用程序位于同一目录中。此库将包含服务引用、即WCF代理类到WindowsNT服务中托管的WCF Hello服务。
  • 在“解决方案资源管理器”中,右键单击HelloServiceClient项目的“引用”文件夹,然后选择“添加服务引用...”菜单选项。在“地址”字段中,键入Hello服务的地址。2该地址应该等于在上面的步骤2中创建的app.config文件中的基址。3单击“开始”按钮。4 Hello服务应该显示在“服务”列表中。按一下[确定]按钮,自动产生Hello服务的Proxy类别。***注意:***我似乎总是在这个程序所产生的Reference.cs档案中遇到编译问题。我不知道是我做错了还是有错误,但修正这个问题最简单的方法是直接修改Reference.cs档案。这个问题通常是命名空间的问题,而且可以用最少的工作来修正。请注意,这是可能的。在这个范例中,我已经将HelloServiceClient.ServiceReference1变更为HelloService(沿着任何其他必要的变更)。
  • 要允许MFC应用程序与WCF服务交互,我们需要生成一个托管C++“桥”DLL。|“新建项目...”茶杯项.选择C++ Win32项目模板.将名称更改为HelloServiceClientBridge,然后单击“确定”按钮.对于“应用程序设置,”将“应用程序类型”更改为DLL,然后选中“空项目”复选框.单击“完成”按钮.
  • 要做的第一件事是修改项目属性。在解决方案资源管理器中右键单击项目,然后选择“属性”菜单选项。在“常规”设置下,将“输出目录”更改为..\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__
  • 将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 Service的SayHello()方法(顺便说一下,该服务应仍在运行)。然后,返回值将显示在消息框中。

希望你能从这个简单的例子中推断出适合你的需要。如果这不起作用,请让我知道,这样我就可以修复这个帖子。

hujrc8aj

hujrc8aj2#

对于那些感兴趣的人,我找到了一个半工作的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赋给对象。

jucafojl

jucafojl3#

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

mnemlml8

mnemlml84#

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

zkure5ic

zkure5ic5#

您是否可以发布REST Web服务并使用MSXML COM库--应该已经安装,具有XML解析器和HTTP库。
http://msdn.microsoft.com/en-us/library/ms763742.aspx

相关问题