Microsoft .NET Remoting:技术概述

.NET Romoting 而在Remoting客户端获得的只是远程对象的一个引用,并不是远程对象本身,对远程对象操作是在服务端完成的。关键就在这点,如果你了解分布式架构的关键你就该明白了,Remoting是处理分布式的可行方案。Remoting 支持许多状态管理选项,并且可能与来自同一个用户的多个调用相关或不相关,这取决于您选择的对象生命周期架构。然而在某些情况下,我们需要跨过应用程序域,与另外的应用程序域进行通信,即穿越边界。通过上面的操作**,服务端已经为了开启了远程调用通道,并且通道里提供我们需要的远程对象**, 阅读详情

简介

Microsoft® .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这种框架提供了多种服务,包括激活和生存期支持,以及负责与远程应用程序进行消息传输的通讯通道。格式化程序用于在消息通过通道传输之前,对其进行编码和解码。应用程序可以在注重性能的场合使用二进制编码,在需要与其他远程处理框架进行交互的场合使用 XML 编码。在从一个应用程序域向另一个应用程序域传输消息时,所有的 XML 编码都使用 SOAP 协议。出于安全性方面的考虑,远程处理提供了大量挂钩,使得在消息流通过通道进行传输之前,安全接收器能够访问消息和序列化流。

通常,如果没有底层框架的支持,管理远程对象的生存期会非常麻烦。.NET Remoting 提供了许多可供选择的生存期模型,这些模型分为两个类别:

  • 客户端激活对象

  • 服务器激活对象

客户端激活对象受基于租用的生存期管理器的控制,这种管理器确保了租用期满时对象可被回收。而对于服务器激活对象,开发人员则可以选择“单一调用”模式或“单一元素”模式。

远程对象

任何远程处理框架的主要目的之一就是要提供必要的基础结构,以便隐藏远程对象调用方法和返回结果的复杂性。任何位于调用方应用程序域之外的对象,即使在同一台计算机上执行,也会被认为是远程对象。在应用程序域内部,原始数据类型按数值传递,而所有的对象按引用传递。因为本地对象引用仅在创建对象的应用程序域内有效,所以它们不能以这种方式传递到远程方法调用或从远程方法调用返回。所有必须跨越应用程序域的本地对象都必须按数值来传递,并且应该用 [serializable] 自定义属性作标记,否则它们必须实现 ISerializable 接口。对象作为参数传递时,框架将该对象序列化并传输到目标应用程序域,对象将在该目标应用程序域中被重新构造。无法序列化的本地对象将不能传递到其他应用程序域中,因而也不能远程处理。

通过从 MarshalByRefObject 导出对象,您可以使任一对象变为远程对象。当某个客户端激活一个远程对象时,它将接收到该远程对象的代理。对该代理的所有操作都被适当地重新定向,使远程处理基础结构能够正确截取和转发调用。尽管这种重新定向对性能有一些影响,但 JIT 编译器和执行引擎 (EE) 已经优化,可以在代理和远程对象驻留在同一个应用程序域中时,防止不必要的性能损失。如果代理和远程对象不在同一个应用程序域中,则堆栈中的所有方法调用参数会被转换为消息并被传输到远程应用程序域,这些消息将在该远程应用程序域中被转换为原来的堆栈帧,同时该方法调用也会被调用。从方法调用中返回结果时也使用同一过程。

代理对象

代理对象是在客户端激活远程对象时创建的。作为远程对象的代表,代理对象确保对代理进行的所有调用都能够转发到正确的远程对象实例。为了准确理解代理对象的工作方式,我们需要更深入地研究它们。当某个客户端激活一个远程对象时,框架将创建 TransparentProxy 类的一个本地实例(该类中包含所有类的列表与远程对象的接口方法)。因为 TransparentProxy 类在创建时用 CLR 注册,所以代理上的所有方法调用都被运行时截取。这时系统将检查调用,以确定其是否为远程对象的有效调用,以及远程对象的实例是否与代理位于同一应用程序域中。如果对象在同一个应用程序域中,则简单方法调用将被路由到实际对象;如果对象位于不同的应用程序域中,将通过调用堆栈中的调用参数的 Invoke 方法将其打包到 IMessage 对象并转发到 RealProxy 类中。此类(或其内部实现)负责向远程对象转发消息。TransparentProxy 类和 RealProxy 类都是在远程对象被激活后在后台创建的,但只有 TransparentProxy 返回到客户端。

要更好地理解这些代理对象,我们需要简要介绍一下 ObjRef。激活一节中有关于 ObjRef 的详细说明。以下方案简要说明了 ObjRef 与这两个代理类的关联方式。但请注意,这只是关于该进程的一个极其概括的说明;根据对象是客户端激活对象还是服务器激活对象,以及它们是单一元素对象还是单一调用对象,该进程会有所不同。

  • 远程对象注册在远程计算机的应用程序域中。远程对象被封送以生成 ObjRef。ObjRef 包含了从网络上的任意位置定位和访问远程对象所需的所有信息,包括:类的增强名称、类的层次结构(其父类)、类实现的所有接口的名称、对象 URI 和所有已注册的可用通道的详细信息。在接收到对某个远程对象的请求时,远程处理框架使用对象 URI 来检索为该对象创建的 ObjRef 实例。

  • 客户端通过调用 new 或某个 Activator 函数(例如 CreateInstance)来激活远程对象。对于服务器激活对象,远程对象的 TransparentProxy 将在客户端应用程序域中生成并返回到客户端,这时不执行任何远程调用。只有在客户端调用远程对象的某个方法时,该远程对象才会被激活。此方案明显不适合客户端激活对象,因为客户端希望框架只在得到请求时才激活对象。当客户端调用某个激活方法时,客户端上会创建一个激活代理,并且将使用 URL 和对象 URI 作为终结点在服务器的远程激活器上初始化一个远程调用。远程激活器激活该对象,然后 ObjRef 流向客户端,并被取消封送以生成一个返回给客户端的 TransparentProxy。

  • 取消封送的过程中会分析 ObjRef 以提取远程对象的方法信息,同时还会创建 TransparentProxy 和 RealProxy 对象。在用 CLR 注册 TransparentProxy 之前,分析后的 ObjRef 内容会被添加到 TransparentProxy 的内部表中。

TransparentProxy 是一种无法替代和扩展的内部类,而 RealProxy 和 ObjRef 类则属于公共类,可以在必要时进行扩展和自定义。因为 RealProxy 类能够处理远程对象的所有函数调用,所以它是执行负载平衡等操作的理想方法。调用 Invoke 时,从 RealProxy 导出的类可以获得网络中服务器的负载信息,并将该调用路由到适当的服务器。简单地为所需的 ObjectURI 从通道请求一个 MessageSink,并调用 SyncProcessMessage 或 AsyncProcessMessage 以将该调用转发至所需的远程对象。当调用返回时,通过调用 RemotingServices 类的 PropagateMessageToProxy 将返回参数推回到堆栈中。

下面的代码片断显示了如何使用导出的 RealProxy 类。

   MyRealProxy proxy = new MyRealProxy(typeof(Foo));
   Foo obj = (Foo)proxy.GetTransparentProxy();
   int result = obj.CallSomeMethod();

上例中获取的 TransparentProxy 可以被转发到另一个应用程序域中。当第二个客户端试图调用代理上的某个方法时,远程处理框架会尝试创建 MyRealProxy 类的实例,并且如果程序集可用,所有的调用都会路由至此实例。如果程序集不可用,调用会路由至默认的远程 RealProxy。

通过为默认的 ObjRef 属性 TypeInfo、EnvoyInfo 和 ChannelInfo 提供替代,可以很容易地自定义 ObjRef。下列代码显示了如何进行自定义:

public class ObjRef {
  public virtual IRemotingTypeInfo TypeInfo 
  {
    get { return typeInfo;}
    set { typeInfo = value;}
  }

  public virtual IEnvoyInfo EnvoyInfo
  {
    get { return envoyInfo;}
    set { envoyInfo = value;}
  }

  public virtual IChannelInfo ChannelInfo 
  {
    get { return channelInfo;}
    set { channelInfo = value;}
  }
}

通道

通道用于在远程对象之间传输消息。当客户端调用某个远程对象上的方法时,与该调用相关的参数以及其他详细信息会通过通道传输到远程对象。调用的任何结果都会以同样的方式返回给客户端。客户端可以选择“服务器”中注册的任一通道,以实现与远程对象之间的通讯,因此开发人员可以自由选择最适合需要的通道。当然,也可以自定义任何现有的通道或创建使用其他通讯协议的新通道。通道选择遵循以下规则:

  • 在能够调用远程对象之前,远程处理框架必须至少注册一个通道。通道注册必须在对象注册之前进行。

  • 通道按应用程序域注册。一个进程中可以有多个应用程序域。当进程结束时,该进程注册的所有通道将被自动清除。

  • 多次注册侦听同一端口的通道是非法的。即使通道按应用程序域注册,同一计算机上的不同应用程序域也不能注册侦听同一端口的通道。

  • 客户端可以使用任何已注册的通道与远程对象通讯。当客户端试图连接至某个远程对象时,远程处理框架会确保该对象连接至正确的通道。客户端负责在尝试与远程对象通讯之前调用 ChannelService 类的 RegisterChannel。

所有的通道都由 IChannel 导出,并根据通道的用途实现 IChannelReceiver 或 IchannelSender。大多数通道既实现了接收器接口,又实现了发送器接口,使它们可以在两个方向上通讯。当客户端调用代理上的某个方法时,远程处理框架会截取该调用并将其转为要发送到 RealProxy 类(或一个实现 RealProxy 类的实例)的消息。RealProxy 将消息转发到消息接收器以进行处理。消息接收器负责与远程对象注册的通道之间建立连接,并通过通道(在不同的应用程序域)将消息从调度位置传输到远程对象本身。激活了一个远程对象后,客户端会通过调用选定通道上的 CreateMessageSink 来选择通道,并从其上检索能够与远程对象通讯的消息接收器。

远程处理框架的一个容易混淆的方面是远程对象和通道之间的关系。例如,如果 SingleCall 远程对象只在被调用时才激活,那么该对象如何侦听要连接的客户端?

部分答案在于这样一个事实:远程对象并不拥有自己的通道,而是共享通道。作为远程对象宿主的服务器应用程序必须注册要通过远程处理框架公开的对象以及所需的通道。注册后的通道会自动开始在指定的端口侦听客户请求。注册远程对象后,会为该对象创建一个 ObjRef 并将其存储在表中。当通道上传来一个请求时,远程处理框架会检查该消息以确定目标对象,同时检查对象引用表以定位表中的引用。如果找到了对象引用,将从表中检索框架目标对象或在必要时将其激活,然后框架将调用转发至该对象。对于同步调用,在消息调用期间会一直维持来自客户端的连接。因为每个客户端连接都在自己的线程上处理,所以一个通道可以同时服务于多个客户端。

生成商务应用时,安全性是一个重要问题。要满足商务要求,开发人员必须能给远程方法调用添加诸如授权或加密等安全特性。为了实现这一目标,开发人员可以自定义通道,使其能够对与远程对象之间的实际消息传输机制进行控制。在传输到远程应用程序之前,所有的消息都必须流过 SecuritySink、TransportSink 和 FormatterSink,且这些消息传递到远程应用程序后会以相反次序流过同样的接收器。

HTTP 通道

HTTP 通道使用 SOAP 协议与远程对象传输消息。所有的消息流过 SOAP 格式化程序时都被转换为 XML 格式且被序列化,所需的 SOAP 头也会被添加到该流中。您也可以指定能够生成二进制数据流的二进制格式化程序。然后,数据流会使用 HTTP 协议传输到目标 URI。

TCP 通道

TCP 通道使用二进制格式化程序将所有的消息序列化为二进制流,并使用 TCP 协议将其传输到目标 URI。

激活

远程处理框架支持远程对象的服务器激活和客户端激活。不需要远程对象在方法调用之间维护任何状态时,一般使用服务器激活。服务器激活也适用于多个客户端调用方法位于同一对象实例上、且对象在函数调用之间维持状态的情况。另一方面,客户端激活对象从客户端实例化,并且客户端通过使用基于租用的专用系统来管理远程对象的生存期。

在可以接受客户端的访问之前,所有的远程对象都必须用远程处理框架注册。对象注册一般由宿主应用程序来完成。宿主应用程序将启动,使用 ChannelServices 注册一个或多个通道,使用 RemotingServices 注册一个或多个远程对象,然后等待被终止。请注意,已注册的通道和对象只有在用来注册它们的进程活动时才可以使用。如果退出了该进程,则会自动从远程处理服务中删除它注册的所有通道和对象。在框架中注册远程对象时,需要以下四项信息:

  1. 包含类的程序集名称。

  2. 远程对象的类型名称。

  3. 客户端定位对象时将使用的对象 URI。

  4. 服务器激活所需的对象模式。该模式可以是 SingleCall,也可以是 Singleton。

远程对象可以通过下列两种方式注册:调用 RegisterWellKnownType,将上述信息作为参数传递;或将上述信息存储在配置文件中,然后调用 ConfigureRemoting 并将该配置文件的名称作为参数传递。以上两种方法执行的功能相同,因此您可以使用它们中的任意一种来注册远程对象。当然,后一种方法更方便些,因为无需重新编译宿主应用程序即可改变配置文件的内容。以下代码片断显示了如何将 HelloService 类注册为 SingleCall 远程对象。

RemotingServices.RegisterWellKnownType(
  "server",
  "Samples.HelloServer",
  "SayHello",
   WellKnownObjectMode.SingleCall);

其中,“server”是程序集的名称,HelloServer 是类的名称,SayHello 是对象 URI。

注册了远程对象后,框架将为该对象创建一个对象引用,然后从程序集中提取与该对象相关的必要元数据。随后,这一信息将与 URI 和程序集名称一起存储在对象引用中(该对象引用将被写入一个用于跟踪已注册远程对象的远程处理框架表中)。请注意,除了在客户端试图调用对象上的某个方法或从客户端激活对象时以外,注册进程不会实例化远程对象自身。

现在,任何知道该对象 URI 的客户端都可以使用 ChannelServices 注册通道,并调用 new、GetObject 或 CreateInstance 激活对象,从而获得该对象的一个代理。以下代码片断显示了该操作的示例:

      ChannelServices.RegisterChannel(new TCPChannel);
      HelloServer obj = (HelloServer)Activator.GetObject(
         typeof(Samples.HelloServer), "tcp://localhost:8085/SayHello");

其中,“tcp://localhost:8085/SayHello”表示我们希望在端口 8085 上使用 TCP 协议连接到位于 SayHello 终结点的远程对象。在编译该客户端代码时,编译器明显会要求关于 HelloServer 类的类型信息。该信息可以通过以下方式之一来提供:

  • 提供对 HelloService 类所在程序集的引用。

  • 将远程对象拆分为实现和接口类,并在编译客户端时引用这些接口。

  • 使用 SOAPSUDS 工具直接从终结点提取所需的元数据。此工具将连接至所提供的终结点,提取元数据,然后生成可用于编译客户端的程序集或源代码。

GetObject 或 new 可用于服务器激活对象。请注意,使用这两个调用时不会实例化对象,实际上不会生成任何网络调用。框架从元数据获得了创建代理所需的足够信息,但并未连接到远程对象上。只有在客户端调用代理上的某个方法时才会建立网络连接。当调用抵达服务器时,框架将从消息中提取 URI,检查远程处理框架表以便定位与 URI 匹配的对象引用,然后在必要时将对象实例化,并将方法调用转发至对象。如果将对象注册为 SingleCall,则完成方法调用后该对象会取消。每次调用一个方法时,都会创建一个新的实例。GetObject 和 new 之间的唯一差别在于,前者允许指定 URL 作为参数,而后者从配置中获得 URL。

CreateInstance 或 new 可用于客户端激活对象。两者都允许使用带参数的构造函数来实例化对象。客户端激活对象的生存期由远程处理框架提供的租用服务控制。对象租用的内容在下一节中说明。

对象的租用生存期

每个应用程序域都包含一个用于管理其租用情况的租用管理器。所有的租用都会被定期检查,以确定租用是否已过期。如果租用过期,则会调用该租用的一个或多个发起者,使它们有机会更新租用。如果所有的发起者都不准备更新租用,则租用管理器会删除该租用并将该对象作为垃圾回收。租用管理器按照剩余租用时间的顺序维护租用列表。剩余时间最短的租用排在列表的顶端。

租用可以实现 ILease 接口并存储一个属性集合,用于确定更新的策略和方法。您也可以使用调用来更新租用。每次调用远程对象上的方法时,租用时间都会设置为目前 LeaseTime 最大值加上 RenewOnCallTime。LeaseTime 即将过期时,发起者会被要求更新租用。因为我们有时会遇上网络不稳定,所以可能会找不到租用发起者。为了确保不在服务器上留下无效对象,每个租用都带有一个 SponsorshipTimeout。该值指定了租用终止之前等待租用发起者回复的时间长度。如果 SponsershipTimeout 为零,CurrentLeaseTime 会被用于确定租用的过期时间。如果 CurrentLeaseTime 的值为零,则租用不会过期。配置或 API 可用于替代 InitialLeaseTime、SponsorshipTimeout 和 RenewOnCallTime 的默认值。

租用管理器维护着一个按发起时间从大到小存储的发起者列表(它们实现 ISponsor 接口)。需要调用发起者以更新租用时间时,租用管理器会从列表的顶部开始向一个或多个发起者要求更新租用时间。列表顶部的发起者表示其以前请求的租用更新时间最长。如果发起者没有在 SponsorshipTimeOut 时间段内响应,则它会被从列表中删除。通过调用 GetLifetimeService 并将对象租用作为参数,即可以获得该对象租用。该调用是 RemotingServices 类的一个静态方法。如果对象在应用程序域内部,则该调用的参数是对象的本地引用,且返回的租用也是该租用的本地引用。如果对象是远程的,则代理会作为一个参数传递,且返回给调用方的是租用的透明代理。

对象能够提供自己的租用并控制自己的生存期。它们通过替代 MarshalByRefObject 上的 InitializeLifetimeService 方法来完成该操作,如下所示:

public class Foo : MarshalByRefObject {
  public override Object InitializeLifetimeService()
  {
    ILease lease = (ILease)base.InitializeLifetimeService();
    if (lease.CurrentState == LeaseState.Initial)  {
      lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
      lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
      lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
    }
    return lease;
  }
}

只有当租用处于初始状态时,才可以更改租用属性。InitializeLifetimeService 的实现通常调用基类的相应方法,来检索远程对象的现有租用。如果在此之前从未对该对象封送过,则返回的租用会处于其初始状态且可以设置租用属性。一旦封送了对象,则租用会从初始状态变为激活状态,并忽略任何初始化租用属性的尝试(但有一种情况例外)。激活远程对象时将调用 InitializeLifetimeService。通过激活调用可以提供一个租用发起者的列表,而且当租用处于激活状态时,可以随时将其他发起者添加到列表中。

可以下列方式延长租用时间:

  • 客户端可以调用 Lease 类上的 Renew 方法。

  • 租用可以向某个发起者请求 Renewal。

  • 当客户端调用对象上的某个方法时,RenewOnCall 值会自动更新租用。

一旦租用过期,其内部状态会由 Active 变为 Expired,且不再对发起者进行任何调用,对象也会被作为垃圾回收。一般情况下,如果发起者分散在 Web 上或位于某个防火墙的后面,远程对象回叫发起者时会遇到困难。因此,发起者不必与客户端处于同一位置,只要远程对象能够访问得到,它可以为网络上的任意位置。

通过租用来管理远程对象的生存期可以作为引用计数的一种替代方法,因为当网络连接的性能不可靠时,引用计数会显得复杂和低效。尽管有人会坚持认为远程对象的生存期比所需的时间要长,但与引用计数和连接客户相比,租用降低了网络的繁忙程度,将会成为一种非常受欢迎的解决方案。

总结

要提供完美的、能够满足大多数商务应用需求的远程处理框架,即使能够做到,也必然会非常困难。Microsoft 提供了能够根据需要进行扩展和自定义的框架,在正确的方向上迈出了关键的一步。

附录 A:使用 TCP 通道进行远程处理的示例

此附录显示了如何编写简单的“Hello World”远程应用程序。客户端将一个字符串传递到远程对象上,该远程对象将单词“Hi There”附加到字符串上,并将结果返回到客户端。

将此文件保存为 server.cs。此处为服务器的代码:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.TCP;

namespace RemotingSamples {
  public class HelloServer : IHello {

    public static int Main(string [] args) {

      TCPChannel chan = new TCPChannel(8085);
      ChannelServices.RegisterChannel(chan);
      RemotingServices.RegisterWellKnownType(
        "server", "RemotingSamples.HelloServer", "SayHello", 
WellKnownObjectMode.SingleCall); System.Console.WriteLine("请按 <enter> 键退出..."); System.Console.ReadLine(); return 0; } public HelloServer() { Console.WriteLine("HelloServer 已激活"); } ~HelloServer() { Console.WriteLine("对象已清除"); } public ForwardMe HelloMethod(ForwardMe obj) { Console.WriteLine("Hello.HelloMethod : {0}", name); return "Hi there " + name; } } }

将此代码保存为 client.cs:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.TCP;

namespace RemotingSamples {
  public class Client
  {
    public static int Main(string [] args)
    {
      TCPChannel chan = new TCPChannel();
      ChannelServices.RegisterChannel(chan);
      ForwardMe param = new ForwardMe();
      HelloServer obj = (HelloServer)Activator.GetObject(
        typeof(RemotingSamples.HelloServer), "tcp://localhost:8085/SayHello");
      if (obj == null) System.Console.WriteLine("无法定位服务器");
      else {
        Console.WriteLine("值为 " + param.getValue());
        ForwardMe after = obj.HelloMethod(param);
        Console.WriteLine("呼叫后的值为 " + after.getValue());
      }
      return 0;
    }
  }
}

下面是 makefile:

all: server.exe client.exe share.dll

share.dll: share.cs
   csc /debug+ /target:library /out:share.dll share.cs

server.exe: server.cs
   csc /debug+ /r:share.dll /r:System.Runtime.Remoting.dll server.cs

client.exe: client.cs server.exe
   csc /debug+ /r:share.dll /r:server.exe /r:System.Runtime.Remoting.dll client.cs

clean:
   @del server.exe client.exe *.pdb *~ *.*~

附录 B:使用 HTTP 通道进行远程处理的示例

将此文件保存为 server.cs。此处为服务器的代码:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.HTTP;

namespace RemotingSamples {
  public class HelloServer : IHello {

    public static int Main(string [] args) {

      HTTPChannel chan = new HTTPChannel(8085);
      ChannelServices.RegisterChannel(chan);
      RemotingServices.RegisterWellKnownType(
        "server", "RemotingSamples.HelloServer", "SayHello", 
WellKnownObjectMode.SingleCall); System.Console.WriteLine("请按 <enter> 键退出..."); System.Console.ReadLine(); return 0; } public HelloServer() { Console.WriteLine("HelloServer 已激活"); } ~HelloServer() { Console.WriteLine("对象已清除"); } public ForwardMe HelloMethod(ForwardMe obj) { Console.WriteLine("Hello.HelloMethod : {0}", name); return "Hi there " + name; } } }

将此代码保存为 client.cs:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.HTTP;

namespace RemotingSamples {
  public class Client
  {
    public static int Main(string [] args)
    {
      HTTPChannel chan = new HTTPChannel();
      ChannelServices.RegisterChannel(chan);
      ForwardMe param = new ForwardMe();
      HelloServer obj = (HelloServer)Activator.GetObject(
        typeof(RemotingSamples.HelloServer), "http://localhost:8085/SayHello");
      if (obj == null) System.Console.WriteLine("无法定位服务器");
      else {
        Console.WriteLine("值为 " + param.getValue());
        ForwardMe after = obj.HelloMethod(param);
        Console.WriteLine("呼叫后的值为 " + after.getValue());
      }
      return 0;
    }
  }
}

下面是 makefile:

all: server.exe client.exe share.dll

share.dll: share.cs
   csc /debug+ /target:library /out:share.dll share.cs

server.exe: server.cs
   csc /debug+ /r:share.dll /r:System.Runtime.Remoting.dll server.cs

client.exe: client.cs server.exe
   csc /debug+ /r:share.dll /r:server.exe /r:System.Runtime.Remoting.dll client.cs

clean:
   @del server.exe client.exe *.pdb *~ *.*~
LVGL开发新选择:深度对比NXP GUI Guider与SquareLine Studio,免费版够用吗? 本文深度对比了LVGL开发中两款主流工具NXP GUI Guider与SquareLine Studio的核心差异,包括功能定位、设计能力、代码生成质量和硬件适配等方面。针对不同项目需求,分析了免费版工具的适用性,帮助开发者根据预算和功能需求做出明智选择。特别关注了GUI设计工具在嵌入式开发中的实际应用效果。 阅读详情

相关推荐

基于STM32F407VET6的智能巡检小车灰度循迹系统优化设计

本文详细介绍了基于STM32F407VET6主控芯片的智能巡检小车灰度循迹系统优化设计。通过将传感器输出从数字量升级为模拟量以获取更丰富的路面信息,并采用PD控制算法结合速度管理策略,有效提升了小车在复杂弯道上的循迹稳定性与适应性。文章分享了硬件电路抗干扰设计、软件滤波及实战调优经验,为嵌入式开发者提供了从“能跑”到“跑得稳”的系统性解决方案。

weixin_28273593的博客 443

Microsoft .Net Remoting系列专题之三:Remoting事件处理全接触

前言:在Remoting中处理事件其实并不复杂,但其中有些技巧需要你去挖掘出来。正是这些技巧,仿佛森严的壁垒,让许多人望而生畏,或者是不知所谓,最后放弃了事件在Remoting的使用。关于这个主题,在网上也有很多讨论,相关的技术文章也不少,遗憾的是,很多文章概述的都不太全面。我在研究Remoting的时候,也对事件处理发生了兴趣。经过参考相关的书籍、文档,并经过反复的试验,深信自己能够把这个问题阐

冯钰 的专栏 5842

基于51单片机的多功能计算器全套设计

当今社会,随着人们物质生活的不断提高,电子产品已经走进了家家户户,无论是生活或学习,还是娱乐和消遣几乎样样都离不开电子产品,大型复杂的计算能力是人脑所不能胜任的,而且人脑比较容易出错。计算器作为一种快速通用的计算工具方便了用户的使用。计算器可谓是我们最亲密的电子伙伴之一。本设计着重在于分析计算器设计开发过程中的环节和步骤,并从实践经验出发对计算器设计做了详细的分析和研究。 本设计是以STC89C52单片机为核心的计算器模拟系统设计,输入采用5×8矩阵键盘,可以进行加、减、乘、除等十几种数字运算,同时支持括号

runweipa的博客 1742

Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇

Microsoft .Net Remoting系列专题之一一、Remoting基础什么是Remoting,简而言之,我们可以将其看作是一种分布式处理方式。从微软的产品角度来看,可以说Remoting就是DCOM的一种升级,它改善了很多功能,并极好的融合到.Net平台下。Microsoft® .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这也正是我们使

mzoy的专栏 3352

Android 默认授予所有应用权限以及特殊权限处理

PackageManagerService作为android的主要系统服务之一,包含处理应用权限相关的职责。源码地址如下:在PMS成功启动时并收到回掉systemReady回进行应用权限授予。DefaultPermissionGrantPolicy为权限处理策略管理类。这里并没有赋予所有系统应用权限,所以当进行应用时还是会显示权限检查的弹框。可通过此方法进行所有应用权限赋予:如果不想所有应用都授予权限,可考虑在system/etc下的权限相关xml文件添加应用包名和所需权限。

Jimmy的专栏 1509

Unity 升级 xLua 版本,编译不同平台链接库

Unity 升级 xLua 详解

LymanCao的专栏 1801

.Net Remoting(分离服务程序实现) - Part.3

 分离服务程序集元信息和实现在上面Remoting基本操作的范例中,我们发现了这样一个情况:即是 客户应用程序 仍然需要引用 服务程序集(ServerAssembly),因为它需要DemoClass的元信息来创建代理。使用这种共享服务程序集的方式构建Remoting程序,其运行时的示意图如下所示:可以看到 宿主应用程序域(Host App Domain,位于服务端) 和 客户应用程

733

S32K系列之PDB框图详解及PDB触发ADC

可编程延迟模块PDB,PDB可提供从内部或外部触发或可编程间隔信号到ADC硬件触发输入的可控制延迟。 PDB可以选择提供脉冲输出,用作CMP块中的采样窗口。 S32K系列的PDB资源如下表: PDB功能框图 在下图中,以下字母表示的意义: N:PDB通道的总数 n:PDB通道号,范围从0到N-1 M:每个PDB通道的总可用预触发 m:预触发编号,范围从0到M-1 Y:脉冲输出的总数 y:脉冲数...

zz56z56的博客 1万+

Remoting技术

一 Remoting技术出现的背景 1)分布式应用需求的迅速增长(Peer-to-Peer, Grid等技术的出现) 2)原有的C/S, B/S模式和技术已经不能胜任(串口RS232,Socket,RPC,DCOM技术各有缺点) 二 什么是Romoting? 采用分布式进行编程的一种技术,Remoting主要用于管理跨应用程序域的同步和异步RPC 会话。在默认情况下,Remoting使

ShuLiu's Log Book 2529

app逆向分析案例-95分

先把justtrusmeplus打开设置好95分,开始抓包,得到结果为: https://www.95fenapp.com/api_goods/list/v3.0?is_all=1&keywords=%E7%A7%91%E6%AF%94& page=1&page_size=20 &publish_timestamp=0 &scene=2&scene_type=95fen_android_search_personal&sn=SearchList

weixin_41763171的博客 2742

LangGraph的容错机制:从节点失败到图级回滚

核心概念铺垫:讲解LLM应用容错与事务性的核心概念(幂等性、重试策略、降级策略、快照、回滚、原子性、一致性、隔离性、持久性、最终一致性),对比LangGraph容错机制与传统数据库ACID事务、Apache Airflow/Kubernetes Workflow/Kubeflow Pipelines容错机制的异同;LangGraph容错机制的演进历史:用markdown表格梳理LangGraph从0.0.1到1.5.0(截止202X年X月)的容错机制发展历程;节点级别的容错:重试与降级。

AI Python 编程之道的博客 172

Microsoft .NET Remoting : 技术概述

本文转载自:http://www.cnblogs.com/kary/archive/2005/02/22/107515.html稍做修改 简介Microsoft® .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这种框架提供了多种服务,包括激活和生存期支持,以及负责与远程应用程序进行消息传输的通讯通道。格式化程序用于在消息通过通道传输之前,对其

王者尖锋 478

NX二次开发点通过云配准获取相同体

先找到体的参考方向(这个参考方向对于相同体重合之后是相同的),这个时候我们的思路是三个不共线的点确定一个坐标系,然后和绝对方向求转换矩阵。然后获取体的所有边的几何中心,把这些点通过转换矩阵转换之后存起来,再和另外一个体转换之后的点数据作对比,在误差范围内配对点的数量等于体获取点的数量就是相同体

qq_39541524的博客 1646
上一篇: Windows DNA介绍:新一代计算方案的框架
下一篇: Alpha 闪烁效果
UU码农
博客等级 码龄26年 1粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值