Julia如何调用C# dll动态链接库?

我用Julia以MATLAB为中介调用C#的dll是成功的(using MATLAB; eval_string(“NET.addAssembly(fullfile(pwd, ‘drivers’, ‘visaDlls’, ‘visaLibFwk.dll’))”);),但是MATLAB要付费,所以换成以Python为中介。
Python用“pip install pythonnet”添加一个模块,即可调用C# dll,但是,在Julia环境下运行这些Python语句,却找不到动态链接库dll文件。
有没有办法解决?
或者有没有更好的、免费的方式调用C# dll?

import clr
clr.AddReference(‘Drivers/visaDlls/visaLibFwk’)
<System.Reflection.RuntimeAssembly object at 0x0000000002C7E588>

julia> using PyCall
julia> pyversion
v"3.7.3"

julia> py"""
import clr
print(clr.FindAssembly(‘serialLib.dll’))
clr.AddReference(‘Drivers/visaDlls/visaLibFwk’)
“”"
None
ERROR: PyError ($(Expr(:escape, :(ccall(#= C:\Users\Hao.julia\packages\PyCall\ttONZ\src\pyeval.jl:39 =# @pysym(:PyEval_EvalCode), PyPtr, (PyPtr, PyPt
r, PyPtr), o, globals, locals))))) <class ‘System.IO.FileNotFoundException’>
FileNotFoundException(“Unable to find assembly ‘Drivers/visaDlls/visaLibFwk’.”)
File “C:\Users\Hao.julia\packages\PyCall\ttONZ\src\pyeval.jl”, line 3, in
const Py_eval_input = 258

Stacktrace:
[1] pyerr_check at C:\Users\Hao.julia\packages\PyCall\ttONZ\src\exception.jl:60 [inlined]
[2] pyerr_check at C:\Users\Hao.julia\packages\PyCall\ttONZ\src\exception.jl:64 [inlined]
[3] macro expansion at C:\Users\Hao.julia\packages\PyCall\ttONZ\src\exception.jl:84 [inlined]
[4] pyeval_(::String, ::PyDict{String,PyObject,true}, ::PyDict{String,PyObject,true}, ::Int64, ::String) at C:\Users\Hao.julia\packages\PyCall\ttONZ
\src\pyeval.jl:39
[5] top-level scope at C:\Users\Hao.julia\packages\PyCall\ttONZ\src\pyeval.jl:232

julia> versioninfo()
Julia Version 1.0.4
Commit 38e9fb7f80 (2019-05-16 03:38 UTC)
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: Intel® Core™ i7-6700 CPU @ 3.40GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, skylake)

julia> using Pkg; Pkg.status()
Status C:\Users\Hao\.julia\environments\v1.0\Project.toml
[7a1cc6ca] FFTW v0.2.4
[28b8d3ca] GR v0.40.0
[10e44e05] MATLAB v0.7.3
[e0fc9d43] PkgMirrors v1.2.0 #master (https://mirrors.zju.edu.cn/julia/PkgMirrors.jl.git)
[91a5bcdd] Plots v0.25.2
[438e738f] PyCall v1.91.2
[8bb1440f] DelimitedFiles
[8f399da3] Libdl
[10745b16] Statistics

导出个C的接口就行了。

Anything mentioned about how to do this from C is directly applicable for doing this in Julia.

C# 导出 C 的接口

1 个赞

谢谢指导!
感觉链接里的方法比较难,没试成功;我还是用Python中转成功了。
之前报错是因为CLR识别路径错误,改成绝对路径就可以了。
using PyCall
pyclr = pyimport(“clr”)
pyclr.AddReference(“C:\Users\Hao\Documents\Julia\Drivers\serialLib”)
输出 PyObject <System.Reflection.RuntimeAssembly object at 0x000000002FA25160>
加载C# dll(串口驱动)成功!

还可以定义函数把C#的对象返回给Julia:
py"""
from serialLib import *
def PySerial(port):
UART = SerialConnection(port)
return UART
“”"
然后定义一个串口收发的宏:
macro pterm(str)
SerialIn = UART.SendRecvString(str, 100)
print(SerialIn)
return (SerialIn)
end
这样就可以连接串口,
UART = py"PySerial"(12)
PyObject <serialLib.SerialConnection object at 0x000000002FA25828>
敲串口命令了:
str = @pterm(“cat /proc/version\n”)
cat /proc/version
Linux version 4.1.21-WR8.0.0.14_standard (ecspens@esekilx5979) (gcc version 5.2.0 (Wind River Linux 5.2.0-8.0) ) #1 SMP PREEMPT Mon May 20 15:50:01 CEST 2019
root@hostname:~#

“我调用FFI的FFI”?..

如果不在意性能的话完全可以借助PowerShell,非常省事
使用Julia调用PS,格式化后用管道取回数据就行了,据我所知PS这边好像没有FFI


如果你的DLL是.NET Core的话那么也是跨平台的

— EDIT
看起来你的这个库和串口通信有关?如果是数据收发的话,可以考虑Julia和PS之间使用管道通信,应该不会有特别大的瓶颈,如果你觉得PS写不顺手的话你可以内嵌C#脚本然后Add-Type就可以生成编译后的程序集到内存里

Add-Type可以增加-PassThru参数,你就能拿到导入的程序集里的东西的元数据(反射)

PS C:\Users\Azure\Desktop\Simuro5v5> $asm=Add-Type -Path '.\V5RPC.dll' -PassThru                                        PS C:\Users\Azure\Desktop\Simuro5v5> $asm|Select-Object Name,Namespace                                                  
Name                     Namespace
----                     ---------
EmbeddedAttribute        Microsoft.CodeAnalysis
IsReadOnlyAttribute      System.Runtime.CompilerServices
IStrategy                V5RPC
StrategyClient           V5RPC
StrategyServer           V5RPC
V5Client                 V5RPC
V5Server                 V5RPC
V5PacketReadWrite        V5RPC
V5Packet                 V5RPC
APIReflection            V5RPC.Proto
[省略]

下面是使用管道进行二进制数据IPC的例子(匿名管道就更简单了,Julia可以直接read(::Cmd)

PowerShell的世界

PS C:\Users\Azure> $guid=New-Guid
PS C:\Users\Azure> $guid

Guid
----
e3610798-7734-47e8-b063-6c6a1b2dd4fa


PS C:\Users\Azure> $server=[System.IO.Pipes.NamedPipeServerStream]::new($guid)
PS C:\Users\Azure> $server.WaitForConnection()
PS C:\Users\Azure> $writer=[System.IO.StreamWriter]::new($server)
PS C:\Users\Azure> $writer.Write('Hello from M$FT world!')
PS C:\Users\Azure> $writer.Flush()
PS C:\Users\Azure> $server.Close()                                                                                      PS C:\Users\Azure>

Julia的世界

julia> data=read("\\\\.\\pipe\\e3610798-7734-47e8-b063-6c6a1b2dd4fa")
22-element Array{UInt8,1}:
 0x48
 0x65
 0x6c
 0x6c
 0x6f
 0x20
 0x66
 0x72
 0x6f
 0x6d
    ⋮
 0x24
 0x46
 0x54
 0x20
 0x77
 0x6f
 0x72
 0x6c
 0x64
 0x21

julia> println(String(data))
Hello from M$FT world!

julia>

管道名字推荐使用GUID生成,保证不重样(当然你要想办法把名字告诉Julia这边)。懒的话固定名字也可以

忘了说了,PowerShell Core开源免费(狗头)

2 个赞

PowerShell里面 $server.WaitForConnection() 为什么一敲下去就卡死了呀?(Win7SP1,刚装了Windows PowerShell 5.1,以前3.0,不识别“New-Guid”)

Add-Type手动敲可以用,读回来 $InstrumentName 不知道怎么传给Julia,也不知道怎么用Julia执行Add-Type、读$InstrumentName的命令。
julia> run(Add-Type -Path 'C:\Users\Hao\Documents\Julia\EtherJulia\Drivers\visaDlls\visaLibFwk.dll' -PassThr)
ERROR: IOError: could not spawn Add-Type -Path 'C:\Users\Hao\Documents\Julia\EtherJulia\Drivers\visaDlls\visaLibFwk.dll' -PassThr: no such file or directory (ENOENT)
Stacktrace:
[1] _spawn_primitive(::String, ::Cmd, ::Array{Any,1}) at .\process.jl:401
[2] #526 at .\process.jl:414 [inlined]
[3] setup_stdios(::getfield(Base, Symbol("##526#527")){Cmd}, ::Array{Any,1}) at .\process.jl:498
[4] _spawn at .\process.jl:413 [inlined]
[5] #run#536(::Bool, ::typeof(run), ::Cmd) at .\process.jl:727
[6] run(::Cmd) at .\process.jl:726
[7] top-level scope at none:0

手动运行是成功的:
PS C:\Users\Hao> Add-Type -Path ‘C:\Users\Hao\Documents\Julia\EtherJulia\Drivers\visaDlls\visaLibFwk.dll’ -PassThr

IsPublic IsSerial Name BaseType


True False Connection System.Object
True False GpibConnection visaLib.VisaConnection
True False IConnection
True False VisaConnection visaLib.Connection
False False System.Object
False True <>c System.Object

PS C:\Users\Hao> $VSA = [visaLib.VisaConnection]::new(“TCPIP0::192.168.1.213::hislip0::INSTR”, 5000)
PS C:\Users\Hao> $VSA

InterfaceType IsOpen VirtualComPort


VISA True True

PS C:\Users\Hao> $InstrumentName = $VSA.StrSendReceive("*idn?")
PS C:\Users\Hao> $InstrumentName
Keysight Technologies,N9030B,MY57140913,A.22.08

因为WaitForConnection是阻塞方法……当有管道客户端连上来之后才会返回
这时你应该在Julia侧连接管道read
管道通信有点像TCP

Julia暂时没有PS的接口,你可以通过命令行调用powershell.exe把脚本内容传进去

花火回复好快呀,太感谢了!
不过还有点问题:“通过命令行调用powershell.exe把脚本内容传进去” 这句话具体指什么?用什么样的语句实现?
敲完这6行powershell命令,Julia只能读一次,下次读还得敲6行。
怎么才能实现Julia自动控制仪器仪表呢?

我试了 julia> data=read("\\.\pipe\406bc1a3-e61e-4629-b737-0fd2b9430126")敲下去,$server.WaitForConnection()就会返回,Julia阻塞;直到敲完$server.Close(),Julia才返回:
julia> println(String(data))
Keysight Technologies,N9030B,MY57140913,A.22.08

尝试把命令存在文件里,在cmd里运行,固定名字管道,报错了:
C:\Users\Hao\Documents\Julia\EtherJulia>PowerShell.exe -file InstrumentName.ps1
Exception calling “.ctor” with “1” argument(s): "All pipe instances are busy.
"
At C:\Users\Hao\Documents\Julia\EtherJulia\InstrumentName.ps1:1 char:1

  • $server=[System.IO.Pipes.NamedPipeServerStream]::new(“VSA”)
  •   + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
      + FullyQualifiedErrorId : IOException
    
    

You cannot call a method on a null-valued expression.
……
改成新建guid,PowerShell.exe -file InstrumentName.ps1 阻塞,不报错,但是不知道新的guid是多少,Julia也不知道该去连谁……
$guid = New-Guid
$guid
$server=[System.IO.Pipes.NamedPipeServerStream]::new($guid)
固定名字或匿名管道的ps1脚本怎么写?
每次从cmd调用PowerShell,是不是都是全新的命名空间?上一次创建的变量都不见了?

在Julia REPL中调用不带参数的PowerShell命令,可以成功:
run(PowerShell.exe "Add-Type -Path 'C:\\Users\\Hao\\Documents\\Julia\\Drivers\\visaDlls\\visaLibFwk.dll' -PassThr")
返回
IsPublic IsSerial Name BaseType


True False Connection System.Object
True False GpibConnection visaLib.VisaConnection
True False IConnection
True False VisaConnection visaLib.Connection
False False System.Object
False True <>c System.Object

上一条命令Add-Type添加的.dll动态链接库,下一条命令访问不到:
run(PowerShell.exe "\$VSA = [visaLib.VisaConnection]::new(\"TCPIP0::192.168.1.213::hislip0::INSTR\", 5000)")
返回
Unable to find type [visaLib.VisaConnection].
At line:1 char:8

  • $VSA = [visaLib.VisaConnection]::new("TCPIP0::192.168.1.213::hislip0: …
  •    ~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : InvalidOperation: (visaLib.VisaConnection:TypeName) [], RuntimeException
    • FullyQualifiedErrorId : TypeNotFound

这两句写到同一个ConnectVSA.ps1文件中运行,就没问题了。
再加上两句
$InstrumentName = $VSA.StrSendReceive("*idn?")
$InstrumentName
Julia可以直接读回Add-Type的打印和InstrumentName,没有用到管道。
但是每次收发消息都要重新加载dll、重新建立VISA连接,好像太傻了,不知道怎么才能共用同一个连接?……
julia> read(PowerShell.exe -file ConnectVSA.ps1)
1471-element Array{UInt8,1}:
0x0d
0x0a
0x49
0x73
0x50
0x75
0x62
0x6c
0x69
?
0x30
0x38
0x0d
0x0a
0x0d
0x0a
0x0d
0x0a

julia> String(ans)
“\r\nIsPublic IsSerial Name BaseType
\r\n-------- -------- ---- --------
\r\nTrue False Connection System.Object
\r\nTrue False GpibConnection visaLib.VisaConnection
\r\nTrue False IConnection
\r\nTrue False VisaConnection visaLib.Connection
\r\nFalse False System.Object
\r\nFalse True <>c System.Object
\r\nKeysight Technologies,N9030B,MY57140913,A.22.08\r\n\r\n\r\n”

不过还有点问题:“通过命令行调用powershell.exe把脚本内容传进去” 这句话具体指什么?用什么样的语句实现?

如果文件太长 其实也可以用-File的

改成新建guid,PowerShell.exe -file InstrumentName.ps1 阻塞,不报错,但是不知道新的guid是多少,Julia也不知道该去连谁……

传过去啊

每次从cmd调用PowerShell,是不是都是全新的命名空间?上一次创建的变量都不见了?

那肯定的,进程都退了

但是每次收发消息都要重新加载dll、重新建立VISA连接,好像太傻了,不知道怎么才能共用同一个连接?……

你这需求相当于是要让PowerShell当RPC服务器啊……
也可以的,我给你写一个


EDIT 走你 Julia使用命名管道进行跨进程通信的示例

如果能在Julia里直接写C#,不是更棒吗(雾)

CLR

如果进展顺利的话应该过段时间就能放出来了

1 个赞