aardio + C# 重大更新来了!

admin2022-04-09  863

我们用 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 的范例都重新整理了一遍,目录结构、相关代码说明也重新整理了:


转载请注明原文地址: http://www.aardio.net/read-304.html
最新回复(0)
aardio问答
aardio编程语言