示例:
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[]
strng[]
.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#");
//可选指定编译参数
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 对象的下标实际上是访问 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 支持通过调用参数的类型自动匹配 .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 为例,支持以下用法:
Task.$Run("System.Int32")
参数传入 .NET 类型名称构建泛型。Task.$Run(System.Int32)
参数直接传入 .NET 类型构建泛型。Task.$Run(0)
自动获取该调用参数的 .NET 类型构建泛型。Task.Run.$(System.Int32)
//Task.Run 是一个类才能用单独的 $
作为函数名构建泛型。.NET 枚举在 aardio 中就是普通的数值。对于 aardio 这样的动态语言,将 .NET 枚举代码直接写成数值是更高效的方法(要确定枚举值后面不会改动,改动枚举值的情况其实很罕见一般可以忽略不记 )。
.NET 中的 System.Drawing.Color 对应于 aardio 中的 ARGB 格式颜色数值,ARGB 值适用于 gdip(GDI+)库、 plus 控件。
.NET 数组传入 aardio 的规则
.NET 交错数组在封包为 DispatchableObject 对象,然后将 DispatchableObject 对象封包为 aardio 中的 dotNet.object对象。
封包为 dotNet.object 的数组支持以下特性:
netArray.Length
。netArray[1].num
。支持 dotNet.each 遍历数组成员。例如:
for i,v in dotNet.each(netArray) {
console.log(i,v)
}
可用 table.parseValue 展开为普通数组。
.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() }
.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 数据表格示例:
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);
相关范例: 调用 UWP 接口 .NET 泛型 / 异步任务