# aardio 范例: 调用 C 语言生成 DLL

```aardio
// 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查看工具 」把修饰名复制出来使用就可以了。
*/
```