aardio 文档

aardio 调用 .NET 入门指南

导入 .NET 名字空间 #

示例:

import dotNet;

/*
加载 .NET 程序集(DLL文件),参数可以是程序集名或者文件路径。
默认会搜索系统目录、应用程序目录、或 "/bin/" 目录下的程序集。
*/
var assembly = dotNet.load("System");

//导入 .NET 名字空间
assembly.import("System");

//调用 .NET 名字空间的成员
var v = System.Uri.CheckHostName("www.aardio.com") 

如果程序集与要导入的名字空间相同可调用 dotNet.import 函数导入:

//导入程序集以及与程序集与同名的名字空间
dotNet.import("System");

使用 dotNet.loadFile 加载程序集 #

dotNet.load 可以使用参数指定的程序集短名称搜索并加载程序集,而 dotNet.loadFile 则是加载参数中指定的具体程序集,

dotNet.loadFile( assemblyPathOrData ) 参数 @1assemblyPathOrData 可指定程序集文件路径、资源文件路径或者程序集的内存数据加载程序集。

内存加载程序集示例:

dotNet.loadFile($"/dll/your-assembly.dll");

在 aardio 中文件路径开始的单个斜杆或反斜杠表示应用程序根目录。在引号包围的文件路径前面加一个 $ 表示将该文件直接编译到内存(发布后不再需要原始文件)。

使用 dotNet.loadFile( assemblyPathOrData,pdbPath ) 格式调用时,可用 pdbPath 参数指定调试文件路径。

注册内存程序集 #

dotNet.loadFile 加载的程序集不能作为其他程序集的依赖项。

如果程序集之间相互依赖,应当如下注册虚拟程序集。

dotNet.reference({

    //等号前面是程序集短名称,等号后面是 DLL 内存数据
    "assemblyName" = $ "/dll/your-assembly.dll";

    "assemblyName2" = $"/dll/your-assembly2.dll";   
}) 

当 .NET 找不到程序集时,会到 dotNet.reference 创建的虚拟程序引用表中搜索,虚拟程序集不但支持 aardio 代码,也能支持 .NET 代码。

只要调用 dotNet.reference 注册虚拟程序集,其他加载程序集的代码就不用再修改,这样可以非常方便地支持生成独立 EXE 文件。

System 库

aardio 标准库里有一个 System 库, 这个库的关键代码只有 2 句:

import dotNet;
dotNet.import("System");

创建与使用 .NET 对象 #

导入 .NET 名字空间以后在 aardio 中仍然要通过完整的名字空间访问 .NET 对象,这与 C# 使用 using 语句实现略写名空空间的用法是不同的。

但我们可以将名字空间赋值为更短的变是,例如:

var Uri = System.Uri;

//调用 .NET 类构造对象实例,在 aardio 里不需要写 new 关键字。
var uri = Uri("https://www.aardio.com/?q=.NET")

在 aardio 中使用 .NET 比较简单,示例:

//读或写 .NET 对象的实例属性
var host = uri.Host 

//调用 .NET 对象实例的成员函数
var hash =  uri.GetHashCode();

//.NET 对象还可以用 tostring 转换为字符串
var str =  tostring(uri) ; 

aardio 官方建议的命名规范:

aardio 代码建议使用小驼峰命名风格小写变量名的首字母。

.NET 类一般大写首字符,aardio 实现的类通常小写首字符。

类型转换 #

aardio 中的基础类型:

.NET 中的值在 aardio 中则分为两类:

  1. null值、数值、字符串、枚举、 System.Drawing.Color 等简单值类型,以及这些值类型的数组可以直接交换。
  2. 其他原生 .NET 对象在 aardio 中存为 com.NETObject 对象(对应 .NET 中的 System.__ComObject 类型)。

.NET 与 aardio 交互的底层接口是 COM 接口,.NET 对象在 aardio 中内里本质是一个 COM 对象,所以我们可以用 com.Release()提前显式释放 .NET 对象( 一般不需要这样做,会自动回收内存 )。COM 只是基础接口,aardio 与 .NET 的实际交互是通这 .NET 反射实现交互调用。

部分无法直接操作的.NET 对象会被封装为 dotNet.object 对象(在 .NET 里是一个 DispatchableObject 对象 ),dotNet.object 也可以用于创建需要在 .NET 函数中引用传参的对象。类似 dotNet.int 等函数可以创建特定类型的 dotNet.object 。

更多细节请参考文档:aardio 与 .NET 数据类型自动转换

在 aardio 中编译 C# 代码 #

import dotNet; 

//创建C#语言编译器
var compiler = dotNet.createCompiler("C#");

//可选指定编译参数
compiler.Parameters.CompilerOptions = "/optimize /unsafe" ;

//下面是待编译的 C# 源代码
compiler.Source = /***
namespace CSharpLibrary  
{  
    public class Object  
    {     
        public static string Test(ref double num){   
            num = 12.3;
            return "<? ='C# 代码可以嵌入 aardio 模板代码' ?>";
        }
    }   
} 
***/

//编译程序集并且导入名字空间,等价于 compiler.Compile().import("CSharpLibrary")
compiler.import("CSharpLibrary");  

//创建 .NET 引用参数( double 类型)
var ret = dotNet.double(12.5,true);

print( ret );

如果 compiler.Source 指定字符串则支持使用 aardio 模板语法<? ?> 中间嵌入 aardio 代码到 C# 代码中以生成新的 C# 代码。

.NET 下标 #

.NET 对象的下标实际上是访问 Item[] 属性。

如果 netObj 是一个 .NET 对象,在 aardio 中写 netObj["key"]netObj.Item["key"] 作用是相同的。但如果 如果 netObj["key"] 读取值失败且 netObj 不存在 Item[] 属性,aardio 将会自动修改为 netObj.key 读取 netObj 名为 key 的属性。

如果下标是数组则略有不同,通过 Item 属性访问下标时按 .NET 规则起始索引为 0,例如netObj.Item[0] 访问第一个成员。不通过 Item 属性并且直接使用数值下标时按 aardio 规则起始索引为 1,例如 netObj[1] 访问第一个成员。

也可以通过函数调用方式访问 Item 属性,支持多参数,可选按 COM 接口规则加上 get 或 set 前缀,例如:

.NET 泛型 #

aardio 支持通过调用参数的类型自动匹配 .NET 泛型,示例:

import System.ValueTuple; 

/*
创建 ValueTuple 对象,最多支持 8 个参数。
注意这是一个典型的泛型函数,aardio 可以通过参数类型自动匹配泛型函数。
*/
var tuple = System.ValueTuple.Create(
    21,22,23,24,25,26,27,
    dotNet.double(28)  
);

也可以在 .NET 类名或函数名前面添加一个 $ 符号表示创建泛型类或函数(构造具体类型), 参数为一个或多个 .NET 类或类型名称(字符串)参数。

以 Task.Run 为例,支持以下用法:

.NET 枚举(enum) #

.NET 枚举在 aardio 中就是普通的数值。对于 aardio 这样的动态语言,将 .NET 枚举代码直接写成数值是更高效的方法(要确定枚举值后面不会改动,改动枚举值的情况其实很罕见一般可以忽略不记 )。

System.Drawing.Color #

.NET 中的 System.Drawing.Color 对应于 aardio 中的 ARGB 格式颜色数值,ARGB 值适用于 gdip(GDI+)库、 plus 控件。

.NET 数组 #

.NET 数组传入 aardio 的规则

  1. 基本纯值类型兼容的 .NET 数组、多维数组在 aardio 中 存为 com.SafeArray 数组。COM 数组与普通 aardio 数组操作完全相同。
  2. .NET 交错数组在封包为 DispatchableObject 对象,然后将 DispatchableObject 对象封包为 aardio 中的 dotNet.object对象。

    封包为 dotNet.object 的数组支持以下特性:

    .NET 的 一维 System.Object 数组在 aardio 中封包为 dotNet.object。如果数组中实际保存的是可以直接互换的通用纯值( 例如字符串、数值、布尔值 …… ),在 aardio 中可用 dotNet.getObject(netObjArray).Value 解包为原生数组。

aardio 数组传入 .NET 的规则:

  1. 基本纯值类型兼容的 .NET 数组、多维数组可以直接交换。
  2. 其他类型化数组必须明确匹配参数中数组的类型。

    aardio 中创建 .NET 类型化数组的例子:

    var objArray = dotNet.createArray( CSharpLibrary.Object, 2 )
    
    //创建的数组默认是空的,添加元素进去
    objArray[1] = CSharpLibrary.Object(); 
    objArray[2] = CSharpLibrary.Object(); 
    

    下面这样写也可以,aardio 会自动转换为类型化数组。

    var objArray = { CSharpLibrary.Object(),CSharpLibrary.Object() } 
    

.NET 委托回调 #

.NET 委托自动支持 aardio 函数对象。

.NET 的调用参数、属性都支持直接传入 aardio 函数,示例:

//函数参数传入委托
netObj.Hello(function(){
    //无论.NET 回调是否在同一线程,被 .NET 回调的 aardio 函数总是在原调用线程执行(不必考虑多线程规则与同步)。  
    console.log("3、aardio 函数被 C# 调用了,参数:",a,b)
    return 2; //如果不返回委托指定类型的返回值会导致报错,委托返回值类型为 void 时这里可以不返回值。
})

//委托回调可以直接赋值为 aardio 函数对象
netObj.callback = function(a,b){
    console.log("1、aardio 函数被 C# 调用了,参数:",a,b)
    return 1; 
} 

也可以如下追加委托回调函数:

//也可以这样追加委托回调函数
dotNet.delegate.combine(
    netObj,"callback",function(a,b){
        console.log("2、aardio 函数被 C# 调用了,参数:",a,b)
        return 2; 
    } 
)

至于 .NET 中的 event 实际上是在委托(delegate)上再做了层封装。在 aardio 中对 event 赋值总是追加而不是覆盖(对delegate 赋值则总是覆盖而不是追加),用法与委托基本相同。

dynamic 与 InvokeMember #

InvokeMember 是通过反射调用 aardio 对象,参考 InvokeMember 范例

dynamic 则更为简洁和强大,只要在 C# 中将 aardio 对象声明为 dynamic类型就可以直接使用。.NET 4.x 都支持 dynamic 对象, 而 Win10 已自带.NET 4.6 以上,所以如是果一定要考虑 Win7 系统可以用 InvokeMember 替代,否则使用 dynamic 会方便很多。

dynamic 非常简单,唯一要注意的是 dynamic 是由 .NET 通过 COM 接口调用 aardio 对象,如果回调参数中有非基础纯值类型的 .NET 对象不会自动转换为 dotNet.Object,需要手动转换一下,例如 netObj = dotNet.object(netObj), dotNet.Object 基于 .NET 反射可以支持更多 .NET 特性,例如重载(函数名相同,参数不同)。

参考 dynamic 范例

扩展 .NET 类 #

导入的 .NET 类如果定义名为 ctor(...) 的扩展构造函数。则该函数将在 aardio 构造 .NET 类之前自动执行。示例如下:

import dotNet;
dotNet.import("System"); 


System.Uri[["ctor(...)"]] = function(netCtor,url,...){

    //在这里可以改动、检测构造参数
    if(!url) error("参数 @1 请指定网址",3);

    //调用真实的构造函数 netCtor 创建对象,该函数成功返回对象,失败返回 null,错误信息
    var obj,err = netCtor(url,...); //返回新的构造参数

    //返回创建的对象
    return obj,err;
} 

在 aardio 中实例化 .NET 类以后,可自动调用名为 "ctor" 的扩展构造参数(不能拦截构造参数,也不需要返回值)。 这个函数的参数就是已经创建的 .NET 对象实例。示例如下:

import dotNet;
dotNet.import("System"); 

//this 参数为已创建的 .NET 对象实例
System.Uri[["ctor"]] = function(this){ 

    /*
    aardio 读写 .NET 对象的成员会触发对象元方法并转换为 .NET 调用。
    如果改用直接下标 [[]] 就可直接读写对象的成员,不会触发元方法也不会解析为 .NET 调用。
    */
    this[["getHostAndHashCode"]] = function(){
        return owner.Host,owner.GetHashCode();
    }   
}

完整范例

.NET 字典(Dictionary) #

.NET 字典(Dictionary)是泛型类,可以像下面这样创建 Dictionary。

var Dictionary = System.Collections.Generic.Dictionary.$(System.String,System.String)
var dict = Dictionary();

更简单的方法是使用 dotNet.dict 创建 .NET 字典(Dictionary) 。 参数 @1 指定一个 aardio 非空表的数据类型必须相同,的数据类型也必须相同。参数 @2 如果为 true 则返回对象可用于 .NET 输出引用参数,参数 @2 是可选参数。

import dotNet; 

var dict = dotNet.dict(
    a="abc",
    d="789"
)

dict["a"] = "新的值";
var v = dict["a"];

print(v);

嵌入 .NET 控件到 aardio 窗口 #

嵌入 .NET 数据表格示例:

import win.ui;
/*DSG{{*/
var winform = win.form(text="嵌入 .NET 数据表格";right=759;bottom=510)
winform.add(
button={cls="button";text="读取数据";left=495;top=439;right=739;bottom=504;color=14120960;db=1;dr=1;font=LOGFONT(h=-14);note="获取数据源中的数据";z=3};
custom={cls="custom";left=25;top=25;right=736;bottom=274;db=1;dl=1;dr=1;dt=1;z=1};
edit={cls="edit";left=26;top=290;right=737;bottom=435;autohscroll=false;db=1;dl=1;dr=1;edge=1;multiline=1;vscroll=1;z=2}
)
/*}}*/

import System;
import System.Data;
import System.Windows.Forms; 

//.NET 控件必须用 System.Windows.Forms.CreateEmbed 嵌入 aardio 窗口
var dataGridView = System.Windows.Forms.CreateEmbed("DataGridView",winform.custom);  
dataGridView.EditMode = 2; 

//添加数据列
var dataTable = System.Data.DataTable("DT"); 
dataTable.Columns.Add("名称");//添加列
dataTable.Columns.Add("计数",System.Double); //添加指定数据类型的列
dataTable.Columns.Add("选择",System.Boolean); //自动显示复选框 

//绑定数据源到视图
var dataView = System.Data.DataView(dataTable);
dataGridView.DataSource = dataView;

//替换名字为"名称"的列为下拉框控件(参考 System.Windows.Forms 库源码)
dataGridView.ReplaceNameValueListColumn("名称",120/*宽度*/,
    { "王五","张三","李四"},
    { "WangWu","ZhangSan","LiSi"}
); 

//添加测试数据
var row = dataTable.NewRow(); 
row.ItemArray = {"WangWu",123, true}
dataTable.Rows.Add(row);   

/*
可以用 FillFromArray 函数一次性填充数据。
避免密集调用 .NET 参数,减少不必要的跨语言交互成本。
FillFromArray 实现请查看 System.Data.DataTable 源码。
*/
dataTable.FillFromArray({
    {"ZhangSan",456,false};
    {"LiSi",789,true}
})

//添加事件(event)
dataTable.ColumnChanged = function(sender,eventArgs){
    var columnName = eventArgs.Column.ColumnName;
    var value  = eventArgs.Row.getItem(columnName);  
    winform.edit.print("已改变列:",columnName," 已变更值:",value);
}

//读取数据
winform.button.oncommand = function(id,event){ 

    //一次读取所有数据到 aardio 数组
    var data = dataTable.ExtractToArray();

        //输出结果
    for(i=1;#data;1){  
            winform.edit.dump(  data[i]  )  
    }

    //修改数据
    data[1][2]= 999;

        //一次性替换并更新原来的数据,更快
    dataTable.FillFromArray(data,true)

        //下面是更原始的方法,跨语言交互次数多一些
    /*
    for(i=1;dataTable.Rows.Count;1){ 
            var arr = dataTable.Rows[i].ItemArray; 
            winform.edit.print( arr[1] ; 
    } 
    */
}

winform.show();
win.loopMessage();

调用 UIA 接口 #

基础用法:

import System.Windows.Automation;

//访问 .NET 类的静态成员
Automation = System.Windows.Automation;
AutomationElement = Automation.AutomationElement;

//直接获取当前输入焦点窗口的 UIA 节点对象
var ele = AutomationElement.RootElement.FocusedElement;
assert(ele,"输入焦点窗口没有找到 UIA 节点")

//获取窗口句柄 
var hwnd = editBox.Current.NativeWindowHandle;

//自窗口句柄得到 AutomationElement 对象
ele = Automation.AutomationElement.FromHandle(hwnd);

//鼠标操作,移动鼠标到控件位置
import mouse;

//注意需要通过 Current 成员访问 UIA 对象的属性
mouse.moveTo(ele.Current.BoundingRectangle);

查找窗口:

//导入 .NET 类  
import System.Windows.Automation;

//访问 .NET 类的静态成员
Automation = System.Windows.Automation;   

//查找窗口。由 System.Windows.Automation.And 生成查询条件。
var notepad = Automation.FindByAnd({
    ClassName = "Notepad",
    ControlType = "Window";
}) 

//查找写字板的编辑框。由 System.Windows.Automation.Or 生成查询条件。 
var editBox = Automation.FindByOr({
    ClassName = {"RichEditD2DPT","RICHEDIT50W","Edit" } 
},notepad) 


var textPattern = editBox.GetCurrentPattern(Automation.TextPattern.Pattern);

//获取全部文本
var text = textPattern.DocumentRange.GetText(-1);

print(text);

print( editBox.Current.LocalizedControlType )

import System.Windows.Automation 改为 import System.Windows.Automation.3 可支持 TextPattern2 等接口,这两个库使用时都要写为 System.Windows.Automation,用法也完全相同。但同一个程序里只能使用其中一个。标准库中 winex.caret, winex.selection, winex.wpfCaret 等库已经包含 import System.Windows.Automation.3

调用 UWP / WinRT 接口 #

aardio 提供 dotNet.uwpCompiler 可用于快速创建启用 UWP / WinRT 接口的 .NET 编译器,不需要复杂的配置。

标准库 dotNet.ocr,扩展库 dotNet.toastListener 都使用了 ddotNet.uwpCompiler 编译的 DLL 程序集以调用 UWP 接口。

使用 dotNet.uwpCompiler 编译 C# 代码需要先安装 Windows 10 SDK ,dotNet.uwpCompiler 需要引用 Windows 10 SDK 提供的 Windows Runtime 元数据文件 Windows.winmd 。

如果仅仅是调用编译后的 DLL 不需要 Windows 10 SDK 或者 Windows.winmd 。

在 aardio 中启用 UWP 接口编译 C# 代码生成 DLL 示例:

import console.int;
import dotNet.uwpCompiler;

//创建启用 UWP / WinRT 接口的 .NET 编译器
var uwpCompiler = dotNet.uwpCompiler("/uwpTest.dll")
uwpCompiler.Parameters.CompilerOptions = "/optimize"

//C# 源代码
uwpCompiler.Source = /****** 
using System; 
using System.Threading.Tasks; 

namespace aardio.WinRT{

    public class API {

        //异步函数
        public async Task<bool> LaunchUriAsync(string uri) {
            return await Windows.System.Launcher.LaunchUriAsync(
                new Uri(uri)
            );
        }

        //异步函数转换为同步函数
        public bool LaunchUri(string uri){   
            return LaunchUriAsync(uri).GetAwaiter().GetResult();
        }  
    }
}
******/

//编译并生成 DLL
var ret = uwpCompiler.Compile();
print( ret ? "编译成功" : uwpCompiler.getLastError() );

调用编译后的程序集:

//加载编译后的程序集
var assembly = dotNet.load("/uwpTest.dll");

//导入名字空间
assembly.import("aardio.WinRT");

//创建 .NET 对象
var uwp = aardio.WinRT.API();

//调用同步方法
uwp.LaunchUri("ms-screenclip:");

如果是调用异步函数,可用 dotNet.wait 等待任务完成并获取调用结果,示例:

//调用异步方法
var task = uwp.LaunchUriAsync("ms-screenclip:")

//同步等待任务完成,不会阻塞界面
var result = dotNet.wait(task);

相关范例: 调用 UWP 接口 .NET 泛型 / 异步任务

Markdown 格式