其实这个帖子是 Julia如何调用C# dll动态链接库? 的后续。
@northc 一开始我提到可以用Julia通过命名管道调用PowerShell,然后由它代为加载.NET程序集DLL,然后给了个简陋的demo。不过看起来你用着不是很顺利orz,毕竟涉及到Win32的知识比较多…然后我决定写一个完整点儿的↓
命名管道是Windows的一种IPC机制,可以实现双工通信。管道除了默认的字节流模式以外,还有一种消息模式,它会帮我们把每次Write的操作封装成数据报,就不用自己处理边界问题了,挺适合用来搞简易的RPC的。
主要思路是让Julia生成一个随机GUID,然后启动PowerShell,创建一个消息模式的管道,再将每次的调用请求做成文本协议,多次请求即可。Windows提供了一个叫CallNamedPipe
的很方便的API,相当于CreateFile
/WaitNamedPipe
、TransactNamedPipe
、CloseHandle
一步到位。
首先是PowerShell侧的服务器代码:
$pipename = $args[0]
$bufsize = 4096
$srv = [System.IO.Pipes.NamedPipeServerStream]::new($pipename, [System.IO.Pipes.PipeDirection]::InOut, 1, [System.IO.Pipes.PipeTransmissionMode]::Message)
if (!$srv) {
Write-Host "Failed to start pipe server"
exit(-1)
}
while ($true) {
$srv.WaitForConnection()
$inbuf = [byte[]]::new($bufsize)
$nbread = $srv.Read($inbuf, 0, $bufsize)
$command = [System.Text.Encoding]::UTF8.GetString($inbuf, 0, $nbread)
$exit = $false
$response = switch ($command) {
"foo" {
"bar"
}
"julia" {
"lang"
}
"exit" {
$exit = $true
"bye"
}
Default { "Error: unknown command" }
}
$outbuf = [System.Text.Encoding]::UTF8.GetBytes($response)
$srv.Write($outbuf, 0, $outbuf.Length)
$srv.WaitForPipeDrain()
$srv.Disconnect()
if ($exit) {
exit(0)
}
}
因为是PowerShell,所以在.NET环境里可以为所欲为了。具体更多玩法可以查看那个帖子。
然后是Julia的代码:
function callnamedpipe(name::String, payload::Vector{UInt8};bufsize = 4096,timeout = 0x00000000)
outbuf = Vector{UInt8}(undef, bufsize)
nbread = Ref{UInt32}()
ret = ccall(:CallNamedPipeA, Int32, (Cstring, Ptr{UInt8}, UInt32, Ptr{UInt8}, UInt32, Ptr{UInt32}, Int32),
"\\\\.\\PIPE\\$name", payload, length(payload), outbuf, bufsize, nbread, timeout)
if ret == 0
return nothing
else
return outbuf[1:nbread[]]
end
end
using UUIDs
pipename = string(uuid4())
p = run(`powershell.exe -File C:\\Users\\Azure\\Desktop\\PipeServer.ps1 $pipename`;wait = false)
while true
cmd = readline()
ret = callnamedpipe(pipename, collect(transcode(UInt8, cmd)))
if !isnothing(ret)
str = String(ret)
println(str)
if str == "bye"
break
end
else
println("Error calling pipe")
break
end
end
> hello
Error: unknown command
> foo
bar
> julia
lang
> exit
bye
剩下的工作就是稍微设计一下文本协议层面的事情了。
PowerShell这边可以分别运行,并不一定非得用Julia启动,只要两边能协商好管道名字即可。管道名字其实也可以是固定的,只是可能有重名风险。callnamedpipe
的timeout
也是可以调的,如果管道未就绪的话它会等待指定的毫秒数。
另外,我最近在做一个Julia直接调C#的包,只不过加班太猛了进度比较慢…