🅰 .NET 类型转换 📄 调试 .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.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 文件。
aardio 标准库里有一个 System 库, 这个库的关键代码只有 2 句:
import dotNet;
dotNet.import("System");
导入 .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 官方建议的命名规范:
uri 。aardio 代码建议使用小驼峰命名风格小写变量名的首字母。
.NET 类一般大写首字符,aardio 实现的类通常小写首字符。
aardio 中的基础类型:
double[]string[].NET 中的值在 aardio 中则分为两类:
.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 数据类型自动转换
import dotNet;
//创建C#语言编译器
var compiler = dotNet.createCompiler("C#");
//可选引入依赖 DLL 程序集(不要内存加载);
//compiler.Reference("System.dll"); //System.dll 默认已引入
//可选指定编译参数
//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.import("CSharpLibrary"); //等价于 compiler.Compile().import("CSharpLibrary")
//创建 .NET 引用参数( double 类型)
var ret = dotNet.double(12.5,true);
print( ret );
编译并生成本地程序集的的写法如下:
//编译生成 DLL 程序集文件,不指定参数生成内存程序集
var assembly = compiler.CompileOrFail("/output.dll");
//导入程序集与同名的命名空间
assembly.import("CSharpLibrary");
如果 compiler.Source 指定字符串则支持使用 aardio 模板语法 在 <? ?> 中间嵌入 aardio 代码到 C# 代码中以生成新的 C# 代码。
在 aardio 里调用 .NET 编译器只与 CLR 版本 有关,与具体 .NET 版本没有直接关系。 Win10 已自带 .NET 4.x ( CLR 4.0 ) ,而 CLR 2.0 编译器不支持 var ,lambda 这些语法。
所有 .NET 对象在 aardio 中都会增加一个小写的 each 方法用于创建迭代器。 但只有支持迭代接口的 .NET 集合对象才能使用 each 方法。
示例:
for i,v in netArray.each() {
// i 是迭代序号(自 1 开始), v 是当前迭代的值。
print(i,v)
}
.NET 命名空间、类名、外部可访问的 .NET 对象的公共方法与属性名首字母一般都会大写,而 aardio 里的命名空间、类名、对象的方法名与属性名首字母一般都是小写(小驼峰风格)。在 aardio 里通常会用命名来区分 aardio 对象与外部对象。例如一个COM 对象 或者 .NET 对象如果提供了小写首字母的方法或属性名,这通常暗示它是一个 aardio 额外增加的属性或方法。例如上面的
netArray.each就是一个 aardio 函数而非 .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 前缀,例如:
var value= netObj.Item(1,2) //多参数读 Item 属性var value= netObj.getItem(1,2) //多参数读Item 属性netObj.setItem(1,2,value) //多参数修改 Item 属性aardio 允许在下标操作符中用逗号分隔多个索引值。
下标包含多项索引时第一个索引必须是单个标识符或字面值而不能是复合表达式,后面的其他索引则无限制。例如写为
obj[x,(y+1)]这样是正确的,但写成obj[(1),2]这样就存在语法错误 。
示例:
netObject.multiDimensionalArray[0,0] = "值"
下面的代码等价于下面的代码:
//注意方括号中间有一个空格以避免包含数组的 [] 被识别为直接下标操作符。
netObject.multiDimensionalArray[ [0,0] ] = "值"
实际传递的索引是一个包含多项索引的纯数组( pure-array )。 aardio 中的 COM 接口、.NET 接口已经自动支持上述的多项索引。
更多关于多项索引的内容请参考:下标操作符 - 多项索引
.NET 数组传入 aardio 的规则
.NET 交错数组在封包为 DispatchableObject 对象,然后将 DispatchableObject 对象封包为 aardio 中的 dotNet.object对象。
封包为 dotNet.object 的数组支持以下特性:
netArray.Length 。netArray[1].num 。for i,v in netArray.each() {
console.log(i,v)//i 是数组索引,v 才是数组值
}
.NET 的 一维 System.Object 数组在 aardio 中封包为 dotNet.object。如果数组中实际保存的是可以直接互换的通用纯值( 例如字符串、数值、布尔值 …… ),在 aardio 中可用 dotNet.getObject(netObjArray).Value 解包为原生数组。
aardio 数组传入 .NET 的规则:
其他类型化数组必须明确匹配参数中数组的类型。
aardio 中创建 .NET 类型化数组的例子:
var objArray = dotNet.createArray( CSharpLibrary.Object, 2 )
//创建的数组默认是空的,添加元素进去
objArray[1] = CSharpLibrary.Object();
objArray[2] = CSharpLibrary.Object();
下面这样写也可以,aardio 会自动转换为类型化数组。
var objArray = { CSharpLibrary.Object(),CSharpLibrary.Object() }
aardio 支持通过调用参数的类型自动匹配 .NET 泛型,示例:
import System.ValueTuple;
/*
创建 ValueTuple 对象,最多支持 8 个参数。
注意这是一个典型的泛型函数,aardio 可以通过参数类型自动匹配泛型函数。
*/
var tuple = System.ValueTuple.Create(
21,22,23,24,25,26,27,
dotNet.double(28)
);
在 aardio 中可以在 .NET 泛型类名泛型方法名称前面添加一个 $ 符号以返回泛型实例化方法。
泛型实例化方法可以指定以下几种调用参数:
.NET 类,这可以是 dotNet.import 导入的类,也可以是实际的 .NET 类型对象。
例如:System.Array.$Empty(System.Int32)
包含 .NET 类型名称的字符串
例如:System.Array.$Empty("System.Int32")
指定其他类型的参数则会获取该参数的 .NET 作为泛型实例化参数。
例如:System.Array.$Empty(0)
示例:
import dotNet
//实例化 .NET 泛型函数
EmptyInt32Array = System.Array.$Empty("System.Int32")
//创建空的 .NET 数组实例
var arr = EmptyInt32Array();
实例化泛型类除了支持上面的 3 种写法以外,还额外支持以下两种写法:
使用泛型类的 $ 方法实例化泛型,例如:
StringList = System.Collections.Generic.List.$(System.String)
使用这种方法实例化的泛型类不会保存于 System.Collections.Generic 命名空间,只能自返回值接收。
按 .NET 的原生泛型类命名规则获取泛型实例,例如:
StringList = System.Collections.Generic["List`1[System.String]"]
上面代码里,反引号后面的数字是指参数的数目,[]内是参数的 .NET 类型,多个参数类型用逗号分开。
这种写法会在 System.Collections.Generic 命名空间直接存储实例化的泛型类,下次以相同的名称获取时就会直接返回。
.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 赋值则总是覆盖而不是追加),用法与委托基本相同。
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 类如果定义名为 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)是泛型类,可以像下面这样创建 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();
基础用法:
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。
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);