gcc 编译 | gcc make 编译 | 不声明直接调用 API | 声明原生 API
// aardio 调用 C 语言生成 DLL
// gcc 编译: https://www.aardio.com/zh-cn/doc/example/Languages/GCC/c.html
// gcc make 编译: https://www.aardio.com/zh-cn/doc/example/Languages/GCC/makefile.html
import tcc;
var c = tcc();
//必须使用最新版 tcc 扩展库才能支持 UTF8,UTF16 字符串
c.code = /**
#include <windows.h>
#include <stdio.h>
/*
入口函数,该函数可以有也可以没有。
入口函数会自动加锁以保证线性调用,要避免在DllMain内调用下列函数:
1、调用LoadLibrary或其他可能加载DLL的API函数( CreateProcess等 )
2、可能再次触发DllMain的函数,例如 CreateThread,ExitThread
3、GetModuleFileName, GetModuleHandle 等其他可能触发系统锁的API函数
总之在DllMain最好不要调用API函数.
*/
int __stdcall DllMain(void * hinstDLL, unsigned long fdwReason, void * lpvReserved) {
if (fdwReason == 1/*DLL_PROCESS_ATTACH*/ ){
}
return 1;
}
//__declspec(dllexport) 声明导出函数
__declspec(dllexport) int Msgbox( HWND hwnd )
{
//定义一个结构体
struct { const char * utf8message;int id; } argument = {
.utf8message = "测试消息来自C语言",
.id = GetCurrentThreadId()
};
/*
_WM_THREAD_CALLBACK 使所有回调安全的转发到UI线程。
_WM_THREAD_CALLBACK 可以跨线程跨语言并且不需要创建回调线程,适用任何普通winform对象。
与其他回调方案的比较:
raw.tocdecl raw.tostdcall 不能跨线程使用,
thread.tocdecl,thread.tostdcall 需要创建回调线程。
thread.command 则只能在aardio代码中使用,需要将窗体转换为thread.command对象。
*/
SendMessage(
hwnd,0xACCE/*_WM_THREAD_CALLBACK*/,
(WPARAM )"onMessageChange( { string utf8message;int id } )", //要调用的窗体函数名( 结构体原型声明 ); 结构体原型声明应使用aardio语法
(LPARAM )&argument //将前面定义的结构体作为调用参数
);
;
return argument.id;
}
typedef struct {
int x;
int y;
} Point;
__declspec(dllexport) int Test (char * buf,char a,int b, unsigned long long c,Point * ppt,Point pt,double * pd)
{
sprintf(buf,"C语言接收到参数 a: %d b:%d c: %llu ppt->x: %d ppt->y:%d pt.x: %d pt.y:%d pd:%g\n",a,b,c,ppt->x,ppt->y,pt.x,pt.y,*pd);
}
**/
/*
加载需要用到的动态库,或静态库
在"~\lib\tcc\.res\lib" 目录下查找 "动态库名.def" "静态库名.a"
也可以使用 vm.addLibPath() 函数添加搜索库的目录,
其实下面的DLL已经默认加载,这里仅用于演示。
*/
c.addLib(
"user32",
"kernel32",
"gdi32"
)
c.output( "/bin.dll" ) //编译C源码,生成DLL
c.close(); //收工
//创建一个窗体以处理_WM_THREAD_CALLBACK线程回调命令。
import win.ui;
var winform = win.form({})
winform.messageOnly(); //窗体仅用于处理消息
winform.onMessageChange = function(param){
winform.msgbox("调用:" + param.utf8message )
win.quitMessage()
//如果修改并返回参数传入的结构体,则修改C语言中的结构体
param.id = 123456;
return param;
}
//加载生成的DLL,下面的"cdecl"指定默认调用约定,免声明调用API时使用此约定,如不指定则默认为 "stdcall"
var dll = raw.loadDll( "/bin.dll",,"cdecl" );
/*
aardio 中的字符串是只读的不应修改其内存,
而 raw.buffer 可分配内存用于创建可修改的缓冲区( buffer ),
而用 raw.buffer 创建的 buffer 可通用于几乎所有字符串函数。
*/
var buf = raw.buffer(100);
/*
不声明直接调用 API: https://www.aardio.com/zh-cn/doc/library-guide/builtin/raw/directCall.html
使用调用 raw.loadDll 指定的 "cdecl" 调用约定
相对于声明API,免声明调用 API 是更优的选择,频繁调用的免声明 API 会被缓存而非重复创建,
长时间不使用的免声明 API 将会被自动释放。
*/
var ret,ppt,pd = dll.Test(
buf, //相当于C语言中的 char buf[100] 或者 char * buf
1,//小于32位的整型数值可以直接传递,自动兼容
2,//32位整型数值可以直接传递,自动兼容
math.size64(3), //无符号64位整数,可以传 math.size64 对象
{int x = 4;int y = 5}, //aardio 在API参数中传结构体,总是传结构体指针,
6,7, //直接在参数中用结构体传值极其罕见,类似这种字段为32位长的结构体字段可以直接展开为多个参数
{double v = 8.1} //对等C中的 double * 这种指针,在 aardio 中转换为同类型的结构体指针即可
);
/*
如果C函数的参数使用了 double,float 等浮点数值参数(传值,而不是使用指针传址),
则必须先声明再调用,不声明直接调用无法支持这类参数。
*/
win.msgbox(buf) //在 aardio 中可以将缓冲区( buffer )替代几乎所有字符串参数。
/*
声明原生 API: https://www.aardio.com/zh-cn/doc/library-guide/builtin/raw/api.html
第二个参数指定cdecl调用约定(如果使用 raw.loadDll指定的默认调用约定,那么下面可以不指定 )
不建议在函数内部声明API,这会重复创建不必要的 API 对象(虽然也会释放,但 aardio 并非立即释放不使用的对象。
*/
Msgbox = dll.api( "Msgbox","int(addr str)", "cdecl" );
//可使用:aardio 『工具 > 转换工具 > API 转换』自动转为 aardio 声明。
var ret = Msgbox( winform.hwnd );
win.loopMessage()
/*
附:编写DLL避免导出函数名乱码(出现修饰名)的几种方法:
--------------------------------------------------------------------
1、C语言的导出函数使用默认的cdecl调用约定,不要用stdcall调用约定,就不会有修饰名,示例:
__declspec(dllexport) int Add( int a,int b )
{
return a + b;
}
2、C++编写DLL在导出函数在前面加上extern "C" 使用cdecl导出就不会有修饰名,例如:
extern "C" __declspec(dllexport) int Add(int a,int b)
{
return a + b;
}
3、如果上面的方法都不用,就只能添加def文件来避免这个问题了。
如果你调用的是别人编写的DLL出现修饰名了怎么办呢?!
--------------------------------------------------------------------
1、这样的DLL不用可能并不是坏事。
2、用请运行「aardio工具 » 探测器 » DLL查看工具 」把修饰名复制出来使用就可以了。
*/
Markdown 格式