【UWP】讓 UWP 自己和自己通信
眾所周知,UWP 一般是運(yùn)行在沙盒里面的,當(dāng)我們需要訪問(wèn)沙盒外資源的時(shí)候,就需要通過(guò)沙盒外的代理服務(wù)器來(lái)獲取。一般情況下我們都是利用 WinRT API 通過(guò) Runtime Broker 來(lái)和沙盒外互通,遇到要自定義的情況則是手動(dòng)開(kāi)一個(gè) Win32 服務(wù)器來(lái)互通,但是有沒(méi)有可能我們可以直接拿 UWP 本體當(dāng)服務(wù)器呢?
UWP 本體實(shí)際上就是一個(gè)普通的 Win32 EXE 程序,只是想要以 UWP 狀態(tài)運(yùn)行只能通過(guò)系統(tǒng)托管,但是如果我們不需要它以 UWP 狀態(tài)執(zhí)行的時(shí)候完全可以直接雙擊打開(kāi)它,這時(shí)候它就是一個(gè)很普通的散裝 Win32 程序了。
利用這個(gè)特性,我們可以制作一種假裝雙擊打開(kāi)的方法,只需要在發(fā)現(xiàn)是 Win32 狀態(tài)下啟動(dòng)就去用 API 喚起 UWP 形態(tài)的自己就行了。
private static unsafe bool IsPackagedApp
{
get
{
uint length = 0;
PWSTR str = new();
_ = PInvoke.GetCurrentPackageFullName(ref length, str);
char* ptr = stackalloc char[(int)length];
str = new(ptr);
WIN32_ERROR result = PInvoke.GetCurrentPackageFullName(ref length, str);
return result != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE;
}
}
private static void Main()
{
if (IsPackagedApp)
{
Application.Start(static p =>
{
DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
});
}
else
{
StartCoreApplicationAsync().Wait();
}
}
private static async Task StartCoreApplicationAsync()
{
PackageManager manager = new();
string basePath = AppDomain.CurrentDomain.BaseDirectory;
XmlDocument manifest = await XmlDocument.LoadFromFileAsync(await StorageFile.GetFileFromPathAsync(Path.Combine(basePath, "AppxManifest.xml")));
IXmlNode identity = manifest.GetElementsByTagName("Identity")?[0];
string name = identity.Attributes.FirstOrDefault(x => x.NodeName == "Name")?.InnerText;
IXmlNode application = manifest.GetElementsByTagName("Application")?[0];
string id = application.Attributes.FirstOrDefault(x => x.NodeName == "Id")?.InnerText;
IEnumerable<Package> packages = manager.FindPackagesForUser(string.Empty).Where(x => x.Id.FamilyName.StartsWith(name));
if (packages.FirstOrDefault() is Package package)
{
IReadOnlyList<AppListEntry> entries = await package.GetAppListEntriesAsync();
if (entries?[0] is AppListEntry entry)
{
_ = await entry.LaunchAsync();
}
}
}
既然我們可以讓自己?jiǎn)酒鹱约海敲醋约汉妥约和ㄐ乓膊皇鞘裁措y事了,我們只需要在 UWP 形態(tài)下喚起一個(gè) Win32 的自己,就可以自己利用自己濫權(quán)(bushi)了。
既然知道了原理可行,那么我們就來(lái)嘗試把它造出來(lái),眾所周知,通信的方法有很多,比如 UWP 擴(kuò)展通信、TCP 通信、管道通信甚至是寫(xiě)文件通信,但是這些用起來(lái)都太復(fù)雜了,既然我們已經(jīng)用了 WinRT,那么我們直接用 WinRT 通信就是了。
不過(guò)真正的 OOP/WinRT 通信的例子并不多,我到目前為止也沒(méi)有成功實(shí)現(xiàn),所以就先用 COM 通信湊數(shù)了。現(xiàn)在微軟到處都是 OOP/COM 通信,比如小組件和 Dev Home,自從微軟瘋狂用 OOP/COM 造插件之后這方面的示例已經(jīng)非常多了,這里就展示一下如何用 C# 實(shí)現(xiàn)基于 OOP/COM 的 WinRT 通信。
既然是通信,肯定要有服務(wù)器和客戶(hù)端,這里這兩個(gè)都是自己,所以我們就只需要?jiǎng)?chuàng)建一個(gè) UWP 項(xiàng)目就行了。由于 C#/WinRT 并不能實(shí)現(xiàn) WinRT 類(lèi)與非 WinRT 類(lèi)混編,所以我們還需要?jiǎng)?chuàng)建一個(gè) C++/WinRT 項(xiàng)目來(lái)生成 winmd 清單,這個(gè) C++ 項(xiàng)目可以精簡(jiǎn)一下,只需要能編譯 IDL 文件就行了,當(dāng)然不修改也可以,只是看起來(lái)不太清爽罷了。
由于 C++/WinRT 項(xiàng)目只負(fù)責(zé)生成清單,如果直接引用該項(xiàng)目會(huì)報(bào)找不到 dll 實(shí)現(xiàn),所以我們需要把 winmd 文件單獨(dú)拿出來(lái),在 vcxprj 中添加任務(wù):
<Target Name="OutputWinMD" BeforeTargets="BuildCompile">
<PropertyGroup>
<PlatformName Condition="'$(Platform)' == 'Win32'">x86</PlatformName>
<PlatformName Condition="'$(Platform)' == 'ARM64EC'">ARM64</PlatformName>
<PlatformName Condition="'$(PlatformName)' == ''">$(Platform)</PlatformName>
</PropertyGroup>
<Copy SourceFiles="$(OutDir)\$(ProjectName).winmd" DestinationFolder="..\WinMD\$(PlatformName)\$(Configuration)\" />
<Message Text="Copied output metadata file to '..\WinMD\$(PlatformName)\$(Configuration)\'." />
</Target>
這樣當(dāng)項(xiàng)目編譯完成后就會(huì)把 winmd 復(fù)制到..\WinMD\$(PlatformName)\$(Configuration)\里面,這個(gè)文件夾是什么可以根據(jù)情況修改,注意只復(fù)制 winmd 文件。
接著我們?cè)?UWP 項(xiàng)目引用 winmd 文件 (SelfCOMServer.Metadata替換成實(shí)際值):
<ItemGroup>
<CsWinRTInputs Include="..\WinMD\$(Platform)\$(Configuration)\SelfCOMServer.Metadata.winmd" />
</ItemGroup>
<ItemGroup>
<None Include="..\WinMD\$(Platform)\$(Configuration)\SelfCOMServer.Metadata.winmd" Visible="False">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
接下來(lái)我們就可以開(kāi)始寫(xiě) COM 類(lèi)了,首先我們要實(shí)現(xiàn)一個(gè)負(fù)責(zé)服務(wù)器管理的類(lèi),這個(gè)類(lèi)用來(lái)管理服務(wù)器的聲明周期和遠(yuǎn)程類(lèi)的獲取方法。
其中我們需要先實(shí)現(xiàn)一種讓服務(wù)器可以在客戶(hù)端關(guān)閉后自動(dòng)關(guān)閉的方法,讓客戶(hù)端通知是很不合理的,因?yàn)榭蛻?hù)端可能會(huì)突然暴斃,這樣就會(huì)導(dǎo)致服務(wù)器永遠(yuǎn)也不會(huì)釋放了,所以我們必須讓服務(wù)器去感知客戶(hù)端不再需要服務(wù)器了。在 C++,我們可以通過(guò)觀察引用計(jì)數(shù)的方法來(lái)知道遠(yuǎn)程類(lèi)是否被客戶(hù)端釋放了,但是在 C#,遠(yuǎn)程類(lèi)被客戶(hù)端釋放后并不會(huì)真的釋放,我暫時(shí)不清楚其中的原因,表現(xiàn)出來(lái)就是解構(gòu)方法并不會(huì)被執(zhí)行,所以我們就無(wú)法通過(guò)類(lèi)釋放來(lái)關(guān)閉服務(wù)器了。于是我們還有一種方法,當(dāng)通信斷開(kāi)后,我們執(zhí)行遠(yuǎn)程方法就會(huì)報(bào)錯(cuò),這樣我們就知道通信已經(jīng)斷開(kāi)了,于是我們可以讓客戶(hù)端給服務(wù)器發(fā)送一個(gè)保活委托,服務(wù)器每隔一段時(shí)間就去執(zhí)行一次,這樣通信斷開(kāi)后再執(zhí)行委托就會(huì)直接報(bào)錯(cuò)了。當(dāng)然我們還可以讓這個(gè)保活委托變成一個(gè)永遠(yuǎn)也不會(huì)結(jié)束的異步,讓 WinRT 自己去等待,這樣服務(wù)器在通信斷開(kāi)的一瞬間知道通信結(jié)束了,不過(guò)由于我們并不需要瞬間就反應(yīng),有時(shí)候我們還需要考慮應(yīng)用被關(guān)閉后馬上就又打開(kāi)的情況,所以我還是選擇自己開(kāi)個(gè)間隔比較長(zhǎng)的定時(shí)器自己喂狗了。
實(shí)現(xiàn)很簡(jiǎn)單,開(kāi)個(gè)定時(shí)器喂狗就行了:
/// <summary>
/// Represents a monitor that checks if a remote object is alive.
/// </summary>
public sealed partial class RemoteMonitor : IDisposable
{
private bool disposed;
private readonly Timer _timer;
private readonly Action _dispose;
/// <summary>
/// Initializes a new instance of the <see cref="RemoteMonitor"/> class.
/// </summary>
/// <param name="handler">The handler to check if the remote object is alive.</param>
/// <param name="dispose">The action to dispose the remote object.</param>
/// <param name="period">The period to check if the remote object is alive.</param>
public RemoteMonitor(IsAliveHandler handler, Action dispose, TimeSpan period)
{
_dispose = dispose;
_timer = new(_ =>
{
bool isAlive = false;
try
{
isAlive = handler.Invoke();
}
catch
{
isAlive = false;
}
finally
{
if (!isAlive)
{
Dispose();
}
}
}, null, TimeSpan.Zero, period);
}
/// <summary>
/// Finalizes the instance of the <see cref="RemoteMonitor"/> class.
/// </summary>
~RemoteMonitor() => Dispose();
/// <inheritdoc/>
public void Dispose()
{
if (!disposed)
{
disposed = true;
_timer.Dispose();
_dispose?.Invoke();
GC.SuppressFinalize(this);
}
}
}
同時(shí)在 IDL 定義IsAliveHandler委托:
delegate Boolean IsAliveHandler();
接下來(lái)我們就可以寫(xiě)服務(wù)器管理類(lèi)了,我們只需要讓它能正確釋放服務(wù)器就行了,分別寫(xiě) IDL 定義和 C# 實(shí)現(xiàn):
interface ISetMonitor
{
void SetMonitor(IsAliveHandler handler, Windows.Foundation.TimeSpan period);
}
interface IRemoteThing requires ISetMonitor, Windows.Foundation.IClosable, Windows.Foundation.IStringable
{
// 其他遠(yuǎn)程操作
}
/// <summary>
/// The manage class for remote object.
/// </summary>
public sealed partial class RemoteThing : IRemoteThing
{
private bool disposed;
private RemoteMonitor _monitor;
/// <summary>
/// Initializes a new instance of the <see cref="RemoteThing"/> class.
/// </summary>
public RemoteThing() => Program.RefCount++;
/// <summary>
/// Finalizes the instance of the <see cref="RemoteThing"/> class.
/// </summary>
~RemoteThing() => Dispose();
/// <summary>
/// Sets the monitor to check if the remote object is alive.
/// </summary>
/// <param name="handler">The handler to check if the remote object is alive.</param>
/// <param name="period">The period to check if the remote object is alive.</param>
public void SetMonitor(IsAliveHandler handler, TimeSpan period) => _monitor = new RemoteMonitor(handler, Dispose, period);
#region 其他遠(yuǎn)程操作
#endregion
/// <inheritdoc/>
public void Dispose()
{
if (!disposed)
{
disposed = true;
_monitor?.Dispose();
GC.SuppressFinalize(this);
if (--Program.RefCount == 0)
{
_ = Program.CheckComRefAsync();
}
}
}
/// <summary>
/// 隨便寫(xiě)點(diǎn)什么作為測(cè)試。
/// </summary>
public override string ToString() =>
new StringBuilder()
.AppendLine("Information")
.AppendLine($"Framework: {RuntimeInformation.FrameworkDescription}")
.AppendLine($"OSPlatform: {Environment.OSVersion}")
.Append($"OSArchitecture: {RuntimeInformation.OSArchitecture}")
.ToString();
}
有了服務(wù)器管理類(lèi),我們就可以把它注冊(cè)到 COM 服務(wù)器了。想要注冊(cè) COM 類(lèi),我們需要一個(gè) Factory 工廠類(lèi)來(lái)讓 COM 服務(wù)器可以在通信時(shí)初始化 COM 類(lèi)。首先我們需要導(dǎo)入 COM 相關(guān) API,定義IClassFactory接口,其中Factory.CLSID_IRemoteThing為遠(yuǎn)程類(lèi)的CLSID。
public static partial class Factory
{
public static readonly Guid CLSID_IRemoteThing = new("01153FC5-2F29-4F60-93AD-EFFB97CC9E20");
public static readonly Guid CLSID_IUnknown = new("00000000-0000-0000-C000-000000000046");
[LibraryImport("api-ms-win-core-com-l1-1-0.dll")]
internal static partial int CoRegisterClassObject(in Guid rclsid, IClassFactory pUnk, uint dwClsContext, int flags, out uint lpdwRegister);
[LibraryImport("api-ms-win-core-com-l1-1-0.dll")]
internal static partial int CoRevokeClassObject(uint dwRegister);
}
// https://docs.microsoft.com/windows/win32/api/unknwn/nn-unknwn-iclassfactory
[GeneratedComInterface]
[Guid("00000001-0000-0000-C000-000000000046")]
public partial interface IClassFactory
{
void CreateInstance(nint pUnkOuter, in Guid riid, out nint ppvObject);
void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}
接下來(lái)我們可以制作一個(gè)抽象的工廠類(lèi),然后就可以直接繼承這個(gè)工廠類(lèi)來(lái)實(shí)現(xiàn)對(duì)應(yīng)類(lèi)的工廠類(lèi),不過(guò)由于 COM 源生成暫時(shí)不支持泛型,所以這個(gè)抽象的工廠類(lèi)不能加GeneratedComClass。
public abstract partial class Factory<T, TInterface> : IActivationFactory, IClassFactory where T : TInterface, new()
{
private const int E_NOINTERFACE = unchecked((int)0x80004002);
private const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
private readonly Guid _iid = typeof(TInterface).GUID;
public nint ActivateInstance() => MarshalInspectable<TInterface>.FromManaged(new T());
public void CreateInstance(nint pUnkOuter, in Guid riid, out nint ppvObject)
{
ppvObject = 0;
if (pUnkOuter != 0)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (riid == _iid || riid == Factory.CLSID_IUnknown)
{
// Create the instance of the .NET object
ppvObject = MarshalInspectable<TInterface>.FromManaged(new T());
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
}
public void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock)
{
}
public abstract void RegisterClassObject();
public abstract void RevokeClassObject();
}
然后我們繼承這個(gè)抽象的工廠類(lèi)來(lái)實(shí)現(xiàn)我們需要的工廠類(lèi):
[GeneratedComClass]
public partial class RemoteThingFactory : Factory<RemoteThing, IRemoteThing>
{
private uint cookie;
public override void RegisterClassObject()
{
int hresult = Factory.CoRegisterClassObject(
Factory.CLSID_IRemoteThing,
this,
(uint)CLSCTX.CLSCTX_LOCAL_SERVER,
(int)REGCLS.REGCLS_MULTIPLEUSE,
out cookie);
if (hresult < 0)
{
Marshal.ThrowExceptionForHR(hresult);
}
}
public override void RevokeClassObject()
{
int hresult = Factory.CoRevokeClassObject(cookie);
if (hresult < 0)
{
Marshal.ThrowExceptionForHR(hresult);
}
}
}
然后我們就可以在程序入口點(diǎn)注冊(cè)這個(gè) COM 類(lèi)了:
public static class Program
{
private static ManualResetEventSlim comServerExitEvent;
public static int RefCount { get; set; }
private static void Main(string[] args)
{
if (args is ["-RegisterProcessAsComServer", ..])
{
comServerExitEvent = new ManualResetEventSlim(false);
comServerExitEvent.Reset();
RemoteThingFactory factory = new();
factory.RegisterClassObject();
_ = CheckComRefAsync();
comServerExitEvent.Wait();
factory.RevokeClassObject();
}
else
{
Application.Start(static p =>
{
DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
});
}
}
public static async Task CheckComRefAsync()
{
await Task.Delay(100);
if (RefCount == 0)
{
comServerExitEvent?.Set();
}
}
}
最后我們需要在清單聲明 COM 服務(wù)器,其中SelfCOMServer.exe為服務(wù)器所在的可執(zhí)行文件,Class ID 填遠(yuǎn)程類(lèi)的CLSID。
<Applications>
<Application>
...
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer
Executable="SelfCOMServer.exe"
Arguments="-RegisterProcessAsComServer"
DisplayName="COM Server"
LaunchAndActivationPermission="O:SYG:SYD:(A;;11;;;WD)(A;;11;;;RC)(A;;11;;;AC)(A;;11;;;AN)S:P(ML;;NX;;;S-1-16-0)">
<com:Class Id="01153FC5-2F29-4F60-93AD-EFFB97CC9E20" DisplayName="COM Server" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
現(xiàn)在我們已經(jīng)完成了服務(wù)端的制作,接下來(lái)我們就可以讓客戶(hù)端調(diào)用服務(wù)器遠(yuǎn)程類(lèi)了。
在Factory類(lèi)繼續(xù)添加獲取遠(yuǎn)程類(lèi)的相關(guān)內(nèi)容:
public static partial class Factory
{
private static bool IsAlive() => true;
public static IRemoteThing CreateRemoteThing() =>
CreateInstance<IRemoteThing>(CLSID_IRemoteThing, CLSCTX.CLSCTX_ALL, TimeSpan.FromMinutes(1));
internal static T CreateInstance<T>(Guid rclsid, CLSCTX dwClsContext = CLSCTX.CLSCTX_INPROC_SERVER)
{
int hresult = CoCreateInstance(rclsid, 0, (uint)dwClsContext, CLSID_IUnknown, out nint result);
if (hresult < 0)
{
Marshal.ThrowExceptionForHR(hresult);
}
return Marshaler<T>.FromAbi(result);
}
internal static T CreateInstance<T>(Guid rclsid, CLSCTX dwClsContext, TimeSpan period) where T : ISetMonitor
{
T results = CreateInstance<T>(rclsid, dwClsContext);
results.SetMonitor(IsAlive, period);
return results;
}
[LibraryImport("api-ms-win-core-com-l1-1-0.dll")]
private static partial int CoCreateInstance(in Guid rclsid, nint pUnkOuter, uint dwClsContext, in Guid riid, out nint ppv);
}
現(xiàn)在我們只需要執(zhí)行IRemoteThing remote = Factory.CreateRemoteThing();就能獲取遠(yuǎn)程類(lèi)了。
到這里我們已經(jīng)完成了 OOP COM 的全部流程,我們想要實(shí)現(xiàn)什么內(nèi)容就可以按照構(gòu)建服務(wù)器管理類(lèi)的方法來(lái)制作 COM 遠(yuǎn)程類(lèi),然后通過(guò)在服務(wù)器管理類(lèi)添加一個(gè)構(gòu)造方法來(lái)獲取這個(gè)遠(yuǎn)程類(lèi),這樣我們就不需要單獨(dú)分配 CLSID 和注冊(cè) COM 類(lèi)了。
比如我們可以給Process套個(gè)殼:
/// <inheritdoc cref="Process"/>
public partial class RemoteProcess(Process inner) : IProcess
{
/// <inheritdoc cref="Process.ProcessName"/>
public string ProcessName => inner.ProcessName;
/// <inheritdoc cref="Process.StandardError"/>
public ITextReader StandardError => new RemoteTextReader(inner.StandardError);
/// <inheritdoc cref="Process.ProcessName"/>
public ITextWriter StandardInput => new RemoteTextWriter(inner.StandardInput);
/// <inheritdoc cref="Process.StandardOutput"/>
public ITextReader StandardOutput => new RemoteTextReader(inner.StandardOutput);
/// <inheritdoc cref="Process.StartInfo"/>
public IProcessStartInfo StartInfo
{
get => new RemoteProcessStartInfo(inner.StartInfo);
set => value.ToProcessStartInfo();
}
private readonly ConditionalWeakTable<CoDataReceivedEventHandler, DataReceivedEventHandler> errorDataReceived = [];
/// <inheritdoc cref="Process.ErrorDataReceived"/>
public event CoDataReceivedEventHandler ErrorDataReceived
{
add
{
void wrapper(object sender, DataReceivedEventArgs e) => value(this, new CoDataReceivedEventArgs(e.Data));
DataReceivedEventHandler handler = wrapper;
inner.ErrorDataReceived += handler;
errorDataReceived.Add(value, handler);
}
remove
{
if (errorDataReceived.TryGetValue(value, out DataReceivedEventHandler handler))
{
inner.ErrorDataReceived -= handler;
errorDataReceived.Remove(value);
}
}
}
private readonly ConditionalWeakTable<CoDataReceivedEventHandler, DataReceivedEventHandler> outputDataReceived = [];
/// <inheritdoc cref="Process.OutputDataReceived"/>
public event CoDataReceivedEventHandler OutputDataReceived
{
add
{
void wrapper(object sender, DataReceivedEventArgs e) => value(this, new CoDataReceivedEventArgs(e.Data));
DataReceivedEventHandler handler = wrapper;
inner.OutputDataReceived += handler;
outputDataReceived.Add(value, handler);
}
remove
{
if (outputDataReceived.TryGetValue(value, out DataReceivedEventHandler handler))
{
inner.OutputDataReceived -= handler;
outputDataReceived.Remove(value);
}
}
}
/// <inheritdoc cref="Process.BeginErrorReadLine"/>
public void BeginErrorReadLine() => inner.BeginErrorReadLine();
/// <inheritdoc cref="Process.BeginOutputReadLine"/>
public void BeginOutputReadLine() => inner.BeginOutputReadLine();
/// <inheritdoc cref="Process.CancelErrorRead"/>
public void CancelErrorRead() => inner.CancelErrorRead();
/// <inheritdoc cref="Process.CancelOutputRead"/>
public void CancelOutputRead() => inner.CancelOutputRead();
/// <inheritdoc cref="Component.Dispose"/>
public void Dispose()
{
inner.Dispose();
GC.SuppressFinalize(this);
}
/// <inheritdoc cref="Process.ToString"/>
public override string ToString() => inner.ToString();
}
靜態(tài)部分可以弄一個(gè)假裝的靜態(tài)類(lèi):
/// <inheritdoc cref="Process"/>
public sealed partial class ProcessStatic : IProcessStatic
{
/// <inheritdoc cref="Process.GetProcesses()"/>
public IProcess[] GetProcesses() => Process.GetProcesses().Select(x => new RemoteProcess(x)).ToArray();
public IProcess Start(IProcessStartInfo startInfo) =>
Process.Start(startInfo.ToProcessStartInfo()) is Process process
? new RemoteProcess(process) : null;
}
然后我們?cè)诜?wù)器管理類(lèi)里增加構(gòu)造方法:
public IProcessStatic CreateProcessStatic() => new ProcessStatic();
這樣我們就可以在 UWP 里使用 Process 創(chuàng)建一個(gè) cmd 進(jìn)程了:
IRemoteThing remote = Factory.CreateRemoteThing();
process = remote.CreateProcessStatic().Start(new RemoteProcessStartInfo
{
FileName = "cmd",
CreateNoWindow = true,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false
});
AppTitle.Text = process.ProcessName;
process.OutputDataReceived += OnOutputDataReceived;
process.ErrorDataReceived += OnErrorDataReceived;
process.BeginOutputReadLine();
process.BeginErrorReadLine();
具體實(shí)現(xiàn)可以查看:SelfCOMServer/SelfCOMServer/Common/RemoteProcess.cs at main · wherewhere/SelfCOMServer

最后附上示例應(yīng)用:https://github.com/wherewhere/SelfCOMServer
本文來(lái)自博客園,作者:where-where,轉(zhuǎn)載請(qǐng)注明原文鏈接:http://www.rzrgm.cn/wherewhere/p/18677329

浙公網(wǎng)安備 33010602011771號(hào)