原生静态类型

友情提醒: aardio文档中提到的“API函数”一般是指的外部 DLL 文件提供的原生接口函数,例如操作系统DLL提供的“WinAPI”函数,DLL文件通常由C语言等原生静态编程语言编写,对于这些基础编程术语,什么是API什么是静态类型 - 属于基础的编程知识,本文档是 aardio 手册,不是编程科普读物,所以对于这些知识请大家自行了解。另外 aardio提供的 raw 库函数、静态数据类型这些本来就是给有基础、学有余力的用户准备的,一般用户可以不需要学习。
aardio可以使用table定义结构体(struct),在结构体中可以定义静态类型类型。请参考:raw库

结构体(struct table)

在 table 中以添加静态类型声明,使用类似于C语言中的语法声明API函数需要的结构体(struct)。
设定结构体成员初始值不是必须的, 转换为原生静态类型时 aardio 会自动初始化结构体成员为空值

通常,我们使用class来定义API函数中需要用到的结构体:

class POINT{
int x = 0; //结构体成员必须
int y = 0;
}

//创建一个结构体对象
pt = POINT();

//每个结构体会创建一个_struct只读字段记录类型定义,语义如下:
pt = { _struct = "int x;int y" }

API数据类型(API Datatype)

注意API数据类型声明严格区分大小写,数据类型大写表示对类型有更严格的限制条件。
其中数值类型小写表示允许负数,大写表示无符号数据类型(没有负数,仅有正整数)。
而对于支持指针的类型(string,pointer),小写表示允许null值并允许自动转换(例如字符串转换为指针),大写表示不接受null实参。


注意C/C++/WINAPI的数据类型名有一些乱,同一个类型有无数的不同名字, 而同一个类型名字可以处理为不同的类型,当然在 aardio 里静态类型已经尽可能地规范和简化,所以学习起来并不难,只要掌握几个类型就可以了。

例如句柄类型,有时候作为无符号数,有时候作为有符号数,有时候又作为指针类型处理,实际上你无论是使用指针类型、有符号数值类型、还是无符号数值类型,对于句柄来说内存中的值是相同的,例如topointer(-1) == topointer(0xFFFFFFFF) 的结果是相等的。 在aardio标准库里,一般句柄类型都被处理为指针,只有窗口句柄(HWND)在aardio中统一被声明为addr类型。
API数据类型 长度 对应的aardio数据类型 C语言类型 WINAPI
类型
备注
类型
无符号字节 BYTE 8位 数值 例: { char chr = 'A'# } unsigned char BYTE 注意C++中的bool(小写)类型为一个字节(8位),等价于aardio中的byte类型.
字节 byte char,
无符号短整型 WORD 16位 数值 例: { short n = 123 } unsigned short WORD
短整型 word short
无符号整型 INT
32位 数值 例: { int n = 123 } unsigned int,unsigned long DWORD winapi中H前缀通常表示32位数。
整型 int int,long LRESULT,
LPARAM,
WPARAM
无符号长整型 LONG64 64位 math.size64()长整数,或普通数值 例: { LONG a = 123; LONG b = math.size64(456) } unsigned long long ,unsigned __int64 可缩写为LONG,
1、API函数返回值中LONG类型返回为math.size64对象
2、在API回调函数中,LONG类型回调参数为math.size64对象
3、在结构体中LONG类型字段值为math.size64对象时,aardio始终
保持该对象的类型以及地址不变,反之则处理为64位浮点数值
长整型 long64 long long,__int64 可缩写为long



ADDR 32位/64位 数值 例: { ADDR n = 123 } void* UINT_PTR,
ULONG_PTR
,DWORD_PTR,
HWND
注意使用数值表示地址时,为保持更好的兼容性,请使用此类型,而不要使用固定位长的int,INT,long,LONG等类型替代,
addr INT_PTR,
LONG_PTR,
HWND
浮点数 float 32位 数值 例: { float n = 123 } float FLOAT
双精度浮点数 double 64位 数值 例: { double n = 123 } double
布尔值 bool 32位 true,false int BOOL
int
静态类型中的布尔值应明确指定true或false,而不应当象aardio类型那样自由的使用其他类型来替代.


注意C++中的bool(小写)类型一般实现为1个字节(8位),在aardio中应使用byte类型表示

而aardio中的bool类型,在C++中表示为BOOL(大写).

指针 pointer
ptr
32位/64位 pointer null void* PVOID,
LPVOID,
LPCVOID
HANDLE,
HINSTANCE
此类型可缩写为ptr,或PTR,也可以使用所有p开头的自定义类型名字表示指针。

大写的数据类表示参数不接受null指针(空指针为null,而不是0),不能转换字符串为常量指针。

指针值必须是一个pointer类型或null值,在aardio中空指针为null而不是0,table 或 cdata 对象可以通过元表中的_topointer成员返回一个有效指针(不能为null)或指针数值, _topointer可以是一个值(元数据)或者一个函数(元方法)。

注意在未声明直接调用 API 函数时,对于结构体 aardio 将默认忽略元表中的 _topointer 优先将其作为结构体处理,除非存在类型声明 —— 则优先按类型声明处理(如果声明为指针则执行 _topointer 元方法获取指针)

在 API 或结构体中声明为指针类型的参数或字段,都可以兼容指针、字符串、字节数组(buffer)、动态指针、声明了 _topointer 元方法的表或 cdata 对象。 如果一个被作为 API 函数输出参数的结构体的指针字段被赋值为 buffer 或 声明了 _topointer 元方法的表,在这个结构体返回时如果指针指向的地址没有变化则字段值不变(仍然指向原来的 buffer 或表对象)

POINTER
PTR
32位/64位 pointer
字符串 string 32位 string pointer null const char* LPCSTR,
LPSTR,   

二进制字符串(但是在结构体中自内存读取此类型字段时,会返回遇 '\0' 结束的纯文本字符串),也可以接受 pointer 指针值,大写则表示参数不接受 null 指针 。在API回调函数或结构体中 API 返回 0~0xFFFF 或 -1 的指针地址 aardio 将转换为指针而不是字符串

STRING 32位 string pointer
文本字符串 str 32位 string pointer null const char *

在普通API中表示'\0'结束的文本(不含 '\0'),在Uniocde API中等价于 ustring (遇 '\u0000' 结束)。在API回调函数或结构体中API返回0~0xFFFF或-1的指针地址aardio将转换为指针而不是字符串

字节数组 pointer 32位/64位 buffer null char *,unsigned char *

这种可以让 API 读或写数据的字节数组也就是 aardio 中的 buffer 类型,使用 raw.buffer() 函数创建 buffer。

如果一个被作为 API 函数输出参数的结构体的指针字段被赋值为 buffer ,在这个结构体返回时如果指针指向的地址没有变化则字段值不变(仍然指向原来的 buffer)

Unicode字符串 ustring 32位 ustring pointer null const wchar_t* LPCWSTR,
LPWSTR,   

Unicode(UTF16) 纯文本字符串 (遇 '\u0000' 结束),aardio 会自动做双向编码转换,传给 API 时是 UTF16 ,从 API 返回时转换为 UTF8。类型名大写时禁用 null 值。在 API 回调函数或结构体中API返回0~0xFFFF或-1的指针地址aardio将转换为指针而不是字符串

USTRING 32位 string pointer
结构体 struct 32位 table struct

可以在API参数中使用空表 {} 表示C/C++中的null结构体指针

注意在API参数中,struct被转换为指针按引用传递,如果是按值传递的结构体,需要展开所有成员为普通参数.

联合 union 32位 table union

u = {
 union value = {
 char c=8;
 short s=123;
 }
}

void void 标识函数返回值为空

声明的数据类型必须保持绝对正确,使用错误的类型会导致内存读写错误并导致程序崩溃。 使用api函数大部分的导致崩溃的错误原因在于数据类型定义错误。

API数组(API Array)

静态数组必须写在一个结构体里面。

array = {
byte buffer[256]={1;2;3}
}

必须在中括号内指定一个有效的数值表示数组长度.
如果不指定任何数值,则表示一个弹性数组(变长数组),aardio 将会在运行时检测获取实际的数组长度,弹性数组长度不能为0.

array = {
byte buffer[]
}

array.buffer={1;2;3} //根据运行时的数组长度确定静态类型数组长度
CTYPE 示例 备注
无符号字节 BYTE []


array = { BYTE str[2] = {'A'#,'B'#} }

或者
array = { BYTE str[2] = 
"AB" }

或者
array = { BYTE str[2] = {0x41;0x42} }

也可以不指定数组长度,
aardio根据运行时值自动获取数组长度,
例如:

array = { BYTE str[] = 
"AB" }

定长 string,
或者 table 数组(元素是数值或null值)

如果将字段指定为 null 或字符串,则 aardio 始终将该字段处理为字符串对象。要特别注意这是定长字符串,结构体写入内存时总是会填充至指定长度(不足长度补'\0')。 自内存读取为字符串时返回二进制字符串,可以包含'\0',不以'\0'为结束符,不会丢失尾部 '\0'。如果需要转换不含'\0'的文本字符串,请调用 string.str() 函数。

如果指定为 table 字节数组,则 aardio 始终将该字段处理为数组,可以调用 string.pack 函数将数组转换为字符串.

也可以使用 buffer 对象作为参数
字节 byte []
无符号短整型 WORD [] array = {
WORD arr[2] = {'A'#,'B'#}
}
如果值是一个 table 数组则 aardio 总是将该字段处理为数组,数组元素必须是数值或 null 值,

如果值为空,或者值是一个字符串,aardio 则总是将该字段作为一个 UTF16 编码的 Unicode 文本字符串处理, 在调用 API、以及作为输出结构体参数时 aardio 会自动做 UTF16 到 UTF8 的双向自动转换( 读取或获取 aardio 字符串是 UTF-8, 写入内存或传入静态 API 则自动转为 UTF-16 编码字符串), 此字符串总是会处理为文本字符串,(字符串内容不含'\u0000',遇 '\u0000' 结束)
短整型 word []
无符号整型 INT [] array = {
INT arr[2] = {'A'#,'B'#}
}
数组元素必须是数值或null值
整型 int []
无符号长整型 LONG64 [] array = {
LONG arr[2] = {'A'#,'B'#}
}
数组元素必须是数值或null值
长整型 long64 []
无符号指针地址 ADDR []  
指针地址 addr []  
浮点数 float [] 数组元素必须是数值或null值
双精度浮点数 double [] 数组元素必须是数值或null值
布尔值 bool [] 数组元素必须是布尔值或null值
指针 pointer []
POINTER []
数组元素必须是pointer或null值
字符串 string []
STRING []
数组元素必须是string或null值
结构体 struct [] 至少要声明第一个数组元素

如果是一个struct组成的数组,则至少显示的声明数组中的第一个元素为有效结构体, 其他已声明长度的数组赋值可指定任意个数的元素,也可以不指定值。未声明长度的数组必须赋值为非空数组以获取运行时数组长度。 数组长度不能为0。

输出参数(Out Parameters )

在aardio中,如果在数据类型以后添加&符号,表示这个参数的值允许被外部函数修改并且会返回修改后的值
如果一个函数包含输出参数,那么传址参数会按原来的先后顺序附加在返回值后面返回。

aardio中函数是纯函数,函数数据只有唯一的入口(参数),也只有唯一的出口(参数),所以被修改的输出参数必须显示的从返回值输出。

apifunc = dllfile.api( "apifunc", " int ( int hwnd, string lpText,string &lpCaption ,INT uType )" )

result, /*输出参数追加在返回值后面*/lpCaption = apifunc(hwnd,lpText, lpCaption,uType);
输出参数 说明
数据类型 aardio类型 C语言类型
无符号字节 BYTE & unsigned char *
字节 byte & char *
无符号短整型 WORD & unsigned short *
短整型 word & short *
无符号整型 INT & unsigned int *
整型 int & int *
无符号长整型 LONG64 & unsigned long long * 该参数支持普通数值,以及math.size64()创建的长整数
长整型 long64 & long long *
无符号指针地址 ADDR & void**
指针地址 addr & void**
浮点数 float & float *
双精度浮点数 double & double *
布尔值 bool & bool *
int *
 
指针 pointer &
POINTER &
void ** 二级指针。
字符串 string &
STRING &
char *

string &类型的参数参数可以使用 buffer 对象(这时候对应的输出参数返回值为buffer类型,而非字符串)。

如果参数是一个字符串,这时候 aardio 会创建一个等长的临时的 buffer 并拷贝字符串到该内存,并将内存指针发送给API函数,在调用结束后增加相应的返回值返回新的字符串。

如果参数是一个指定 buffer 长度的数值(以字节为单位),aardio初始化 buffer 所有字节值为0,并且在 buffer 尾部增加2个字节并写入'\u0000'。使用参数0表示传递给此类型参数一个null指针(而不是使用null空参数)

这种API类型,也可以在 aardio 中声明为pointer指针类型,然后用 raw.buffer() 函数创建一个可以让API写入数据的 buffer 传过去。

str & char * 同上,但以'\0'为终结符返回文本字符串。如果参数使用 buffer 对象,这时候对应的输出参数返回值为字符串,而非buffer类型
宽字符串 ustring &
USTRING &
wchar_t *

可以使用 buffer 对象(这时候对应的输出参数返回值为字符串,而非buffer类型)。

如果参数是一个字符串(aardio会自动转换为UTF16编码再取长度),这时候aardio会创建一个等长的临时的 buffer 并拷贝字符串到该内存,并将内存指针发送给API函数,在调用结束后增加相应的返回值返回新的字符串。

如果参数是一个指定 buffer 长度的数值(以字符为单位,一个字符占2个字节),aardio初始化 buffer 所有字节值为0,并且在 buffer 尾部增加2个字节并写入'\u0000'。使用参数0表示传递给 buffer 一个null指针(而不是使用null空参数)

也可以 buffer 对象作为参数,如果使用 buffer,输入值不转换编码,但返回值转换为UTF8编码,并转换为字符串类型作为返回值(不改变输入参数的类型)

结构体 struct & void* 结构体按引用传递,具有副作用的,即使不接收对应的返回值,结构体仍然可被API函数修改值。

可以在API参数中使用空结构体 {} 表示C/C++中的null结构体指针

请参考:table参数的副作用

对于数值类型、poiner类型不能使用输出参数来代替输入参数( 请参考: 重载API函数 )。

如果API形参定义为int,那么你传递实参数值123时,API函数接收到的是123这个数值。
如果API形参定义为int &,那么你同样传递参数值123时,API收到的是指向123的地址,是另外一个数值。
对于pointer类型、pointe &引用类型,会有同样的问题。

而对于string类型、struct类型,你在形参中加不加&,API接收到的是相同的指针地址。唯一的区别是引用参数会返回修改后的值。字符串中的引用参数并不象其他参数类型那样 - 给API提供的是变量的地址,也就是你不能将string &理解为C中的 const char ** 这样的二级指针用来接收一个字符串(const char *)的地址。如果需要接收这样的地址,应当改用一个结构体来获取。

在API函数中使用字符串

aardio中的string数据类型是传址的,多个相同内容的字符串变量内部指向相同的内存数据。
而字符串内存数据是只读的,修改字符串总是会导致aardio创建新的字符串,而不是改变原字符串的内存数据。

对于外部API函数:
如果一个字符串有可能被外部函数所改变,并且需要返回修改后的新字符串,
那么在API函数参数中应将其声明为string&以通知aardio字符串的内存可能被修改,而在结构体中应声明为char[]数组。在结构体中的char[]是一个字节数组,仍然可以使用字符串进行赋值。例如:

class struct{
byte b[3] = {'a'#;'b'#;'c'#};
byte b2[3] = "abc";//char数组仍然可以使用字符串进行赋值
}

// struct.b , struct.b2的内容是相同的

在API中字符串实际上是一个内存指针,因此API函数中的字符串参数同样可以声明为指针类型。
使用 raw.buffer 创建的 buffer 对象 - 在API函数中该类型等价于一个C语言中的char *指针,该指针指向可修改的内存,在API函数返回以后,可以再用raw.tostring()将指针转换为普通字符串。另外在新版aardio中,多数字符串函数可以直接支持buffer数型的字节数组(作为字符串直接使用)

注意:buffer 对象用于普通函数输入参数,或普通结构体成员指针。
对于输出指针地址的参数(而不是在指针指向的地址修改数据)、不需要也不应传入 buffer 对象。

要注意在API中声明为string &,或者在结构体中声明为BYTE[]或者UTF16的WORD[],或者 buffer 对象,这几种不同形式的本质都相同的,也就是分配一块内存让API可以写入数据。

而在结构体中声明为string 类型,作为输出参数使用时,这时候接收的就是一个字符串的地址,例如 {string pstr}, 在WINAPI中有一些这种字符串字段在某些情况下会赋值为一个小于0xFFFF的原子值、ID等数值,aardio对这种情况做了分别处理,如果API返回的是一个字符串指针则获取字符串,对于小于0xFFFF或者为-1的值, aardio会将其转换为指针类型。

在API函数中使用指针

API函数会严格的检测数据类型,不允许用数值作为API指针实参(API pointer类型)。

pointer输出参数必须使用pointer类型的实参、或null值。
pointer输入参数中以使用string、pointer类型的实参、也可以使用 buffer 对象。
如是对象声明了_topointer元属性,并返回一个pointer指针或数值,则可以作为指针实参使用。

string的指针是常量指针,外部API函数不应当修改字符串指针指向的内存。
而raw.buffer分配的内存是可修改的,当使用raw.buffer分配的指针作为参数时,该参数在API函数声明中不应当声明为“pointer&”类型。

对于API的输入参数,poinetr类型与string类型的实参可以相互替代,也可以使用null值。
如果使用大写的STRINGA或POINTER类型,则不能在实参中使用null值。

转换数据类型

raw.convert(from[,tostruct])

将from参数转换为tostruct类型。并返回tostruct.
from参数可以是一个指针、或结构体、或普通字符串。

下面是使用raw.convert编写的一个数值类型转换小程序。

import console; 

namespace raw{
    
    cast = 
function( ctype,v ) {
        
var s = { v = v;_struct= ctype + " v"}
        
return convert( s ,s).v; 
    }   
}

//将-1.35转换为无符号数
var v = raw.cast( "INT" , -1.35 );

console.log(
"您输入的是" ,v );
console.pause(
true);

将API中表示字符串的pointer类型指针转换为aardio字符串,请看示例(注意红色部分代码);

//下面是win库中的部分代码
::Rpcrt4 := ..raw.loadDll("Rpcrt4.dll");
::UuidCreate := Rpcrt4.api( "UuidCreate","int(struct &)");
::UuidToString := Rpcrt4.api( "UuidToString","int(type ,ptr &)");
::RpcStringFree := Rpcrt4.api("RpcStringFree","int(p)");

::GUID := class{
	INT Data1;
	WORD Data2;
	WORD Data3;
	BYTE Data4[8]="";
	@{_tostring = function() begin //重载_tostring元方法
		var re, lpsz=::UuidToString(owner,null);
		var str = ..raw.tostring( lpsz );
		::RpcStringFree(lpsz); //释放UuidToString分配的字符串内存
		return str;
	end }
} 

guid = function() begin
	var guid = GUID(); 
	UuidCreate(guid); 
	return guid;
end;

import win;
win.msgbox( guid() )