我们用 aardio 调用 C# 生成 EXE 发行包几百 KB 不太难。再加上前几天添加的 dotNet.reference() 函数,只需要一句代码就可以把一堆 DLL 程序集嵌入内存加载 —— 调用 C# 生成独立 EXE 就很简单了。
在这样激动人心的大好形势下,我们不免就要思考一个问题:“为什么不搞得更完美一些呢……”,好吧,今天我们要把 aardio C# 开发会遇到的坑、不方便的地方全部抓出来并清除干净。
首先第一个很坑的问题,aardio 不支持 C# 定义的 struct ,当然并不是真的不支持,我指的是不用写代码的超级懒人包式的全自动化支持。毕竟人生苦短,时代不同了,没有几个人有耐心慢慢地堆代码。
其实静态 struct 原本是用于描述和传输原生内存结构,各种语言支持和交互起来都很容易,几乎不用写太多代码。但 C# 的结构体 —— 要与其他语言交换就很难搞。如果按原生结构体的规则来,你让大家看到每个字段前一堆的
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
这太累人了,要不走 COM 接口获取结构体也很麻烦,麻烦并不一定只是工作量,也会带来更多的 BUG 以及更多的不确定性。
复杂是不好的,简洁才是我们的目标。于是我决定换个思路,不转换这个结构体了,直接让他留在 .Net 里,然后通过一个代理对象去访问和控制这个结构体,这方法简单,几句代码就解决了问题,而且避免了复制的开销,还可以让两个不同的编程语言轻松的共享同一内存结构。
好吧,一起来看看新版范例,aardio 调用代码是很简洁的(或者说其实根本不用写代码,来去传输底层都由 aardio 自动解决了)。
import dotNet;
var compiler = dotNet.createCompiler("C#");
compiler.Source = /***
namespace CSharpLibrary
{
public struct TestStruct
{
public TestStruct(string n, double v)
{
Name = n;
Value = v;
}
public string Name;
public double Value;
};
}
***/
//编译生成程序集,导入 C# 名字空间
var CSharpLibrary = compiler.import("CSharpLibrary");
//调用 C# 创建结构体
var testStruct = CSharpLibrary.TestStruct("测试",12345);
//输出值看一下
import console;
console.logPause(testStruct.Name,testStruct.Value);
是不是帅呆了?!
接着看下一个问题,如何支持 C# 里的 tuple ,这里说的比较难搞的 ValueTuple。其实研究 aardio C# 开发很有意思,你可以了解到很多奇怪的知识,可能单纯做 C# 开发一般不会注意到这些。
我们先用 C# 写一个 TestValueTuple.dll,源码如下:
namespace TestValueTuple
{
public class Class1
{
public static (double, int, int, int, int, int, int, int, int, int, int) tupleValue = (12.3, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22);
public static (double, int) GetTupleValue()
{
return (12.3, 22);
}
}
}
用起来就非常简单了:
import console;
import dotNet;
//加载程序集 TestValueTuple.dll 并导入 TestValueTuple 名字空间
var TestValueTuple = dotNet.import("TestValueTuple");
//直接获取 C# 中的 tuple
var tuple = TestValueTuple.Class1.tupleValue;
console.log(tuple) //调用 tostring(tuple)
console.log(tuple.Item1) //返回第 1 个元素的值
console.log(tuple.Item2) //返回第 2 个元素的值
console.log(tuple.Item7) //返回第 7 个元素的值
。
console.log(tuple.Rest) //调用 tostring(tuple.Rest)
console.log(tuple.Rest.Item1) //返回第 8 个元素的值
//当然也可以这样修改值。
tuple.Rest.Item1 = 99;
console.pause();
tuple 最多只能有 7 个元素,你说你想要 8 个?首先写这么长的 tuple 不至于吧?!如果真要写第 8 个,.Net 还给了个机会 —— 可以在 Rest 里放下一个 tuple 来存放更多的元素。
其实搞定这个 tuple 支持的代码也没几句,原理跟前面支持 struct 的方法是一样的。
我们再来看下一个问题,我们知道,.Net 折腾了很多年才艰苦地支持了 Python 这种动态语言,而且 Python 玩 .Net 还是有点吃力的,我上次就看到有人折腾了一天部署 Python .Net 环境还失败了…… 我们就更不要说用于支持这些功能的底层代码有多少了。但是 aardio 说实话实现了 aardio .Net 非常方便而且简洁的接口,代码却没几句,而且兼容到极旧的 .Net 运行时。我可以说 aardio .Net 开发,稍有基础的用户 10 秒钟内就能把范例运行起来,10 分钟就可以熟练地使用。
但这样搞有问题吗?其实还真有一个大坑。我们知道 aardio 这种非 .Net 语言与 C# 这样的 .Net 语言运行机制存在巨大的差别,两个语言存储的数据在不同的内存空间 —— 并拥有不同的格式。例如 aardio 中的一个数值传到 C# 里那就完全是另一个对象了,这对于最常见的传值类型的参数不是问题。但如果想要传址(传引用)这就糟糕了,例如 C# 里 ref, out 类型的参数,可以在被调用的函数内部修改参数的值,并且影响到调用者传入的参数。这对于 aardio 调 C# 基本可以说是无法实现的事。
其实不能实现的事我们不一定要去钻牛角尖,很多时候我们只要换个思路,就可以轻松地绕过去。所以我们不要用 aardio 解决这个问题,我们用 C# 来解决这个问题。我用 C# 写了一个类,将任意的用 C# 创建的对象用这个类包装起来,然后用 aardio 遥控这个类就行了,因为这个对象始终在 C# 的地盘里,当然也就可以支持 ref,out 这些参数了。
这个方法确实很简单,代码量没几句,但是真要做得简单易用,却并不容易。我们要在 aardio 与 .Net 交互的每一个环节自动处理所有的事,遇到需要包装的类型就进行包装,需要拆包的时候要准确地拆包。.Net 的类型其实是非常复杂的,当我们小心翼翼、精细地排除一切 BUG 以后,终于一切变得美好,最终 aardio 中增加了以下创建这种纯 .Net 对象的函数:
dotNet.object(value,byRef)
dotNet.byte(value,byRef)
dotNet.ubyte(value,byRef)
dotNet.word(value,byRef)
dotNet.uword(value,byRef)
dotNet.int(value,byRef)
dotNet.uint(value,byRef)
dotNet.long(value,byRef)
dotNet.ulong(value,byRef)
dotNet.float(value,byRef)
dotNet.double(value,byRef)
上面除 dotNet.object() 支持任意类型参数以外,其他函数都用于封装并创建 .Net 数值对象,函数名前面带 u 的表示无符号数( aardio 静态类型中用大写表示 U , 也是无符号的意思 ), 如果 byRef 参数为 true 则会支持 ref,out 等输出类型参数。
value 的值可以是普通的值或数值,也可以是数组,例如
dotNet.double({1,2,3})
就在 .Net 里创建了一个 double 数组,这个函数返回的对象底层其实是有些复杂的 —— 我们先忽略他,因为用法很简洁。这个 double 数组实际上是存储在 .Net 里,但是 aardio 中也可以读写这个数组。这样我们就创建了一个 aardio 与 C# 可以同时访问的数组,这非常酷。
注意 .Net 数组总是传址( 传引用 ) 所以没必要指定 byRef 的值,但非数组的普通数据默认是传值 ,当 byRef 为 true 时则支持 ref,out 类型引用参数。
其实我们前面说的 struct ,tuple 支持底层都是同一机制实现,不过这些由 aardio 在底层自动封装 —— 表面上我们感觉不到这一层机制。
下面先看看用这些新的函数实现的 ref,out 参数调用示例:
//引用参数
import dotNet;
var compiler = dotNet.createCompiler("C#");//创建C#语言编译器
compiler.Source = /***
namespace CSharpLibrary
{
public class Object
{
public static void Test(ref double num,int [] arr){
num = 12.3;
arr[0] = 56;
}
}
}
***/
var CSharpLibrary = compiler.import("CSharpLibrary"); //自程序集导入名字空间
//参数@2 指定 true 创建引用参数。
var num = dotNet.double(12.5,true);
//也可以创建 .Net 数组,数组总是传引用的,参数@2不必要指定为 true
var arr = dotNet.int({1,2,3});
//调用函数
CSharpLibrary.Object.Test(num,arr);
import console;
console.log( arr[1] ) //在 aardio 中这种数组可用下标直接读写数组成员。
//如果 num 是 Primitive 类型(数值、布尔值),则可以简写为 num[0]
console.log( num[0] ); //等价于 dotNet.unWrapObject(num).Value
//支持 tostring(),tonumber() 转换。
console.log(tostring(num),tonumber(num));
console.pause();
然后顺手把 aardio 中所有调用 .Net 的范例都重新整理了一遍,目录结构、相关代码说明也重新整理了:
主题数 170 | 今日评论 0 | 今日主题 0 |