unity-工具-csharp与python交互
很多时候写工具都是使用 python 来写, unity 工具需要调用 python 脚本并获取到执行结果.
非弹窗式
可以从 Python 的 os 的 标准输出 (
sys.stdout.write
) 中返回给 cshappython 的
sys.stdout.write + 换行符
csharp 执行 命令行 的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44// 可以获取到 py 脚本 print 的值
public static string ProcCmd(string command, string argument) {
ProcessStartInfo psi = new ProcessStartInfo(command);
psi.Arguments = argument;
psi.CreateNoWindow = true;
psi.ErrorDialog = true;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.RedirectStandardInput = true;
psi.StandardOutputEncoding = System.Text.UTF8Encoding.UTF8;
psi.StandardErrorEncoding = System.Text.UTF8Encoding.UTF8;
Process p = Process.Start(psi);
StringBuilder sb1 = new StringBuilder();
StreamReader reader = p.StandardOutput;
while (!reader.EndOfStream) {
string line = reader.ReadLine();
if (!line.StartsWith("---")) { // 过滤掉自定义的日志, 方便 py 调试
sb1.Append(line);
}
if (!reader.EndOfStream) {
sb1.Append("\n");
}
}
reader.Close();
StringBuilder sb2 = new StringBuilder();
StreamReader errorReader = p.StandardError;
while (!errorReader.EndOfStream) {
sb2.Append(errorReader.ReadLine()).Append("\n");
}
errorReader.Close();
p.WaitForExit();
p.Close();
if (sb2.Length > 0) {
throw new Exception(string.Format("--- Error, python error, msg:{0}", sb2.ToString()));
}
return sb1.ToString();
}python 工具脚本
1
2
3
4
5
6
7#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
if __name__ == "__main__":
print("hello world, arg1: {}".format(sys.argv[1])) # 这个就能输出给 csharp测试 csharp 执行 python 结果
1
2
3string pyPath = Path.Combine(PackConfig.PyToolDir, "tool.py");
string res = EditorUtils.ProcCmd("python3", string.Format("{0} {1}", pyPath, "getProjBranch"));
Debug.LogFormat("--- py res: {0}", res);
弹窗式
单独一个命令行窗口, 标准输出不会返回给 csharp
Windows
csharp 执行 命令行 的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 弹窗执行 py
public static void ProcCmdOnWin(string command, string argument) {
ProcessStartInfo psi = new ProcessStartInfo(command);
psi.Arguments = argument;
psi.CreateNoWindow = true;
psi.ErrorDialog = true;
psi.UseShellExecute = true;
psi.RedirectStandardOutput = false;
psi.RedirectStandardError = false;
psi.RedirectStandardInput = false;
Process p = Process.Start(psi);
p.WaitForExit();
p.Close();
}
数据结构的传递
一般使用 json 作为数据结构的格式传递给 Python, 但是命令不能正常传递 json 字符串 给 Python 获取, 所以得用曲线救国的方式 base64 encode 一下给 py, 然后在 py 在 decode 解出正常的 json
csharp
1
2
3
4
5
6
7
8
9public static string Base64Encode(string plainText) {
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes).Replace("\n", "");;
}
public static string Base64Decode(string base64EncodedData) {
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}python
1
2
3
4
5
6def base64Encode(txt: str) -> str:
# 参考: https://blog.csdn.net/u010953266/article/details/52590570
# 根据RFC822规定,BASE64Encoder编码每76个字符,还需要加上一个回车换行
return base64.encodebytes(txt.encode()).decode().replace("\n", "")
def base64Decode(encodedData: str) -> str:
return base64.decodebytes(encodedData.encode()).decode()
踩坑
base64 encode 后有换行符
根据RFC822规定,BASE64Encoder编码每76个字符,还需要加上一个回车换行
解决办法: 将换行符去掉即可. 参考: https://blog.csdn.net/u010953266/article/details/52590570
macOS
mac 系统下就不能使用 Windows 那种方式直接执行 python 脚本. 但可以执行 shell 脚本, 但是也不可以传递多个参数.
所以曲线救国的思路就是
创建一个可执行 shell 脚本, 如:
mac_shell.sh
, 赋予 +x 执行权限1
$ touch mac_shell.sh && chmod +x mac_shell.sh
执行的命令写入到 shell 里面
open 命令执行这个 shell
1
2
3
4
5
6
7
8
9
10
11// 弹窗执行 py, 新开线程, 不阻塞 gui
public static void ProcCmdOnMacOs(string command, string argument) {
// 曲线救国, mac 下无法唤起带参数的命令行窗口, 只能先写到 shell 里面再执行 shell
string shellPath = EditorUtils.Join(PackConfig.TempSave, "mac_shell.sh");
Utils.WriteFileUTF8(shellPath, string.Format("{0} {1}", command, argument)); // 写入到 mac_shell.sh
new Thread(() => {
Process.Start("open", shellPath);
}).Start();
Thread.Sleep(500); // 睡眠一下 500ms, 防止多条指令同时执行
}