1 ///
2 module ssll;
3 
4 import core.stdc.stdlib : free, malloc;
5 import core.stdc.string : memcpy;
6 
7 public import std.meta : AliasSeq;
8 public import std.traits : hasUDA, getUDAs, ReturnType, Parameters,
9                            ParameterIdentifierTuple;
10 
11 version (Posix)
12 {
13     import core.sys.posix.dlfcn : dlopen, dlclose, RTLD_LAZY;
14 
15     alias LibHandler = void*; ///
16 }
17 else version (Windows)
18 {
19     import core.sys.windows.winbase : LoadLibraryA, FreeLibrary;
20     import core.sys.windows.windef : HINSTANCE;
21 
22     alias LibHandler = HINSTANCE; ///
23 }
24 else static assert(0, "unknown platform");
25 
26 ///
27 auto api(string lname="lib", Linkage linkage=Linkage.c) @property
28 { return ApiUDA(lname, linkage); }
29 
30 ///
31 auto api(Linkage linkage) @property { return ApiUDA("lib", linkage); }
32 
33 ///
34 enum Linkage : string
35 {
36     c = "C", ///
37     d = "D", ///
38     cpp = "C++", ///
39     windows = "Windows", ///
40     objC = "Objective-C", ///
41     system = "System" ///
42 }
43 
44 @nogc nothrow:
45 
46 struct ApiUDA { string libname; Linkage linkage; }
47 
48 ///
49 LibHandler loadLibrary(string name)
50 {
51     const ln = name.length;
52 
53     if (ln == 0) return null;
54 
55     auto buf = cast(char*)malloc(ln+1);
56     if (buf is null) return null;
57     scope (exit) free(buf);
58 
59     memcpy(buf, name.ptr, ln);
60     buf[ln] = '\0';
61 
62     version (Posix)   return dlopen(buf, RTLD_LAZY);
63     version (Windows) return LoadLibraryA(buf);
64 }
65 
66 ///
67 void unloadLibrary(ref LibHandler lib)
68 {
69     version (Posix)   dlclose(&lib);
70     version (Windows) FreeLibrary(lib);
71 
72     lib = null;
73 }
74 
75 /// used in SSLL_CALL mixin
76 template commaSeparated(string[] arr)
77 {
78     template r(string[] a)
79     {
80         static if (a.length == 0) enum r = "";
81         else static if (a.length == 1) enum r = a[0];
82         else enum r = r!(a[0..$/2]) ~ ", " ~ r!(a[$/2..$]);
83     }
84 
85     enum commaSeparated = r!arr;
86 }
87 
88 ///
89 enum SSLL_CALL = q{
90     enum __dimmy_symbol__;
91     alias __self_function__ = AliasSeq!(__traits(parent, __dimmy_symbol__))[0];
92     enum __self_function_name__ = __traits(identifier, __self_function__);
93     enum __function_pointer_name__ = apiFunctionPointerName!(__self_function_name__);
94     version (ssllCheckLoadingSymbols)
95         if (mixin(__function_pointer_name__ ~ " is null"))
96             assert(0, `function '` ~ __self_function_name__ ~ `' not loaded, call '`
97                      ~ __MODULE__ ~ `.loadApiSybols() before`);
98     mixin((is(ReturnType!__self_function__ == void) ? "" : "return ") ~
99             apiFunctionPointerName!(__traits(identifier, __self_function__)) ~
100         "(" ~ commaSeparated!([ParameterIdentifierTuple!__self_function__]) ~ ");");
101 };
102 
103 ///
104 enum LoadApiSymbolsVerbose
105 {
106     none, ///
107     message, ///
108     assertion ///
109 }
110 
111 ///
112 mixin template SSLL_INIT()
113 {
114     alias apiFuncs = funcsByUDA!(__traits(parent, loadApiSymbols), ApiUDA);
115 
116     void loadApiSymbols(LoadApiSymbolsVerbose verbose=LoadApiSymbolsVerbose.none)
117     {
118         version (Posix)
119         {
120             import core.sys.posix.dlfcn : dlsym;
121             alias getSymbol = dlsym;
122         }
123         version (Windows)
124         {
125             import core.sys.windows.windows : GetProcAddress;
126             alias getSymbol = GetProcAddress;
127         }
128 
129         foreach (f; apiFuncs)
130         {
131             enum libname = getUDAs!(f, ApiUDA)[$-1].libname;
132             enum fname = __traits(identifier, f) ~ '\0';
133             enum pname = apiFunctionPointerName!(fname[0..$-1]);
134             mixin(pname ~ " = cast(typeof(" ~ pname ~ "))getSymbol("
135                                 ~ libname ~ ", fname.ptr);");
136             if (mixin(pname ~ " is null"))
137             {
138                 with (LoadApiSymbolsVerbose) final switch (verbose) 
139                 {
140                     case none: break;
141                     case message:
142                         import core.stdc.stdio : printf;
143                         printf("can't find '%s' function\n", fname.ptr);
144                         break;
145                     case assertion: assert(0, fname[0..$-1]);
146                 }
147             }
148         }
149     }
150 
151     mixin funcPointers!apiFuncs;
152 }
153 
154 template apiFunctionPointerName(string f)
155 { enum apiFunctionPointerName = "__" ~ f ~"_fnc_ptr"; }
156 
157 template funcsByUDA(alias symbol, uda)
158 {
159     template impl(lst...)
160     {
161         static if (lst.length == 1)
162         {
163             static if (is(typeof(__traits(getMember, symbol, lst[0])) == function))
164             {
165                 alias ff = AliasSeq!(__traits(getMember, symbol, lst[0]))[0];
166                 static if (hasUDA!(ff, uda)) alias impl = AliasSeq!(ff);
167                 else alias impl = AliasSeq!();
168             }
169             else alias impl = AliasSeq!();
170         }
171         else alias impl = AliasSeq!(impl!(lst[0..$/2]), impl!(lst[$/2..$]));
172     }
173 
174     alias funcsByUDA = impl!(__traits(allMembers, symbol));
175 }
176 
177 mixin template funcPointers(funcs...)
178 {
179     static if (funcs.length == 0) {}
180     else static if (funcs.length == 1)
181     {
182         alias __this = funcs[0];
183         enum linkage = getUDAs!(__this, ApiUDA)[$-1].linkage;
184         mixin(`private __gshared extern(`~ linkage ~
185         `) @nogc nothrow ReturnType!__this function(Parameters!__this) ` ~
186                 apiFunctionPointerName!(__traits(identifier, __this)) ~ `;`);
187     }
188     else
189     {
190         mixin funcPointers!(funcs[0..$/2]);
191         mixin funcPointers!(funcs[$/2..$]);
192     }
193 }