raw库 - 声明外部API函数

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

加载dll模块

dll := raw.loadDll( path | strMemoryDll,shareName,callingConvention);

raw.loadDll从路径path或内存数据strMemoryDll中加载dll模块,
然后我们可以使用 dll.api 函数声明我们需要用到的api函数。

如果自内存加载DLL,可选使用 shareName 参数指定多线程共享名,
指定 shareName 后,多个线程可共享同一内存DLL模块,指定 shareName 参数后可省略参数@1 —— 此时仅查找已加载的DLL。

callingConvention 参数指定默认调用约定,可省略,不指定时默认值为 "stdcall"

参考:API数据类型

//使用内存DLL的示例

dllmodule := raw.loadDll($"d:\\hardware.dll");
testapi := dllmodule.api("getbios", "s()" );

io.open();
ioprint( testapi( ));


aardio 已默认加载以下 DLL 模块

::User32, ::Kernel32, ::Ntdll, ::Shell32

使用 aardio『工具 / 转换工具 / API 转换』工具可自动转换 C/C++ 的 API 函数声明为 aardio 格式,并自动识别 ::User32, ::Kernel32, ::Ntdll, ::Shell32 这些 DLL 模块提供的函数。

注意:

如果 DLL 厂商仅提供一个版本的 DLL,一般是 32 位的 DLL,在 aardio 可以直接加载,
aardio 程序也是 32 位的,这是因为 64 位程序只能运行在 64 位平台,而 32 位程序兼容所有平台。
简单务实,不贪心追求完美,不搞复杂是 aardio 的基本原则。

如果 DLL 厂商提供了 64 位、32 位两个版本的 DLL,这时候你需要选择 32 位版本的DLL才能加载,
如果 对方只提供 64 位的 DLL (比较罕见,从未见过),只要包装为进程调用,aardio 仍然可以支持。

aardio 有一个极大的优势就是多进程、多线程开发都非常方便。
通过多进程交互,aardio 就可以非常方便地调用 64 位的组件。

一般来说,即使是单个 32 位进程的内存上限都足够用了。
以多进程实现的网页浏览器为例,你不可能打开一个网页就占用 10 GB 的内存。

实际上多进程交互的开发模式已经非常普及,典型的例如浏览器就是多进程模式。
在 aardio 中也有很多这样的应用或实例,例如:

标准库中的 web.view 就可以非常方便地调用 64 位的 WebView2 组件。
扩展库中的 process.ruffle 就是通过 ruffle.exe 实现了在窗口上创建 Flash 控件播放动画。
而通过 process.python 扩展库更是可以兼容 64位/32位的 Python。
用于编写网站的 fastcgi 同样是运行于多进程模式。

aardio 中通过 process,process.popen,这些库可以方便地创建多进程以及实现进程管道读写。
也可以使用 process.command,process.rpc, web.rpc, web.socket 等等方便地实现进程交互。
并且 COM 接口也支持开发或调用进程外组件。

声明API函数

语法:

dll = raw.loadDll( path | strMemoryDll );

dll.api( 函数名|函数序号,函数原型,调用约定="stdcall",this指针=null) 

这里的dll指raw.loadDll加载的dll模块对象.
第一个参数是加载的DLL模块中定义的API函数名字,或导出函数序号.
调用约定,this指针都是可选参数可以省略,this指针可用于thiscall调用约定,也可用于其他调用约定中指定默认的第一个参数。

函数原型是以一个字符串表示的API函数声明,定义该函数的参数类型、返回值类型等,
这里的类型指静态数据类型. 例如:

"int(int a,int b)"

定义了一个函数原型,有一个int类型的参数a,一个int类型的参数b,返回值为int类型.

请参考: 静态数据类型

调用约定可以不指定,默认值为"stdcall",可选值为"cdecl","thiscall","fastcall","regparm(n)"等, 可以在调用约定后面紧跟一个逗号以及目标DLL的开发平台,可选值为",borland" ",microsoft" ,microsoft是默认选项可以省略,使用delphi编写的DLL时,"stdcall","cdecl"等调用约定不需要指定开发平台。

fastcall,regparm(n)调用约定( 也就是寄存器传参方式 )详解:

"fastcall"
"fastcall,microsoft"
以上两种写法作用相同,前2个小于等于32位的数值参数使用edx,ecx寄存器, 如果还有参数则自右向左依次压栈; 由被调者负责维护堆栈平衡(清除压入的参数);如果函数有返回值则把返回值存放在eax寄存器中.

"fastcall,borland"
同上,前3个小于等于32位的数值参数使用eax,edx,ecx寄存器,

"fastcall,regparm(n)"
"stdcall,regparm(n)"
以上两种写法作用相同,n可以为3,2,1,前n个参数使用eax,edx,ecx寄存器,如果为1则仅使用eax,为2仅使用eax,edx

"regparm(n)"
"cdecl,regparm(n)"
以上两种写法作用相同,n可以为3,2,1,前n个参数使用eax,edx,ecx寄存器,如果为1则仅使用eax,为2仅使用eax,edx
如果还有参数则自右向左依次压栈; 由调者负责维护堆栈平衡(清除压入的参数);如果函数有返回值则把返回值存放在eax寄存器中.

"thiscall"
C++对象调用约定,可在声明API时增加一个参数指定this指针,如果不在声明时指定,也可以在调用时用首参数传递this指针


参考:API数据类型



//声明API调用示例
//=====================================================
//导入DLL

User32 := raw.loadDll("User32.dll");

//声明API函数 //声明函数原形的方式遵循C语法
messageBox := User32.api( "MessageBoxW", " void ( int hWnd, ustring lpText,ustring lpCaption ,INT uType )","stdcall") //最后一个参数可以省略

//使用API函数
messageBox( 0, "这是一个测试对话框", "对话框标题", 0x00001000 )


三、声明内部函数

raw.main = raw.loadDll();
func = raw.main.api( 内部指针地址,函数原型 ) 

如果调用raw.loadDll()时未使用任何参数,则这里的第一个参数应当是一个内部函数指针.

一个有趣的示例(危险操作,请勿模仿):

import win;
func = function(str){
	win.msgbox( "非法操作:" + str)
}

//转换为静态函数指针
func_c = raw.tostdcall(func,"void(str)" )
 

// 对内核对象使用topointer,tonumber等函数是无效的 
funcAddr = raw.toPointer(func_c)  

//声明一个特殊的API,调用内部函数指针
func_api = raw.main.api( funcAddr ,"void(str)" )

//看来是一件很无聊的事,转来转去,我们只是调用aardio函数而已.
func_api("hello")