]>
Commit | Line | Data |
---|---|---|
cd5bf2a6 RR |
1 | /* |
2 | * dlf.c | |
3 | * | |
4 | * $Id$ | |
5 | * | |
6 | * Dynamic Library Loader (mapping to SVR4) | |
7 | * | |
8 | * The iODBC driver manager. | |
9 | * | |
10 | * Copyright (C) 1995 by Ke Jin <kejin@empress.com> | |
11 | * | |
12 | * This library is free software; you can redistribute it and/or | |
13 | * modify it under the terms of the GNU Library General Public | |
14 | * License as published by the Free Software Foundation; either | |
15 | * version 2 of the License, or (at your option) any later version. | |
16 | * | |
17 | * This library is distributed in the hope that it will be useful, | |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 | * Library General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU Library General Public | |
23 | * License along with this library; if not, write to the Free | |
24 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
25 | */ | |
1a6944fd | 26 | |
cd5bf2a6 | 27 | #include <dlf.h> |
1a6944fd RR |
28 | #include <errno.h> |
29 | ||
cd5bf2a6 RR |
30 | #ifdef DLDAPI_DEFINED |
31 | #undef DLDAPI_DEFINED | |
1a6944fd RR |
32 | #endif |
33 | ||
cd5bf2a6 RR |
34 | #ifdef DLDAPI_SVR4_DLFCN |
35 | #define DLDAPI_DEFINED | |
36 | static char sccsid[] = "@(#)dynamic load interface -- SVR4 dlfcn"; | |
1a6944fd RR |
37 | #endif |
38 | ||
cd5bf2a6 | 39 | /********************************* |
1a6944fd | 40 | * |
cd5bf2a6 | 41 | * HP/UX |
1a6944fd RR |
42 | * |
43 | *********************************/ | |
1a6944fd | 44 | |
cd5bf2a6 RR |
45 | #ifdef DLDAPI_HP_SHL |
46 | #define DLDAPI_DEFINED | |
47 | #include <dl.h> | |
1a6944fd | 48 | |
cd5bf2a6 RR |
49 | static char sccsid[] = "@(#)dynamic load interface -- HP/UX dl(shl)"; |
50 | ||
51 | void * | |
52 | dlopen (char *path, int mode) | |
1a6944fd | 53 | { |
cd5bf2a6 | 54 | return (void *) shl_load ((char *) (path), BIND_DEFERRED, 0L); |
1a6944fd RR |
55 | } |
56 | ||
cd5bf2a6 RR |
57 | |
58 | void * | |
59 | dlsym (void *hdll, char *sym) | |
1a6944fd | 60 | { |
cd5bf2a6 RR |
61 | void *symaddr = 0; |
62 | int ret; | |
1a6944fd | 63 | |
cd5bf2a6 RR |
64 | if (!hdll) |
65 | hdll = (void *) PROG_HANDLE; | |
1a6944fd | 66 | |
cd5bf2a6 RR |
67 | /* Remember, a driver may export calls as function pointers |
68 | * (i.e. with type TYPE_DATA) rather than as functions | |
69 | * (i.e. with type TYPE_PROCEDURE). Thus, to be safe, we | |
70 | * uses TYPE_UNDEFINED to cover all of them. | |
71 | */ | |
72 | ret = shl_findsym ((shl_t *) & hdll, sym, TYPE_UNDEFINED, &symaddr); | |
1a6944fd | 73 | |
cd5bf2a6 RR |
74 | if (ret == -1) |
75 | return 0; | |
1a6944fd | 76 | |
cd5bf2a6 | 77 | return symaddr; |
1a6944fd RR |
78 | } |
79 | ||
cd5bf2a6 RR |
80 | |
81 | char * | |
82 | dlerror () | |
1a6944fd | 83 | { |
cd5bf2a6 | 84 | extern char *strerror (); |
1a6944fd | 85 | |
cd5bf2a6 | 86 | return strerror (errno); |
1a6944fd RR |
87 | } |
88 | ||
cd5bf2a6 RR |
89 | |
90 | int | |
91 | dlclose (void *hdll) | |
1a6944fd | 92 | { |
cd5bf2a6 | 93 | return shl_unload ((shl_t) hdll); |
1a6944fd | 94 | } |
cd5bf2a6 | 95 | #endif /* end of HP/UX Seection */ |
1a6944fd | 96 | |
1a6944fd RR |
97 | |
98 | /********************************* | |
99 | * | |
cd5bf2a6 | 100 | * IBM AIX |
1a6944fd RR |
101 | * |
102 | *********************************/ | |
cd5bf2a6 RR |
103 | |
104 | #ifdef DLDAPI_AIX_LOAD | |
105 | #define DLDAPI_DEFINED | |
106 | #include <sys/types.h> | |
107 | #include <sys/ldr.h> | |
108 | #include <sys/stat.h> | |
109 | #include <nlist.h> | |
1a6944fd RR |
110 | |
111 | /* | |
cd5bf2a6 | 112 | * Following id sting is a copyright mark. Removing(i.e. use the |
1a6944fd | 113 | * source code in this .c file without include it or make it not |
cd5bf2a6 RR |
114 | * appear in the final object file of AIX platform) or modifing |
115 | * it without permission from original author(kejin@empress.com) | |
1a6944fd RR |
116 | * are copyright violation. |
117 | */ | |
cd5bf2a6 RR |
118 | static char sccsid[] |
119 | = "@(#)dynamic load interface, Copyright(c) 1995 by Ke Jin"; | |
1a6944fd | 120 | |
cd5bf2a6 RR |
121 | #ifndef HTAB_SIZE |
122 | #define HTAB_SIZE 256 | |
123 | #endif | |
1a6944fd | 124 | |
cd5bf2a6 | 125 | #define FACTOR 0.618039887 /* i.e. (sqrt(5) - 1)/2 */ |
1a6944fd | 126 | |
cd5bf2a6 RR |
127 | #ifndef ENTRY_SYM |
128 | #define ENTRY_SYM ".__start" /* default entry point for aix */ | |
129 | #endif | |
1a6944fd RR |
130 | |
131 | typedef struct slot_s | |
cd5bf2a6 RR |
132 | { |
133 | char *sym; | |
134 | long fdesc[3]; /* 12 bytes function descriptor */ | |
135 | struct slot_s *next; | |
136 | } | |
137 | slot_t; | |
1a6944fd RR |
138 | |
139 | /* Note: on AIX, a function pointer actually points to a | |
140 | * function descriptor, a 12 bytes data. The first 4 bytes | |
cd5bf2a6 | 141 | * is the virtual address of the function. The next 4 bytes |
1a6944fd | 142 | * is the virtual address of TOC (Table of Contents) of the |
cd5bf2a6 RR |
143 | * object module the function belong to. The last 4 bytes |
144 | * are always 0 for C and Fortran functions. Every object | |
1a6944fd RR |
145 | * module has an entry point (which can be specified at link |
146 | * time by -e ld option). iODBC driver manager requires ODBC | |
147 | * driver shared library always use the default entry point | |
148 | * (so you shouldn't use -e ld option when creating a driver | |
cd5bf2a6 | 149 | * share library). load() returns the function descriptor of |
1a6944fd RR |
150 | * a module's entry point. From which we can calculate function |
151 | * descriptors of other functions in the same module by using | |
cd5bf2a6 RR |
152 | * the fact that the load() doesn't change the relative |
153 | * offset of functions to their module entry point(i.e the | |
154 | * offset in memory loaded by load() will be as same as in | |
155 | * the module library file). | |
1a6944fd RR |
156 | */ |
157 | ||
cd5bf2a6 RR |
158 | typedef slot_t *hent_t; |
159 | typedef struct nlist nlist_t; | |
160 | typedef struct stat stat_t; | |
1a6944fd | 161 | |
7e616b10 | 162 | typedef struct obj |
cd5bf2a6 RR |
163 | { |
164 | int dev; /* device id */ | |
165 | int ino; /* inode number */ | |
166 | char *path; /* file name */ | |
167 | int (*pentry) (); /* entry point of this share library */ | |
168 | int refn; /* number of reference */ | |
169 | hent_t htab[HTAB_SIZE]; | |
170 | struct obj * next; | |
171 | } | |
172 | obj_t; | |
173 | ||
174 | static char *errmsg = 0; | |
175 | ||
176 | static void | |
177 | init_htab (hent_t * ht) | |
1a6944fd RR |
178 | /* initate a hashing table */ |
179 | { | |
cd5bf2a6 | 180 | int i; |
1a6944fd | 181 | |
cd5bf2a6 RR |
182 | for (i = 0; i < HTAB_SIZE; i++) |
183 | ht[i] = (slot_t *) 0; | |
1a6944fd | 184 | |
cd5bf2a6 | 185 | return; |
1a6944fd RR |
186 | } |
187 | ||
cd5bf2a6 RR |
188 | |
189 | static void | |
190 | clean_htab (hent_t * ht) | |
1a6944fd RR |
191 | /* free all slots */ |
192 | { | |
cd5bf2a6 RR |
193 | int i; |
194 | slot_t *ent; | |
195 | slot_t *tent; | |
7e616b10 | 196 | |
cd5bf2a6 RR |
197 | for (i = 0; i < HTAB_SIZE; i++) |
198 | { | |
199 | for (ent = ht[i]; ent;) | |
200 | { | |
201 | tent = ent->next; | |
7e616b10 | 202 | |
cd5bf2a6 RR |
203 | free (ent->sym); |
204 | free (ent); | |
7e616b10 | 205 | |
cd5bf2a6 RR |
206 | ent = tent; |
207 | } | |
7e616b10 | 208 | |
cd5bf2a6 RR |
209 | ht[i] = 0; |
210 | } | |
7e616b10 | 211 | |
cd5bf2a6 | 212 | return; |
1a6944fd RR |
213 | } |
214 | ||
cd5bf2a6 RR |
215 | |
216 | static int | |
217 | hash (char *sym) | |
1a6944fd | 218 | { |
cd5bf2a6 RR |
219 | int a, key; |
220 | double f; | |
1a6944fd | 221 | |
cd5bf2a6 RR |
222 | if (!sym || !*sym) |
223 | return 0; | |
1a6944fd | 224 | |
cd5bf2a6 RR |
225 | for (key = *sym; *sym; sym++) |
226 | { | |
227 | key += *sym; | |
228 | a = key; | |
1a6944fd | 229 | |
cd5bf2a6 RR |
230 | key = (int) ((a << 8) + (key >> 8)); |
231 | key = (key > 0) ? key : -key; | |
232 | } | |
1a6944fd | 233 | |
cd5bf2a6 RR |
234 | f = key * FACTOR; |
235 | a = (int) f; | |
1a6944fd | 236 | |
cd5bf2a6 | 237 | return (int) ((HTAB_SIZE - 1) * (f - a)); |
1a6944fd RR |
238 | } |
239 | ||
cd5bf2a6 RR |
240 | |
241 | static hent_t | |
242 | search (hent_t * htab, char *sym) | |
1a6944fd RR |
243 | /* search hashing table to find a matched slot */ |
244 | { | |
cd5bf2a6 RR |
245 | int key; |
246 | slot_t *ent; | |
1a6944fd | 247 | |
cd5bf2a6 | 248 | key = hash (sym); |
1a6944fd | 249 | |
cd5bf2a6 RR |
250 | for (ent = htab[key]; ent; ent = ent->next) |
251 | { | |
252 | if (!strcmp (ent->sym, sym)) | |
253 | return ent; | |
254 | } | |
1a6944fd | 255 | |
cd5bf2a6 | 256 | return 0; /* no match */ |
1a6944fd RR |
257 | } |
258 | ||
cd5bf2a6 RR |
259 | |
260 | static void | |
261 | insert (hent_t * htab, slot_t * ent) | |
1a6944fd RR |
262 | /* insert a new slot to hashing table */ |
263 | { | |
cd5bf2a6 | 264 | int key; |
1a6944fd | 265 | |
cd5bf2a6 | 266 | key = hash (ent->sym); |
1a6944fd | 267 | |
cd5bf2a6 RR |
268 | ent->next = htab[key]; |
269 | htab[key] = ent; | |
1a6944fd | 270 | |
cd5bf2a6 | 271 | return; |
1a6944fd RR |
272 | } |
273 | ||
cd5bf2a6 RR |
274 | |
275 | static slot_t * | |
276 | slot_alloc (char *sym) | |
1a6944fd RR |
277 | /* allocate a new slot with symbol */ |
278 | { | |
cd5bf2a6 | 279 | slot_t *ent; |
1a6944fd | 280 | |
cd5bf2a6 | 281 | ent = (slot_t *) malloc (sizeof (slot_t)); |
1a6944fd | 282 | |
cd5bf2a6 | 283 | ent->sym = (char *) malloc (strlen (sym) + 1); |
1a6944fd | 284 | |
cd5bf2a6 RR |
285 | if (!ent->sym) |
286 | { | |
287 | free (ent); | |
288 | return 0; | |
289 | } | |
1a6944fd | 290 | |
cd5bf2a6 | 291 | strcpy (ent->sym, sym); |
1a6944fd | 292 | |
cd5bf2a6 | 293 | return ent; |
1a6944fd RR |
294 | } |
295 | ||
1a6944fd | 296 | |
cd5bf2a6 | 297 | static obj_t *obj_list = 0; |
1a6944fd | 298 | |
cd5bf2a6 RR |
299 | void * |
300 | dlopen (char *file, int mode) | |
301 | { | |
302 | stat_t st; | |
303 | obj_t *pobj; | |
304 | char buf[1024]; | |
305 | ||
306 | if (!file || !*file) | |
307 | { | |
308 | errno = EINVAL; | |
309 | return 0; | |
310 | } | |
311 | ||
312 | errno = 0; | |
313 | errmsg = 0; | |
314 | ||
315 | if (stat (file, &st)) | |
316 | return 0; | |
317 | ||
318 | for (pobj = obj_list; pobj; pobj = pobj->next) | |
319 | /* find a match object */ | |
320 | { | |
321 | if (pobj->ino == st.st_ino | |
322 | && pobj->dev == st.st_dev) | |
323 | { | |
324 | /* found a match. increase its | |
325 | * reference count and return | |
326 | * its address */ | |
327 | pobj->refn++; | |
328 | return pobj; | |
329 | } | |
330 | } | |
331 | ||
332 | pobj = (obj_t *) malloc (sizeof (obj_t)); | |
333 | ||
334 | if (!pobj) | |
335 | return 0; | |
336 | ||
337 | pobj->path = (char *) malloc (strlen (file) + 1); | |
338 | ||
339 | if (!pobj->path) | |
340 | { | |
341 | free (pobj); | |
342 | return 0; | |
343 | } | |
344 | ||
345 | strcpy (pobj->path, file); | |
346 | ||
347 | pobj->dev = st.st_dev; | |
348 | pobj->ino = st.st_ino; | |
349 | pobj->refn = 1; | |
350 | ||
351 | pobj->pentry = (int (*)()) load (file, 0, 0); | |
352 | ||
353 | if (!pobj->pentry) | |
354 | { | |
355 | free (pobj->path); | |
356 | free (pobj); | |
357 | return 0; | |
358 | } | |
359 | ||
360 | init_htab (pobj->htab); | |
361 | ||
362 | pobj->next = obj_list; | |
363 | obj_list = pobj; | |
364 | ||
365 | return pobj; | |
1a6944fd RR |
366 | } |
367 | ||
cd5bf2a6 RR |
368 | |
369 | int | |
370 | dlclose (void *hobj) | |
1a6944fd | 371 | { |
cd5bf2a6 RR |
372 | obj_t *pobj = (obj_t *) hobj; |
373 | obj_t *tpobj; | |
374 | int match = 0; | |
375 | ||
376 | if (!hobj) | |
377 | { | |
378 | errno = EINVAL; | |
379 | return -1; | |
380 | } | |
381 | ||
382 | errno = 0; | |
383 | errmsg = 0; | |
384 | ||
385 | if (pobj == obj_list) | |
386 | { | |
387 | pobj->refn--; | |
388 | ||
389 | if (pobj->refn) | |
390 | return 0; | |
391 | ||
392 | match = 1; | |
393 | obj_list = pobj->next; | |
394 | } | |
395 | ||
396 | for (tpobj = obj_list; !match && tpobj; tpobj = tpobj->next) | |
397 | { | |
398 | if (tpobj->next == pobj) | |
399 | { | |
400 | pobj->refn--; | |
401 | ||
402 | if (pobj->refn) | |
403 | return 0; | |
404 | ||
405 | match = 1; | |
406 | tpobj->next = pobj->next; | |
407 | } | |
408 | } | |
409 | ||
410 | if (match) | |
411 | { | |
412 | unload ((void *) (pobj->pentry)); | |
413 | clean_htab (pobj->htab); | |
414 | free (pobj->path); | |
415 | free (pobj); | |
416 | } | |
417 | ||
418 | return 0; | |
1a6944fd RR |
419 | } |
420 | ||
cd5bf2a6 RR |
421 | |
422 | char * | |
423 | dlerror () | |
1a6944fd | 424 | { |
cd5bf2a6 | 425 | extern char *sys_errlist[]; |
1a6944fd | 426 | |
cd5bf2a6 RR |
427 | if (!errmsg || !errmsg[0]) |
428 | { | |
429 | if (errno >= 0) | |
430 | return sys_errlist[errno]; | |
7e616b10 | 431 | |
cd5bf2a6 RR |
432 | return ""; |
433 | } | |
1a6944fd | 434 | |
cd5bf2a6 | 435 | return errmsg; |
1a6944fd RR |
436 | } |
437 | ||
cd5bf2a6 RR |
438 | |
439 | void * | |
440 | dlsym (void *hdl, char *sym) | |
1a6944fd | 441 | { |
cd5bf2a6 RR |
442 | nlist_t nl[3]; |
443 | obj_t *pobj = (obj_t *) hdl; | |
444 | slot_t *ent; | |
445 | int (*fp) (); | |
446 | long lbuf[3]; | |
447 | ||
448 | if (!hdl || !(pobj->htab) || !sym || !*sym) | |
449 | { | |
450 | errno = EINVAL; | |
451 | return 0; | |
452 | } | |
453 | ||
454 | errno = 0; | |
455 | errmsg = 0; | |
456 | ||
457 | ent = search (pobj->htab, sym); | |
458 | ||
459 | if (ent) | |
460 | return ent->fdesc; | |
461 | ||
462 | #define n_name _n._n_name | |
463 | ||
464 | nl[0].n_name = ENTRY_SYM; | |
465 | nl[1].n_name = sym; | |
466 | nl[2].n_name = 0; | |
467 | ||
468 | /* There is a potential problem here. If application | |
469 | * did not pass a full path name, and changed the | |
470 | * working directory after the load(), then nlist() | |
471 | * will be unable to open the original shared library | |
472 | * file to resolve the symbols. there are 3 ways to working | |
473 | * round this: 1. convert to full pathname in driver | |
474 | * manager. 2. applications always pass driver's full | |
475 | * path name. 3. if driver itself don't support | |
476 | * SQLGetFunctions(), call it with SQL_ALL_FUNCTIONS | |
477 | * as flag immidately after SQLConnect(), SQLDriverConnect() | |
478 | * and SQLBrowseConnect() to force the driver manager | |
479 | * resolving all will be used symbols. | |
480 | */ | |
481 | if (nlist (pobj->path, nl) == -1) | |
482 | return 0; | |
483 | ||
484 | if (!nl[0].n_type && !nl[0].n_value) | |
485 | { | |
486 | errmsg = "can't locate module entry symbol"; | |
487 | return 0; | |
488 | } | |
489 | ||
490 | /* Note: On AIX 3.x if the object library is not | |
491 | * built with -g compiling option, .n_type field | |
492 | * is always 0. While on 4.x it will be 32. | |
493 | * On AIX 4.x, if the symbol is a entry point, | |
494 | * n_value will be 0. However, one thing is for sure | |
495 | * that if a symbol is not existance in the file, | |
496 | * both .n_type and .n_value would be 0. | |
497 | */ | |
498 | ||
499 | if (!nl[1].n_type && !nl[1].n_value) | |
500 | { | |
501 | errmsg = "symbol not existance in this module"; | |
502 | return 0; | |
503 | } | |
504 | ||
505 | ent = slot_alloc (sym); | |
506 | ||
507 | if (!ent) | |
508 | return 0; | |
509 | ||
510 | /* catch it with a slot in the hashing table */ | |
511 | insert (pobj->htab, ent); | |
512 | ||
513 | memcpy (ent->fdesc, pobj->pentry, sizeof (ent->fdesc)); | |
514 | ||
515 | /* now ent->fdesc[0] is the virtual address of entry point | |
516 | * and ent->fdesc[1] is the TOC of the module | |
517 | */ | |
518 | ||
519 | /* let's calculate the virtual address of the symbol | |
520 | * by adding a relative offset getting from the module | |
521 | * file symbol table, i.e | |
522 | * | |
523 | * functin virtual address = entry point virtual address + | |
524 | * + ( function offset in file - entry point offset in file ) | |
525 | */ | |
526 | ||
527 | (ent->fdesc)[0] = (ent->fdesc)[0] + | |
528 | (nl[1].n_value - nl[0].n_value); | |
529 | ||
530 | /* return the function descriptor */ | |
531 | return ent->fdesc; | |
1a6944fd | 532 | } |
cd5bf2a6 | 533 | #endif /* end of IBM AIX Section */ |
1a6944fd RR |
534 | |
535 | ||
cd5bf2a6 | 536 | /********************************* |
1a6944fd | 537 | * |
cd5bf2a6 | 538 | * Windows 3.x, 95, NT |
1a6944fd RR |
539 | * |
540 | *********************************/ | |
1a6944fd | 541 | |
cd5bf2a6 RR |
542 | #ifdef DLDAPI_WINDOWS |
543 | #define DLDAPI_DEFINED | |
544 | #include <windows.h> | |
1a6944fd | 545 | |
cd5bf2a6 RR |
546 | void FAR * |
547 | dlopen (char FAR * dll, int mode) | |
548 | { | |
549 | HINSTANCE hint; | |
1a6944fd | 550 | |
cd5bf2a6 RR |
551 | if (dll == NULL) |
552 | { | |
553 | return GetWindowWord (NULL, GWW_HINSTANCE); | |
554 | } | |
1a6944fd | 555 | |
cd5bf2a6 | 556 | hint = LoadLibrary (dll); |
1a6944fd | 557 | |
cd5bf2a6 RR |
558 | if (hint < HINSTANCE_ERROR) |
559 | { | |
560 | return NULL; | |
561 | } | |
1a6944fd | 562 | |
cd5bf2a6 | 563 | return (void FAR *) hint; |
1a6944fd RR |
564 | } |
565 | ||
1a6944fd | 566 | |
cd5bf2a6 RR |
567 | void FAR * |
568 | dlsym (void FAR * hdll, char FAR * sym) | |
1a6944fd | 569 | { |
cd5bf2a6 | 570 | return (void FAR *) GetProcAddress (hdll, sym); |
1a6944fd RR |
571 | } |
572 | ||
1a6944fd | 573 | |
cd5bf2a6 RR |
574 | char FAR * |
575 | dlerror () | |
7e616b10 | 576 | { |
cd5bf2a6 | 577 | return 0L; /* unimplemented yet */ |
7e616b10 RR |
578 | } |
579 | ||
7e616b10 | 580 | |
cd5bf2a6 RR |
581 | int |
582 | dlclose (void FAR * hdll) | |
7e616b10 | 583 | { |
cd5bf2a6 | 584 | FreeLibrary ((HINSTANCE) hdll); |
7e616b10 | 585 | } |
cd5bf2a6 | 586 | #endif /* end of Windows family */ |
7e616b10 | 587 | |
7e616b10 RR |
588 | |
589 | /*********************************** | |
590 | * | |
cd5bf2a6 | 591 | * other platforms |
7e616b10 RR |
592 | * |
593 | ***********************************/ | |
cd5bf2a6 RR |
594 | |
595 | #ifdef DLDAPI_OS2 | |
596 | #define DLDAPI_DEFINED | |
597 | /* | |
598 | * DosLoadModule(), DosQueryProcAddress(), DosFreeModule(), ... | |
599 | */ | |
600 | #endif | |
601 | ||
602 | #ifdef DLDAPI_MAC | |
603 | #define DLDAPI_DEFINED | |
1a6944fd RR |
604 | #endif |
605 | ||
606 | #ifdef DLDAPI_NEXT | |
cd5bf2a6 | 607 | #define DLDAPI_DEFINED |
1a6944fd RR |
608 | #endif |
609 | ||
610 | #ifndef DLDAPI_DEFINED | |
cd5bf2a6 | 611 | #error "dynamic load editor undefined" |
1a6944fd | 612 | #endif |