aardio 文档

aardio 范例: 调用 C 语言生成 DLL

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 格式