注:以下内容仅为本人当前理解,不保证正确,可能随时更新。

欢迎各位aardio爱好者交流,共同研究aardio,知对改错,共同进步。

愿aardio越来越强大。

API调用解析

1、静态数据类型参数

1、静态数据类型(number、pointer等)在API调用时,不管声明或不声明,都是传值,只能作为输入参数。

2、如果要作为“输出参数”传递指针(传址),需要使用结构体代替。

3、使用table定义结构体struct,在结构体中定义静态类型。

4、struct中所有成员应当赋于初始值(待验证,看下面的小测试)。

如:

数值的指针: {int abc} 或  {int abc=123}。

数值数组的指针:{int data[4]} 。

字节数组的指针:{byte data[4]} 或 raw.buffer()

// 通常,我们使用class来定义API函数中需要用到的结构体:class POINT{
int x = 0; // 结构体成员必须设定初始值
int y = 0;
}// 创建一个结构体对象
pt = POINT();// 每个结构体会创建一个_struct只读字段记录类型定义,语义如下:
pt = { _struct = "int x;int y" }

小测试: 关于struct中所有成员应当赋于初始值

测试结论:即便没有给struct的成员赋予初始值,也一切OK。

我觉得像 int 这种定长的数据类型,没有初始值也没关系。

如果是数组,应该要提前确定结构长度。

易语言dll代码:

aardio调用代码:

没有赋予struct初始值,int类型的值默认为0

传递给api后,也可以正常修改变量值。

2、动态数据类型参数

1、动态类型(string、buffer)本身就是指针,所以不能再传址。

也就是说,从表面上来看,他们只能作为输入参数,不能作为输出参数。

然而,虽然如此,但因其本身就是指针,所以其值在api中也可以被修改。

2、struct在声明API时,可以作为输入或输出参数;不声明API时,强制作为输出参数。

作为输入参数时,传递的是数值(传值),不作为API返回值返回。

作为输出参数时,传递的是指针(传址),并作为API返回值返回。

3、不声明直接调用API:

一、参数:

1、不声明直接调用API,可以根据需要灵活改变参数类型,也更方便、节省资源,建议使用。

2、调用约定在加载DLL的参数中指定,支持cdecl不定个数参数。

3、null参数不可省略。

4、数值参数一律处理为32位int整型。(小于32位的整数、枚举类型、8位或32位bool值都跟int 32位数值兼容)

5、64位整数可以用math.size64对象表示。(或者用两个数值参数表示一个64位整数值参数,其中第一个参数表示低32位数值,第二个参数表示高32位数值)

6、数值类型的指针(输出参数)一律使用结构体表示。

7、数组指针,使用结构体指针替代,如 {int data[4]} 。

8、结构体,一律处理为输出参数,并在aardio返回值中返回。

9、除结构体外的其他类型,只能作为输入参数。

0、注意在aardio中,任何结构体在API调用中传递的都是结构体指针(传址)(待进一步验证,因为在下面的简单数值型struct测试代码中,进行不传址测试时,api中并未能成功改变真实变量的值。

这里说的意思很有可能是:任何结构体在API直接调用中传递的都是结构体指针)。

二、返回值:

1、直接调用API的,返回值默认为int类型。

2、可以使用 【API尾标】 改变返回值为其他类型。

3、未声明的API函数,只是一个普通的aardio函数对象,不能作为函数指针参数传给API参数(声明后的API函数对象是可以的)。

4、结构体总是作为输出参数,附加在api返回值后面返回。

三、API尾标:

1、当不声明直接调用API时,API函数名尾部如果不是大写字符,则可以使用一个大写的特定字符(这就是API尾标)修改默认的API调用规则。

2、在API函数名后添加尾标,不会影响到查找API函数的结果,无论真实的API带不带指定的尾标,aardio都能找到真实的函数。

3、所有可用的 [API尾标] 及代表的规则如下:

  • dll.ApiNameW()    切换到Unicode版本,字符串UTF8-UTF16双向转换
  • dll.ApiNameA()     切换到ANSI版本,字符串不作任何转换
  • dll.ApiNameL()     返回值为64位LONG类型
  • dll.ApiNameP()     返回值为pointer指针类型
  • dll.ApiNameD()     返回值为double浮点数
  • dll.ApiNameF()     返回值为float浮点数
  • dll.ApiNameB()     返回值为byte类型(C++中的8位bool)

4、对于已存在W尾标的API函数,可在调用时使用其他尾标,并且仍然可以正确检测并切换到API函数的Unicode版本。

四、使用字符串

1、字符串、buffer类型字节数组,一般作为字符串指针使用。

2、如果API需要向字符串指向的内存中写入数据,那么必须使用raw.buffer()函数创建定长的字节数组。

3、普通的aardio字符串指向的内存是禁止写入的(这种说法,仅对aardio常规操作而言,看看下面的api测试代码,可以成功修改字符串的值),aardio中修改普通字符串会返回新的字符串对象,而不是在原内存上修改数据。

4、对于Ansi版本的API,string字符串直接输入原始的数据(文本默认是UTF8编码)。

对于Unicode版本的API,string字符串会被强制转换为Unicode(UTF16)。

buffer类型的参数,总是以二进制方式使用原始数据与API交互(不会做文本编码转换)。

五、Ansi和Unicode

1、可以在 raw.loadDll ( ) 加载DLL时,可以在调用约定中添加 ",unicode",让它默认使用Unicode API。

2、可以在函数名后添加尾标"A"或“W”,声明Ansi或Unicode版本的API。

如果同时指定尾标版本 和 loadDll约定版本,则以尾标版本为准。

aardio在找不到该版本的API函数时,会移除尾标,但依然会根据指定的尾标版本,进行字符串编码。

4、一些API在接收字符串、字节数组等参数时,通常下一个参数需要指定内存长度, aardio中用#操作符取字符串、缓冲区的长度时,返回的都是字节长度,一些API可能需要你传入字符个数,如果是Unicode版本的API一个字符为两个字节,对于一个UTF8字符串应当事用string.len()函数得到真正的字符长度, 而Unicode字符串则用#取到字节长度后乘以2即可。

简单数值型struct测试

易语言编写的dll代码:

aardio调用代码:

1、定义为输入参数时【不传址】:

// 结构体未定义为输出参数:import console; testdll = raw.loadDll("C:\Users\Administrator\Desktop\test.dll")
test=testdll.api("test","int(struct)","stdcall")var i = {int abc=123}var a,b = test (i)
console.dump("a:",a)
console.dump("b:",b)
console.dump("i:",i)console.pause(true);

执行结果

执行结果:传递进去数值正常;修改结构体数据失败;返回值正常;结构体未作为返回值返回;

2、定义为输出参数时【传址】:

import console; testdll = raw.loadDll("C:\Users\Administrator\Desktop\test.dll")
test=testdll.api("test","int(struct&)","stdcall")var i = {int abc=123}var a,b = test (i)
console.dump("a:",a)
console.dump("b:",b)
console.dump("i:",i)console.pause(true);

执行结果:

执行结果:传入api数值正常;api中修改结构体变量数据正常;返回值正常;结构体作为返回值返回成功;

3、直接调用API函数时:

import console; testdll = raw.loadDll("C:\Users\Administrator\Desktop\test.dll")var i = {int abc=123}
var a,b = testdll.test(i)console.dump("a:",a)
console.dump("b:",b)
console.dump("i:",i)console.pause(true);

结果同上。因为struct被强制作为输出参数进行了传址。

简单string字符串传址测试

1、易语言编写的dll代码:

2、aardio调用示例(传址):

import console; testdll = raw.loadDll("C:\Users\Administrator\Desktop\test.dll")
test=testdll.api("test","string(string&)","stdcall")var i =..string.fromto("张三丰")var a,b = test(i)console.dump("a:",a)
console.dump("b:",b)
console.dump("i:",i)console.pause(true);

3、执行结果:

执行结果:传入api文本正常;api中修改文本变量数据失败;返回值正常;传出参数修改后的数据作为返回值返回。

4、aardio调用示例(不传址):

执行结果:传入api文本正常;api中修改文本变量数据成功;返回值正常;无附加返回值。

我的大胆猜想

1、可能是出于保护内存数据安全或其他目的,aardio对除结构体以外的数据类型,都做了保护,禁止传址,防止其数据被非法篡改。

2、当这些数据类型被强制传址时,aardio会申请与其数据一致的临时变量,传址给api执行,并获得api执行结果和被修改后的临时变量数据,返回给aardio。

3、所谓的“只读”、“不可修改”只是相对而言,因为你无法提供准确有效的获取 诸如 var i=123 这种变量的地址的有效方法,所以才感觉无法修改。

4、为了验证这个猜想,做一个lstrcpyn测试:

通过测试结果可以看出,api确实成功修改并返回了传址进去的数值型变量的值,但实际上真实变量的值却并没有发生改变。因为api改的是临时变量的值,aardio取的也是临时变量的值。

5、通过lstrcpyn测试和上面的文本传址测试,其结果初步符合上述猜想。

注:该猜想已于2021-10-12版本 v33.18.3 更新 raw.argsPointer 库时提供的例程:

范例程序\ aardio 语言\ 语言扩展\ 结构体二级指针

中得到了证实,作者做了如下注解:

aardio 结构体作为调用 API 的参数时会分配一块临时的内存,
并将 aardio 结构体的值复制过去,然后将该内存的指针作为调用 API 的参数,
在调用 API 结束后再将内存中新的值同步到 aardio 结构体,然后立即释放临时内存,
释放临时内存是立即操作,而非等待垃圾回收器操作。

文本传址与不传址的深入测试:

同样用易语言写一个dll,功能:

1、显示传递进来的文本变量的值

2、显示传递进来的文本变量的地址

3、修改传递进来的文本变量的内存数据

4、返回一个文本值

1、用两个内容【一样】的string变量进行【不传址】测试。

第一步:传递第1个变量 t1,api中提示:内容为“张三丰”,变量地址为 36657216

第二步:api中修改了变量 t1 的内容为“我很好”,aardio中console也同步显示,返回值为“哈哈哈”,t1为“我很好”,OK,正确无误。

第三步:传递第2个变量 t2,api中提示:内容为“我很好”,变量地址为 36657216。

那么问题来了,为什么 t2 不是 “张三丰” 呢?

那是因为aardio中,同样内容的文本变量,共享同一内存地址。

你改了一个,等于是改了所有。

这也是为什么这两个变量传递进去的“变量地址”是一样的原因。

所以,这里传给api的,就是string变量的“真实地址”。

第四步:执行完毕,毫无悬念。

结论就是:t1 、t2 内容一样,地址一样,api改一个变量的内存数据,等于全改了。“不传址”的情况下,传给api的反而是真实地址。

2、用两个内容【不一样】的string变量进行【不传址】测试。

第一步:传递第1个变量 t1,api中提示:内容为“张三丰”,变量地址为 27875392

第二步:api中修改了变量 t1 的内容为“我很好”,aardio中console也同步显示,返回值为“哈哈哈”,t1为“我很好”,OK,正确无误。

第三步:传递第2个变量 t2,api中提示:内容为“张无忌”,变量地址为 27875424。

第四步:执行完毕,变量 t2 内容变为“我很好”。

执行完毕,结论就是:t1 、t2 内容不一样,地址也不一样,“不传址”的情况下,传给api的都是真实地址,都被api成功修改。

3、用两个内容【一样】的string变量进行【传址】测试。

因为string传址会被aardio作为返回值返回,也就是说api会有多个返回值,所以我们下面的测试代码中,将api的执行结果放在console.log的最后一个参数,以便于能全部显示出来。

第一步:传递第1个变量 t1,api中提示:内容为“张三丰”,变量地址为27423792。

第二步:api中没有成功修改变量 t1 的内容为“我很好”,t1 的真实内容仍然为“张三丰”。返回值为“哈哈哈”,但返回值中的 t1 为“我很好”。

第三步:传递第2个变量 t2,api中提示:内容为“张三丰”,变量地址为 27423792。

同样,t1、t2 内容一样,地址一样,因为 t1 没有被修改,所以 t2 传进去,还是 “张三丰”。

所以,这里传给api的,应该不是string变量 t1 的“真实地址”。

但 api 却成功修改并返回了修改后的“正确”结果,所以猜想这里用了一个临时变量去“欺骗”api,而非真实的 t1。

而且,两次传递的临时变量地址是一样的,但第一次传递后,明明内容被修改了,第二次传递却又恢复了。

这说明:aardio将临时变量传递给api时,是先给临时变量同步了真实变量的值。

第四步:执行完毕,结论就是:“传址”的情况下,先把真实变量的值,复制给临时变量,再把临时变量传给api。api改的是临时变量的值,返回的也是临时变量的值。

4、用两个内容【不一样】的string变量进行【传址】测试。

第一步:传递第1个变量 t1,api中提示:内容为“张三丰”,变量地址为36533296。

第二步:api中没有成功修改变量 t1 的内容为“我很好”,t1 的真实内容仍然为“张三丰”。返回值为“哈哈哈”,但返回值中的 t1 为“我很好”。

第三步:传递第2个变量 t2,api中提示:内容为“张无忌”,变量地址为36533296。OK,临时变量走一波。

第四步:执行完毕,t2 的真实内容仍然为“张无忌”,返回值为“哈哈哈”,但返回值中的 t2 为“我很好”。

结论就是:跟上面一样,临时变量走一波耶~~~

API字符串传址方法示例 —— 先声明后调用:

以 User32.GetWindowTextA 为例,演示如何传递指针(文本指针、缓冲区指针、结构体指针)给api函数调用。

import console;
import winvar gwt,t//定义传址的文本变量为 string 类型
gwt = ::User32.api("GetWindowTextA","int(addr,string,int)")// 用string类型变量t = string.repeat(255,'\x0')gwt (..win.getForeground(),t,255)console.log('定义为 string 类型,传递 string 类型,返回结果:\n'+t)// 用buffer类型变量t = raw.buffer(255)gwt (..win.getForeground(),t,255)console.log('定义为 string 类型,传递 buffer 类型,返回结果:\n'+ tostring(t))//定义传址的文本变量为 pointer 类型
gwt = ::User32.api("GetWindowTextA","int(addr,pointer,int)")// 用string类型变量t = string.repeat(255,'\x0')gwt (..win.getForeground(),t,255)console.log('定义为 pointer 类型,传递 string 类型,返回结果:\n'+ t)// 用buffer类型变量t = raw.buffer(255)gwt (..win.getForeground(),t,255)console.log('定义为 pointer 类型,传递 buffer 类型,返回结果:\n'+ tostring(t))//定义传址的文本变量为 struct& 类型
gwt = ::User32.api("GetWindowTextA","int(addr,struct&,int)")// 用struct类型变量t = {byte t[255]}gwt (..win.getForeground(),t,255)console.log('定义为 struct& 类型,传递 struct 类型,返回结果:\n'+ tostring(t.t))console.pause(true);

执行结果:

API字符串传址方法示例 —— 不声明直接调用:

     t = string.repeat(255,'\x0')User32.GetWindowTextA(..win.getForeground(),t,255)console.log('传递 string 类型,直接调用:返回结果:\n'+ t)t = raw.buffer(255)User32.GetWindowTextA(..win.getForeground(),t,255)console.log('传递 buffer 类型,直接调用:返回结果:\n'+ tostring(t))t = {byte t[255]}User32.GetWindowTextA(..win.getForeground(),t,255)console.log('传递 struct 类型,直接调用:返回结果:\n'+ tostring(t.t))

执行结果:

===============================================================

关于处理掉字符串尾部0的方法

aardio中字符串是允许包含字符0的,所以通过传址方式获取的字符串,在aardio中不会遇0终止,但是可以通过通过以下方法来截掉后面多余的 \0

t = string.repeat(255,'\x0')
gwt (..win.getForeground(),t,255)
//方法一(推荐)
t = ..raw.tostring(..raw.toPointer(t))
//方法二
t = ..string.trimright(t,'\0')

不声明直接传float数值的方法

因为上面提到过:

4、数值参数一律处理为32位int整型。(小于32位的整数、枚举类型、8位或32位bool值都跟int 32位数值兼容)

所以,如果要传递float数据类型,如何传递呢?

这里写了两个dll函数进行测试:

aardio中调用方式总结如下(测试过程就不详细解释了,直接说结果吧):

var dll=..raw.loadDll()//传值测试://数值型数据结构dll.show_byvalue(123.456)//错误(float传递时被自动处理为整数类型,值已发生改变)dll.show_byvalue(..raw.float(123.456))//正确(构造为小数类型,aardio自动识别并在调用时自动调整参数类型为float)//传址型数据结构dll.show_byvalue({float v=123.456})//错误(传的是“地址”,不是“数值”,值不一样)dll.show_byvalue(..raw.float(123.456,true))//错误(传的是“地址”,不是“数值”,值不一样)//数值型,数据类型转换,将float数值转为int数值,再进行传递dll.show_byvalue(raw.convert({ float f = 123.456 },{int i}).i)//正确(用int数值,伪造float类型的内存数据,能被dll正确解析为float数值。原理同union)//总之,就是必须将正确的“值”传递过去。//传址测试://数值型数据结构dll.show_byaddr(123.456)//错误,保护异常,退出dll.show_byaddr(..raw.float(123.456))//错误,保护异常,退出//传址型数据结构dll.show_byaddr({float v=123.456})//正确dll.show_byaddr(..raw.float(123.456,true))//正确//传址相对比较简单,其他方法不一一测试了。

===============================================================

结束语:

以上内容抛砖引玉,不保证完全正确,如果有不对之处,欢迎留言指正。

aardio - API调用分析相关推荐

  1. 腾讯文智自然语言处理介绍与情感分析API调用

    一.产品概述 文智中文语义开放平台是基于并行计算系统和分布式爬虫平台,结合独特的语义分析技术,一站式满足用户NLP.转码.抽取.全网数据抓取等中文语义分析需求的开放平台.用户能够基于平台对外提供的Op ...

  2. JAVA开发(后端):微信小程序API调用详细分析及步骤

    关键词:微信登录.统一下单(支付).统一下单通知(回调).统一下单查询.企业付款至零钱.支付查询.获取ACCESS_Token.获取小程序二维码 因为做项目涉及到微信这些接口的调用,尽管看了很多博客, ...

  3. 音乐API调用以及分析(以酷狗音乐为例)

    音乐排行榜列表: 接口地址: http://m.kugou.com/rank/list&json=true 请求方式:Get 返回数据:(这里本编使用只截取了5条数据) {"JS_C ...

  4. 分布式定时任务—xxl-job学习(四)——调度中心web页面端api调用源码分析

    分布式定时任务-xxl-job学习(四)--调度中心web页面端api调用源码分析 前言 一.controller目录下非controller类 1.1 PermissionLimit自定义注解 1. ...

  5. 如何通过API调用来分析恶意软件

    导读 在今年的上一个季度,我们发现有越来越多的恶意软件开始使用各种加密.封装和加壳等保护技术来对恶意代码进行混淆处理,以防止系统或程序对其进行识别.除此之外,这些技术甚至还会让研究人员对其的静态分析变 ...

  6. nvGRAPH API参考分析(一)

    nvGRAPH API参考分析(一) 本文通过描述nvGRAPH库函数的输入/输出参数,数据类型和错误代码来指定其行为. 返回值nvgraphStatus_t 除以下内容外,所有nvGRAPH库返回值 ...

  7. Windows恶意软件API调用特征分析

    本文讲的是Windows恶意软件API调用特征分析, 1.背景 目标: 1)找到病毒调用概率高的API 2)找到病毒调用概率不高,但是当调用频次高的时候,是病毒概率高的API. 通常对病毒使用API的 ...

  8. 【阿里云API】 阿里云API调用的若干说明

    阿里云API 为了监控我们使用的一些阿里云产品,需要些一些脚本,定时调用这些脚本来获得相关阿里云产品的信息. ■ 概述 调用阿里云API大约分成两类方法,一个是直接从HTTP协议开始,自己根据阿里云的 ...

  9. Windows内核实验004 API调用

    文章目录 完善代码 内核API调用 修复一个潜在问题 复现问题 完整代码 前面几次实验我们已经完成了一个三环的程序调用零环API的必要条件. 提升到零环权限 使fs指向KPCR 完善代码 这次我们去掉 ...

  10. 百度UNIT 机器人多轮对话技能创建以及API调用

    百度UNIT 机器人多轮对话技能创建以及API调用 基于百度UNIT2.0 版本,实现简单的多轮人机对话功能 创建机器人必备条件 确定有哪些技能 例如:查询天气.订票.讲故事等,以畅越冰激凌套餐营销话 ...

最新文章

  1. CRM+Mysql+php初学
  2. SAP ABAP MARD和MARDH计算逻辑
  3. JVM内存结构|虚拟机栈
  4. python 网页爬取数据生成文字云图
  5. python爬虫视频 下载 黑马_Python爬虫能爬视频么(python爬虫零基础视频教程)
  6. 金融综合(网课+读书笔记)
  7. WebRtc与P2P
  8. layui树形美化_Layui导航树美化 - walkwithdream的个人空间 - OSCHINA - 中文开源技术交流社区...
  9. MySQL 窗函数 流动平均数 running average
  10. HEVC函数入门(22)——变换量化
  11. java实训项目百度脑图
  12. 不改一行代码!快速迁移 Koa 应用上云
  13. 微信小程序开发:组件
  14. 二层与三层交换机之间有什么区别?
  15. swift 自制framework中加载nib
  16. 首富软件测试工资,测试你成为富豪 测试你天生是什么命
  17. 淘宝卖家掌握最新操作方法,让你的直通车飞起来!
  18. 回首2016,展望2017
  19. 数学建模多元线性回归内生性问题的蒙特卡洛模拟matlab代码
  20. 关于完全卸载2345王牌输入法-win10

热门文章

  1. Modelsim搭建具有各组件的UVM验证平台
  2. Java实现网络通信(TCP程序设计)
  3. 基于python的opencv图像处理对交通路口的红绿灯进行颜色检测(最简单的方法)
  4. DirextX 11游戏开发(1)
  5. hyper-v开启与关闭
  6. Jetseon TX2 IntelRealsense D435i Python
  7. SFDC Developer
  8. Snapper 快照管理工具
  9. 整数 (Integer)
  10. java系列 - entity,vo转换