- Larva
- Herramientas
- Bindings nativos
Bindings nativos
Llama a bibliotecas de C desde Antril - declaraciones Import, larva bind con encabezados de C y descripciones .bind, y perfiles de bindings.
En esta página
Antril llama directamente a funciones de bibliotecas nativas (.dll, .so, .dylib). Puedes declarar cada función tú mismo, o dejar que larva bind escriba un binding completo a partir de un encabezado de C.
Declarar una función de C
[Import("library")] en una función sin cuerpo la convierte en una llamada a la biblioteca. Un segundo argumento indica el nombre en C cuando el nombre en Antril es distinto:
import System;
import System.Runtime.InteropServices;
into namespace Native
{
[Import("kernel32.dll")]
GetCurrentProcessId() : u32;
[Import("kernel32.dll", "lstrlenW")]
WideLength(String<u16> text) : i32;
Main()
{
Console.WriteLine(GetCurrentProcessId() > 0);
Console.WriteLine(WideLength(new String<u16>(Unicode.ToUtf16("hello"))));
}
}
larva run
true
5
- Los parámetros pueden ser números,
bool, punteros,func*y cadenas. Unstrviaja como una copia terminada en cero; unstr?también puede sernull. El resultado es un número, unbool, un puntero o unfunc*. - Las estructuras de C son tipos
valuecon campos públicos en el orden de C, y pasan a C por puntero (SYSTEMTIME*), en códigounsafe. [ValueLayout(Layout.Explicit)]con[FieldOffset(n)]define la disposición de las uniones, y[ValueLayout(Layout.Sequential, Pack = 1)], la de las estructuras empaquetadas.- No se pueden llamar funciones con un número variable de argumentos ni pasar estructuras por valor.
[Export("name")]en una función estática pública le da ese nombre en C, para que el código C pueda llamarla.System.Runtime.InteropServices.Marshalcopia cadenas y memoria hacia y desde la memoria nativa.
larva bind
larva bind lee las exportaciones de una biblioteca nativa y una descripción de su API, y escribe un binding: una biblioteca de Larva (.dll) con declaraciones [Import]. Con --source, también escribe el binding como código fuente de Antril, para que lo puedas leer.
larva bind <lib.dll|lib.so> <api.bind|api.h> [-o <Name.dll>] [--source Name.ant] [--describe api.bind]
[--no-pp] [-I <dir>]... [--class Name] [--core <dir>]
larva bind --list <lib.dll|lib.so>
larva bind --profile <Profile.ant|folder> [<lib>...] [<api.h>...] [-I <dir>]... [-o <Name.dll>] [--source <folder>]
[--class Name] [--dry-run] [--timings] [--core <dir>]
larva bind --list kernel32.dll muestra los nombres que exporta una biblioteca.
Desde una descripción .bind
Un archivo .bind lista las funciones que quieres, con los tipos de Antril:
# A few of kernel32's functions.
library kernel32.dll
GetCurrentProcessId() : u32
GetTickCount64() : u64
Sleep(u32 milliseconds)
Beep as MakeBeep(u32 frequency, u32 duration) : bool
larva bind kernel32.dll kernel32.bind --source Kernel32.ant
Kernel32Bindings.dll: Kernel32 with 4 function(s) of kernel32.dll
Kernel32.ant: Antril source
Kernel32.ant muestra lo que contiene el binding:
into namespace Bindings
{
/// Functions of kernel32.dll, called directly.
public static class Kernel32
{
public:
[Import("kernel32.dll")]
GetCurrentProcessId() : u32;
[Import("kernel32.dll")]
GetTickCount64() : u64;
[Import("kernel32.dll")]
Sleep(u32 milliseconds);
[Import("kernel32.dll", "Beep")]
MakeBeep(u32 frequency, u32 duration) : bool;
}
}
Un programa usa la biblioteca pasándola a larva build:
import System;
import Bindings;
Main()
{
u64 start = Kernel32.GetTickCount64();
Kernel32.Sleep(50);
Console.WriteLine(Kernel32.GetTickCount64() - start >= 40);
}
larva build -o app.exe main.ant Kernel32Bindings.dll
app.exe (llvm back end, O3, entry app::$Module.Main)
app.exe
true
Lo que puede decir un archivo .bind, una declaración por línea:
| Línea | Qué declara |
|---|---|
library kernel32.dll |
La biblioteca nativa (obligatoria). |
namespace Win, class Kernel32, assembly Win.Bindings |
Dónde van las declaraciones (por defecto, el espacio de nombres Bindings, una clase con el nombre de la biblioteca y un ensamblado con el nombre de la clase más Bindings). |
file Time |
El archivo fuente en el que se escriben las declaraciones siguientes. |
Name(T a, U b) : R |
Una función. |
Entry as Name(…) : R |
Una función con otro nombre en Antril. |
struct Name [pack N] { T field; … }, union Name { … } |
Una estructura o unión de C. |
enum Name : u32 [flags] { A = 1, B = 2 } |
Un enum (flags: un enum de banderas de bits). |
const NAME = 7 |
Una constante entera. |
# … |
Un comentario. |
Desde un encabezado de C
Si en lugar de un archivo .bind le das un encabezado, larva lee las declaraciones de C:
larva bind kernel32.dll api.h --class Kernel32 -o Win.Bindings.dll --source Win.ant --describe win.bind
Win.Bindings.dll: Kernel32 with 2 function(s) of kernel32.dll, 1 struct(s)
Win.ant: Antril source
- Si tienes clang instalado, primero se preprocesa el encabezado (
-Iagrega carpetas de inclusión; en Windows, las del SDK y las de MSVC se agregan automáticamente).--no-pplo lee tal como está. - Las estructuras y uniones se convierten en tipos
valuecon la disposición de C, las constantes de#definey de los enums se convierten en constantes,const char*se convierte enstr, y los punteros a función se convierten enfunc*<…> : R. - Se omiten las funciones que la biblioteca no exporta, y también las que no se pueden llamar (argumentos variables, estructuras por valor).
--describeescribe lo que se leyó como un archivo.bind, que puedes editar y volver a usar conlarva bind.
Una estructura del encabezado se usa por puntero, en código unsafe:
import System;
import Bindings;
Main()
{
SYSTEMTIME now;
unsafe { Kernel32.GetSystemTime(&now); }
Console.WriteLine(now.wYear);
}
Perfiles de bindings
Una biblioteca grande exige tomar decisiones: qué declaraciones conservar, cómo nombrarlas, dónde ponerlas. Un perfil de bindings es una clase de Antril que las toma. Deriva de System.Bindings.BindingProfile, y larva la compila y la ejecuta mientras genera el binding:
larva bind --profile SDLBindingProfile.ant
Este es el perfil del paquete SDL3:
import System;
import System.Bindings;
into namespace SDL.Bindings.Internal
{
/// SDL3 for Larva: a namespace, a class and a file per header (SDL.Init, SDL.Video…), names in Pascal case
/// without `SDL_`, and the init and window flags as bitflag enums.
public class SDLBindingProfile : BindingProfile
{
public:
override Configure(BindingSettings s)
{
// What is read (paths from this file's folder).
s.Libraries.Add("build/SDL3.dll");
s.Headers.Add("include/SDL3/SDL.h");
s.Includes.Add("include");
// Where it goes.
s.Namespace = "SDL";
s.Class = "SDL";
s.Assembly = "SDL.Bindings";
s.Output = "SDL.Bindings.dll";
s.Source = "bindings";
// Names: no `SDL_`, Pascal case (parameters in camel case).
s.Strip.Add("SDL_");
s.TypeCase = NameCase.Pascal;
s.FunctionCase = NameCase.Pascal;
s.ConstantCase = NameCase.Pascal;
s.FieldCase = NameCase.Pascal;
s.ParameterCase = NameCase.Camel;
// One namespace, class and file per header: SDL_video.h → SDL.Video, class Video, Video.ant.
s.NamespaceByHeader = true;
s.ClassByHeader = true;
s.Split = SplitBy.Header;
}
override Start(BindingModel m)
{
// Groups of #defines as typed enums: what C wrote with the typedef takes the enum.
m.MakeEnum("InitFlags", "SDL_INIT_*", true).Typedef("SDL_InitFlags");
m.MakeEnum("WindowFlags", "SDL_WINDOW_*", "SDL_WINDOW_SURFACE_*", true).Typedef("SDL_WindowFlags");
m.MakeEnum("Keycode", "SDLK_*", false).Typedef("SDL_Keycode");
m.MakeEnum("Keymod", "SDL_KMOD_*", true).Typedef("SDL_Keymod");
}
// Leaves out what you do not want in the binding.
override Function(CFunction f) : bool => !f.Entry.StartsWith("SDL_iconv");
// Specific renames; the rest keeps the name the settings propose.
override Name(NameKind kind, str cName, str proposed) : str =>
cName == "SDL_Init" ? "Initialize" : proposed;
}
}
Con este perfil, la API de C de SDL se lee así desde Antril:
import SDL.Init;
import SDL.Events;
unsafe Main() : i32
{
if (!Init.Initialize(InitFlags.Video | InitFlags.Events))
return 1;
Event e;
loop (Events.PollEvent(&e))
{
if (e.Type == (u32)EventType.Quit)
break;
}
Init.Quit();
return 0;
}
Los hooks
larva llama a los métodos del perfil en este orden:
| Método | Cuándo | Qué decide |
|---|---|---|
Configure(BindingSettings s) |
Una vez, antes de leer los encabezados. | Qué se lee y las reglas para todos los nombres. |
Start(BindingModel m) |
Con todo ya leído. | Enums creados a partir de grupos de constantes (m.MakeEnum("Keycode", "SDLK_*", false)), con sus typedefs. |
Enum, Struct, Constant, Function |
Para cada declaración. | false la omite. El hook también puede cambiar su nombre, sus tipos, su espacio de nombres, su clase o su archivo. |
Name(NameKind kind, str cName, str proposed) |
Para cada nombre que ningún hook fijó. | El nombre final. |
Finish(BindingModel m) |
Con el binding completo. | Los últimos cambios. |
Si dos declaraciones terminan con el mismo nombre en el mismo lugar, la segunda conserva su nombre de C y larva muestra una advertencia.
La configuración
| Propiedad | Qué configura |
|---|---|
Libraries, Headers, Includes |
Las bibliotecas nativas (se importa desde la primera), los encabezados y las carpetas de inclusión. Las bibliotecas y los encabezados indicados en la línea de comandos reemplazan a estos. |
Namespace, Class, Assembly |
El espacio de nombres raíz (por defecto, Bindings), la clase de funciones y constantes (por defecto, el nombre de la biblioteca) y el nombre del ensamblado (por defecto, la clase más Bindings). |
Output, Source |
La biblioteca que se escribe y la carpeta del código fuente de Antril (vacía: ninguno). |
Split |
Cómo se divide el código fuente: SplitBy.None, Header, Namespace o Kind. |
NamespaceByHeader, ClassByHeader, HeaderStrip |
Un espacio de nombres y una clase por encabezado, y prefijos que se quitan de los nombres de los encabezados. |
Strip, StripTypes, StripFunctions, StripConstants |
Prefijos que se quitan de los nombres de C. |
TypeCase, FunctionCase, ConstantCase, FieldCase, ParameterCase, MemberCase |
El estilo de mayúsculas de cada tipo de nombre: NameCase.Keep, Pascal, Camel, Upper, Snake. |
StripMemberPrefix |
Los miembros de un enum pierden el prefijo que comparten. |
EnumsFromC, AutoFlags |
Los enums de C se convierten en enums; los enums de bits distintos se convierten en enums de banderas de bits. |
Exports |
Una función declarada que la biblioteca no exporta se omite (ExportCheck.Keep) o es un error (Check). |
LongBits, AllHeaders |
El tamaño del long de C (32 en Windows, 64 en Linux y macOS); si también se conservan las declaraciones de los encabezados del sistema. |
--dry-run ejecuta el perfil sin escribir nada, y --timings muestra cada paso. El editor analiza un perfil como cualquier archivo de Antril, con System.Bindings disponible.
Usar un binding en un proyecto
Un proyecto nombra el binding compilado con assembly: y la biblioteca nativa con lib: en su bloque DEPS. La biblioteca nativa se copia junto al programa. Deja el perfil y el código fuente generado fuera de la compilación:
chunk SOURCES:
exclude: [bindings, SDLBindingProfile.ant]
chunk DEPS:
assembly: SDL.Bindings.dll,
lib: build/SDL3.dll
Un binding nunca tiene el mismo nombre de archivo que la biblioteca nativa que envuelve: Windows cargaría una en lugar de la otra. Por eso larva bind lo nombra <Name>Bindings.dll o <Name>.Bindings.dll, y rechaza un -o que coincida con la biblioteca nativa.
Para compartir un binding, publícalo como un paquete de Hive.
Editar esta página en GitHub Reportar un problema