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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (33)

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

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

  • WWSAPI - 不好,不适用于Win 2000客户端。
  • ATL Server,使用以下指南作为参考。我遵循了概述的步骤(删除策略引用并将WSDL扁平化),但是生成的WSDL仍然无法使用sproxy

还有什么想法?请仅在您自己确实有效时才回答。

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

提问于
用户回答回答于

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

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分配给对象。

用户回答回答于

基本的想法是用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服务添加到项目中。为此,请右键单击Solution Explorer窗口中的项目,然后选择Add | New Item ...菜单项。在Add New Item对话框中,选择C#WCF Service模板并单击Add按钮。这将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 ++常规设置下,将.. \ HelloServiceClientBridge添加到其他包含目录。在链接器常规设置下,将.. \ Debug添加到其他库目录。点击确定按钮。

  • 从文件菜单中,选择添加|新建项目...菜单项。选择C#类库模板。将名称更改为HelloServiceClient,然后单击确定按钮。右键单击解决方案资源管理器中的项目,然后选择“属性”菜单选项。在Build选项卡中,将输出路径更改为.. \ bin \ Debug,以使程序集和app.config文件与MFC应用程序位于同一目录中。该库将包含服务引用(即WCF代理类)到Windows NT Service中托管的WCF Hello Service。
  • 在解决方案资源管理器中,右键单击HelloServiceClient项目的引用文件夹,然后选择添加服务引用...菜单选项。在地址字段中,输入Hello Service的地址。这应该等于上面步骤2中创建的app.config文件中的基地址。点击开始按钮。Hello服务应显示在服务列表中。单击确定按钮以自动生成Hello服务的代理类。 注意: 我似乎总是遇到由该进程生成的Reference.cs文件的编译问题。我不知道我是否做错了或者是否存在错误,但解决此问题的最简单方法是直接修改Reference.cs文件。问题通常是一个命名空间问题,可以通过最小的努力来解决。请注意,这是一种可能性。在这个例子中,我将HelloServiceClient.ServiceReference1更改为HelloService(以及其他所需的更改)。
  • 为了允许MFC应用程序与WCF服务进行交互,我们需要构建一个托管的C ++“桥”DLL。从文件菜单中,选择添加|新建项目...菜单项。选择C ++ Win32项目模板。将名称更改为HelloServiceClientBridge并单击确定按钮。对于应用程序设置,将应用程序类型更改为DLL并选中空项目复选框。点击完成按钮。
  • 首先要做的是修改项目属性。右键单击解决方案资源管理器中的项目,然后选择“属性”菜单选项。在General设置下,将输出目录更改为.. \ bin \ Debug,并将Common Language Runtime Support选项更改为Common Language Runtime Support(/ 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 (...) { } }
  • 运行该应用程序并单击说你好!按钮。这将导致应用程序调用Windows NT服务中托管的WCF Hello Service的SayHello()方法(顺便说一句,它应该仍在运行)。返回值然后显示在消息框中。

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

扫码关注云+社区