1、解決問題,須要深刻,並從細節入手,多從代碼找緣由,不能認爲代碼是死的,不會出錯:c++
以前代碼都運行良好,忽然某一天,在我電腦上出問題了。出了問題,那就應該找出緣由。其實這個問題,自己並不難,好歹給你報出了個錯:服務器
獲取Word遠程代理服務失敗:沒法加載類型「clr:NoteFirst.KMS.Clients.RomoteInterface.IOfficeService, NoteFirst.KMS.Clients.RomoteInterface」。, Server stack trace: 在 System.Runtime.Remoting.Messaging.MethodCall.ResolveMethod(Boolean bThrowIfNotResolved) 在 System.Runtime.Remoting.Messaging.MethodCall.HeaderHandler(Header[] h) 在 System.Runtime.Serialization.Formatters.Soap.ObjectReader.ParseObject(ParseRecord pr) 在 System.Runtime.Serialization.Formatters.Soap.SoapHandler.StartChildren() 在 System.Runtime.Serialization.Formatters.Soap.SoapParser.ParseXml() 在 System.Runtime.Serialization.Formatters.Soap.SoapParser.Run() 在 System.Runtime.Serialization.Formatters.Soap.ObjectReader.Deserialize(HeaderHandler handler, ISerParser serParser) 在 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Deserialize(Stream serializationStream, HeaderHandler handler) 在 System.Runtime.Remoting.Channels.CoreChannel.DeserializeSoapRequestMessage(Stream inputStream, Header[] h, Boolean bStrictBinding, TypeFilterLevel securityLevel) 在 System.Runtime.Remoting.Channels.SoapServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg,
ITransportHeaders& responseHeaders, Stream& responseStream)
net remoting在調用定義的接口時報錯,沒法加載類型,這錯誤是個什麼樣的錯誤,怎麼就不能加載了,以前都好好的。爲了解決這個問題,我花了一天多的時間。從系統運行環境,到office從新安裝,折騰了個遍,就差裝系統了。都說出了問題,從內部找緣由,但是同事機器上的代碼運行良好,咱們的代碼絕對一致。因而,我把目光就聚焦到外部環境上了。不過話說回來,外部環境也是有點問題的,好比安裝了多個版本的office。在安裝和卸載的頻繁操做之下,很難知道註冊表會不會出問題。socket
到了次日,我就去改改代碼,試着用另一種方法解決問題。結果改着改着,就發現了代碼原來是有bug的。前輩的代碼,看似高深,調用了c++的不少方法。tcp
TcpChannel tcpChannel = new TcpChannel(9998); ChannelServices.RegisterChannel(tcpChannel, false); RemotingConfiguration.RegisterWellKnownServiceType(typeof(OfficeServiceImplement), CHANNEL_NAME, WellKnownObjectMode.SingleCall); EventLog.WriteEntry("NoteFirst", "註冊tcp remote服務成功");
以前remoting採用的是http通道,我給改爲tcp通道,結果問題就解決了。我就想,僅僅是通道不一樣,就會解決問題嗎,因此想着http通道確定是能夠的。ide
channel = new HttpServerChannel(CHANNEL_NAME, GetEnablePort(), Provider); RemotingConfiguration.RegisterWellKnownServiceType(typeof(OfficeServiceImplement), OBJECT_URI, WellKnownObjectMode.Singleton);
看下GetEnablePort的定義:url
private static int GetEnablePort() { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); int result = 4211; while (true) { try { socket.Bind(new IPEndPoint(IPAddress.Any, result)); socket.Listen(100); socket.Close(); ShareDataRW.OfficeAddinServicesPort = result; break; } catch { ++result; } } return result; }
動態獲取了端口,並有賦值操做:ShareDataRW.OfficeAddinServicesPort = result;spa
service = Activator.GetObject(typeof(IOfficeService), string.Format(OfficeService.ServiceUrl, ShareDataRW.OfficeAddinServicesPort)) as IOfficeService;
這個是客戶端調用remoting的代碼,看看 ShareDataRW.OfficeAddinServicesPort 端口是怎麼獲取的:.net
public static int OfficeAddinServicesPort { get { return ReadShareDataStruct().OfficeAddinServicesPort; } set { ShareData sd = ReadShareDataStruct(); sd.OfficeAddinServicesPort = value; WriteReadShareDataStruct(sd); } }
這裏又引入了幾個方法:代理
//將數據從非託管內存塊封送到新分配的指定類型的託管對象
private static ShareData ReadShareDataStruct() { return (ShareData)Marshal.PtrToStructure(ShareDataMemoryPoint, ShareDataType); }
//將數據從託管對象封送到非託管內存塊中 private static void WriteReadShareDataStruct(ShareData data) { Marshal.StructureToPtr(data, ShareDataMemoryPoint, false); }
ShareData是個結構體:code
[StructLayout(LayoutKind.Sequential)] private struct ShareData { public int ClientServicesPort; public int OfficeAddinServicesPort; public int WpsAddinServicesPort; public int MainWindowsHandle; }
Type ShareDataType = typeof(ShareData);
ShareDataMemoryPoint由於牽扯到c++裏面的東西,不過從字面上看,共享內存地址,我猜的。看了這麼多代碼,咱們大體理解,它是經過共享內存實現的端口存放,那爲何服務器端存進去的端口和客戶端取出來的端口就不同呢?這是個人疑惑點。爲何以前的代碼就沒有發生過這樣的事情,請不要
老提過去好很差,代碼是動態運行的,內存當中的活動也是動態的。有一種可能性,就是發佈服務的端口在代碼執行到那句的時候已經定好了,並把它寫到內存中了。等客戶端再去拿的時候,在這以前值被動了手腳。至於誰修改了它,何時修改的,這將是一個祕密,等待探尋。
2、WCF實現:
在這漫長的解決問題當中,我無心間看到微軟的建議:把.net remoting遷移到wcf中。微軟給出了具體的遷移步驟,特別詳細,因而我就改寫了代碼,用wcf去實現:
定義協議
[ServiceContract] public interface IOfficeService { [OperationContract] void InsertTo(Bibliography[] bibliographies); [OperationContract] IntPtr GetActiveDocumentWindowHandle(); [OperationContract] void Insert(string stream); /// <summary> /// 獲取文檔的初始化時間 /// </summary> /// <returns></returns> [OperationContract] DateTime GetDateTimeOfActivedDocument(); }
注意:方法不能同名
怎麼實現並不重要,想怎麼實現就怎麼實現,我只管定義接口,這是發佈服務,自託管服務:
NetTcpBinding binding = new NetTcpBinding(); Uri baseAddress = new Uri("net.tcp://localhost:8099/wcfserver"); ServiceHost serviceHost = new ServiceHost(typeof(OfficeServiceImplement), baseAddress); serviceHost.AddServiceEndpoint(typeof(IOfficeService), binding, baseAddress); serviceHost.Open(); EventLog.WriteEntry("NoteFirst", string.Format("The WCF server is ready at {0}", baseAddress));
再來看看客戶端的調用:
NetTcpBinding binding = new NetTcpBinding(); String url = "net.tcp://localhost:8099/wcfserver"; EndpointAddress address = new EndpointAddress(url); ChannelFactory<IOfficeService> channelFactory = new ChannelFactory<IOfficeService>(binding, address); service = channelFactory.CreateChannel();
拿到service,即遠程對象的代理,咱們就能夠調用接口中的方法了。
注意:實際代碼中,須要考慮通道的釋放等問題。