A common cross-platform GUI library, the core uses Lazarus LCL.


Keywords
c, cpp, delphi, desktop-widget, go, golang, golang-library, govcl, gui, lcl, liblcl, rust, rust-library, vcl
License
Other
Install
go get github.com/ying32/liblcl

Documentation


新功能请查看dev分支。

liblcl

一个通用的跨平台GUI库,核心使用Lazarus LCL。


编译指南



语言绑定工具生成器:genBind,目前已经生成了c/c++、rust、nim的,详细参考这几种语言的绑定模板文件。


其他

所有导出的函数都为标准的c方式。 在Windows上采用__stdcall约定,其它平台采用__cdecl约定。

注:liblcl导出的某些API上用了些看起来怪异的方式,原本liblcl是为govcl所写,所以因为go天生的一些的原因,造成本自己都看着有点不太爽,但又很无奈。


字符编码

在所有平台上都默认使用utf-8编码。


默认的实例类

无需手动调用创建和释放。

// 定义
TApplication Application; // 应用程序
TScreen Screen;           // 屏幕
TMouse  Mouse;            // 鼠标
TClipboard  Clipboard;    // 剪切板
TPrinter Printer;         // 打印机

// 获取实例类指针
Application = Application_Instance();
Screen = Screen_Instance();
Mouse = Mouse_Instance();              
Clipboard = Clipboard_Instance();      
Printer = Printer_Instance();          

事件回调

事件回调分为3种类型。

获取参数数组中每个成员

// x86: sizeof(uintptr_t) = 4
// x64: sizeof(uintptr_t) = 8

// 从指定索引和地址获取事件中的参数
#define getParamOf(index, ptr) \
 (*((uintptr_t*)((uintptr_t)ptr + (uintptr_t)index*sizeof(uintptr_t))))
  • 基本事件回调
 
typedef void(*ESYSCALL0)();  
typedef void(*ESYSCALL1)(intptr_t);  
typedef void(*ESYSCALL2)(intptr_t, uintptr_t);  
typedef void(*ESYSCALL3)(intptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL4)(intptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL5)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL6)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL7)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL8)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL9)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL10)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL11)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  
typedef void(*ESYSCALL12)(intptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);  


// 回调函数原型
// f:        通过SetOnXXX传入的Id或者函数指针
// args:     参数组数指针,通过getParamOf来获取每个成员
// argcount: 参数数组长度
// 事件回调
static void* LCLAPI doEventCallbackProc(void* f, void* args, long argCount) {
 
	// 获取参数的宏  
	#define _A_(index) \
	   getParamOf(index, args)

    switch (argCount) {
    case 0:  ((ESYSCALL0) (f))(); break;
    case 1:  ((ESYSCALL1) (f))(_A_(0)); break;
    case 2:  ((ESYSCALL2) (f))(_A_(0), _A_(1)); break;
    case 3:  ((ESYSCALL3) (f))(_A_(0), _A_(1), _A_(2)); break;
    case 4:  ((ESYSCALL4) (f))(_A_(0), _A_(1), _A_(2), _A_(2)); break;
    case 5:  ((ESYSCALL5) (f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4)); break;
    case 6:  ((ESYSCALL6) (f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5)); break;
    case 7:  ((ESYSCALL7) (f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5), _A_(6)); break;
    case 8:  ((ESYSCALL8) (f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5), _A_(6), _A_(7)); break;
    case 9:  ((ESYSCALL9) (f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5), _A_(6), _A_(7), _A_(8)); break;
    case 10: ((ESYSCALL10)(f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5), _A_(6), _A_(7), _A_(8), _A_(9)); break;
    case 11: ((ESYSCALL11)(f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5), _A_(6), _A_(7), _A_(8), _A_(9), _A_(10)); break;
    case 12: ((ESYSCALL12)(f))(_A_(0), _A_(1), _A_(2), _A_(3), _A_(4), _A_(5), _A_(6), _A_(7), _A_(8), _A_(9), _A_(10), _A_(11)); break;
    }
    return NULL;
}

// 设置回调
SetEventCallback(GET_CALLBACK(doEventCallbackProc));
  • TForm消息回调
// f: addr
// msg: TMessage
void* LCLAPI doMessageCallbackProc(void* f, void* msg) {
   ((void(*)(void*))f)(msg);
    return NULL;
}

// 设置回调
SetMessageCallback(GET_CALLBACK(doMessageCallbackProc));
  • 线程同步回调
static TThreadProc threadSyncProc;

void* LCLAPI doThreadSyncCallbackProc() {
    if (threadSyncProc) {
        ((TThreadProc)threadSyncProc)();
        threadSyncProc = NULL;
    }
    return NULL;
}

// 设置回调
SetThreadSyncCallback(GET_CALLBACK(doThreadSyncCallbackProc));

// 线程同步操作
void ThreadSync(TThreadProc fn) {
   
#ifdef __GNUC__
    pthread_mutex_lock(&threadSyncMutex);
#else
    EnterCriticalSection(&threadSyncMutex);
#endif
    threadSyncProc = fn;
    Synchronize(FALSE);
    threadSyncProc = NULL;
#ifdef __GNUC__
    pthread_mutex_unlock(&threadSyncMutex);
#else
    LeaveCriticalSection(&threadSyncMutex);
#endif
   
}
集合类型操作
// 集合加法,val...中存储为位的索引,下标为0
TSet Include(TSet s, uint8_t val) {
    return (TSet)(s | (1 << val));
}

// 集合减法,val...中存储为位的索引,下标为0
TSet Exclude(TSet s, uint8_t val) {
    return (TSet)(s & (~(1 << val)));
}

// 集合类型的判断,val表示位数,下标为0
BOOL InSet(uint32_t s, uint8_t val) {
    if ((s&(1 << val)) != 0) {
        return TRUE;
    }
    return FALSE;
}

初始liblcl示例
#define GET_CALLBACK(name) \
  (void*)&name
 
static void init_lib_lcl() {
#ifdef __GNUC__
    pthread_mutex_init(&threadSyncMutex, NULL);
#else
    InitializeCriticalSection(&threadSyncMutex);
#endif

    // 设置事件的回调函数
    SetEventCallback(GET_CALLBACK(doEventCallbackProc));
    // 设置消息回调
    SetMessageCallback(GET_CALLBACK(doMessageCallbackProc));
    // 设置线程同步回调
    SetThreadSyncCallback(GET_CALLBACK(doThreadSyncCallbackProc));
    // 初始实例类
    Application = Application_Instance();
    Screen = Screen_Instance();
    Mouse = Mouse_Instance();            
    Clipboard = Clipboard_Instance();    
    Printer = Printer_Instance();        
}

static void un_init_lib_lcl() {
#ifdef __GNUC__
    pthread_mutex_destroy(&threadSyncMutex);
#else
    DeleteCriticalSection(&threadSyncMutex);
#endif
}

c语言调用liblcl示例

#include "liblcl.h" 

 
#ifdef _WIN32
// UTF8解码
char *UTF8Decode(char* str) {
    int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0);
    wchar_t* wCharBuffer = (wchar_t*)malloc(len * sizeof(wchar_t) + 1);
    MultiByteToWideChar(CP_UTF8, 0, str, -1, wCharBuffer, len);

    len = WideCharToMultiByte(CP_ACP, 0, wCharBuffer, -1, 0, 0, 0, NULL);
    char* aCharBuffer = (char*)malloc(len * sizeof(char) + 1);
    WideCharToMultiByte(CP_ACP, 0, wCharBuffer, -1, aCharBuffer, len, 0, NULL);
    free((void*)wCharBuffer);

    return aCharBuffer;
}
#endif

// 按钮单击事件
void onButton1Click(TObject sender) {
    ShowMessage("Hello world!");
}

// 文件拖放事件
void onOnDropFiles(TObject sender, void* aFileNames, intptr_t len) {
    printf("aFileNames: %p, len=%d\n", aFileNames, len);
    intptr_t i;
    // GetFPStringArrayMember 为一个从Lazarus的string数组中获取成员的函数。
    for (i = 0; i < len; i++) {
        
#ifdef _WIN32
        // 由于liblcl使用的是UTF-8编码,所以获取或者传入的在Windows下都要经过UTF-8编/解码 
        char *filename = UTF8Decode(GetFPStringArrayMember(aFileNames, i));
#else
        // Linux与macOS默认都是UTF-8,则无需编/解码
        char *filename = GetFPStringArrayMember(aFileNames, i);
#endif
        printf("file[%d]=%s\n", i+1, filename);
#ifdef _WIN32
        free((void*)filename);
#endif
    }
}

// 窗口键盘按下事件
void onFormKeyDown(TObject sender, Char* key, TShiftState shift) {
    printf("key=%d, shift=%d\n", *key, shift);
    if (*key == vkReturn) {
        ShowMessage("press Enter!");
    }

    TShiftState s = Include(0, ssAlt);
    if (InSet(s, ssAlt)) {
        printf("ssAlt1\n");
    }
    s = Exclude(s, ssAlt);
    if (!InSet(s, ssAlt)) {
        printf("ssAlt2\n");
    }
}

// 编辑框内容改变事件
void onEditChange(TObject sender) {
    printf("%s\n", Edit_GetText(sender));
}

int main()
{
    // 加载库
#ifdef _WIN32
    if (load_liblcl("liblcl.dll")) {
#endif
#ifdef __linux__
    if (load_liblcl("liblcl.so")) {
#endif
#ifdef __APPLE__
    if (load_liblcl("liblcl.dylib")) {
#endif

        // 主窗口显示在任务栏,仅Windows有效
        Application_SetMainFormOnTaskBar(Application, TRUE); 
        // 应用程序标题,影响到:比如ShowMessage的标题。
        Application_SetTitle(Application, "Hello LCL"); 
        // 初始化应用程序
        Application_Initialize(Application);

        // 创建窗口
        TForm form = Application_CreateForm(Application, FALSE);
        // 设置窗口标题
        Form_SetCaption(form, "LCL Form");
        // 设置窗口位置
        Form_SetPosition(form, poScreenCenter);

        // --- 拖放文件测试 ---
        // 接受文件拖放
        Form_SetAllowDropFiles(form, TRUE); 
        // 拖放文件事件
        Form_SetOnDropFiles(form, onOnDropFiles);

        // 窗口优先接受按键,不受其它影响
        Form_SetKeyPreview(form, TRUE);

        // 窗口按键事件
        Form_SetOnKeyDown(form, onFormKeyDown);
        
        // ---------- 从内存流或者文件加载UI布局文件 ----------
        // 从文件加载窗口设置
        // 从流加载
        //TMemoryStream mem = NewMemoryStream();
        //MemoryStream_Write(mem, data, datalen);
        //MemoryStream_SetPosition(mem, 0); 
        //ResFormLoadFromStream(mem, form);
        //MemoryStream_Free(mem);
        
        // 从文件加载
        //ResFormLoadFromFile("./Form1.gfm", form);

        // ----------  动态创建控件 ---------- 
        // 创建一个按钮
        TButton btn = Button_Create(form);
        // 设置子父窗口
        Button_SetParent(btn, form);
        // 设置按钮单击事件
        Button_SetOnClick(btn, onButton1Click);
        // 设置按钮标题
        Button_SetCaption(btn, "button1");
        // 设置按钮在Parent的左边位置
        Button_SetLeft(btn, 100);
        // 设置按钮在Parent的顶边位置
        Button_SetTop(btn, 100);
        
        // 创建一个单行文件框(多行为TMemo)
        TEdit edit = Edit_Create(form);
        // 设置子父窗口
        Edit_SetParent(edit, form);
        // 设置左边
        Edit_SetLeft(edit, 10);
        // 设置顶边
        Edit_SetTop(edit, 10);
        // 设置编辑器内容改变事件
        Edit_SetOnChange(edit, onEditChange);

        // 运行app
        Application_Run(Application);

        // 释放liblcl库
        close_liblcl();
    }
    return 0;
}

授权

保持跟Lazarus LCL组件采用相同的授权协议: COPYING.modifiedLGPL