Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

/DELAYLOAD Linux counterpart for shared objects

580 views
Skip to first unread message

per...@gmail.com

unread,
Jun 22, 2008, 3:41:56 PM6/22/08
to
How do I construct an executable that is capable of using a shared
object when a user requests certain functions, without needing
that .so for users that don’t do such requests? The point is that
users not interested in the services offered by the particular .so
should not have to install it.

Of course I could use dlopen(3) in my code, but then it seems I’d have
to use dlsym for maybe hundreds of functions of the .so. On windoze I
can use /DELAYLOAD and the exe starts without bothering about the dll,
until there’s a reference, and I just have to handle an
ERROR_MOD_NOT_FOUND exception on attempts to call the main entry
functions of the dll.

Thanks,
Per

John Reiser

unread,
Jun 22, 2008, 9:58:31 PM6/22/08
to
per...@gmail.com wrote:
> How do I construct an executable that is capable of using a shared
> object when a user requests certain functions, without needing
> that .so for users that don’t do such requests? The point is that
> users not interested in the services offered by the particular .so
> should not have to install it.

Thereby leading to disappointment when they _do_ attempt to use
those services after accumulating several hours of state that
is lost because the program did not verify the presence of
all resources at the beginning.

> Of course I could use dlopen(3) in my code, but then it seems I’d have
> to use dlsym for maybe hundreds of functions of the .so.

No doubt you have heard of a loop that updates a transfer vector?

> On windoze I
> can use /DELAYLOAD and the exe starts without bothering about the dll,
> until there’s a reference, and I just have to handle an
> ERROR_MOD_NOT_FOUND exception on attempts to call the main entry
> functions of the dll.

You can simulate the same thing with ELF .so by using a transfer vector
whose every entry begins by pointing to a "dlopen the .so" function.

--

David Schwartz

unread,
Jun 23, 2008, 3:11:35 AM6/23/08
to
On Jun 22, 12:41 pm, per...@gmail.com wrote:

> Of course I could use dlopen(3) in my code, but then it seems I’d have
> to use dlsym for maybe hundreds of functions of the .so.

Umm, so what? Computers are great at performing tedious, repetitive
tasks millions of times. Hundreds should be no problem at all.

DS

per...@gmail.com

unread,
Jun 23, 2008, 5:28:51 AM6/23/08
to
On 23 Juni, 03:58, John Reiser <jrei...@BitWagon.com> wrote:
> per...@gmail.com wrote:
> > How do I construct an executable that is capable of using a shared
> > object when a user requests certain functions, without needing
> > that .so for users that don’t do such requests? The point is that
> > users not interested in the services offered by the particular .so
> > should not have to install it.
>
> Thereby leading to disappointment when they _do_ attempt to use
> those services after accumulating several hours of state that
> is lost because the program did not verify the presence of
> all resources at the beginning.
>
For brevity, I did not give the whole story. The shared object in
question contains the API of a commercial product. Some people buy
that product, install it, and configure my program for that. Other
people use another product, install and configure for that, and they
don't want to buy/install anything else.

> > Of course I could use dlopen(3) in my code, but then it seems I’d have
> > to use dlsym for maybe hundreds of functions of the .so.
>
> No doubt you have heard of a loop that updates a transfer vector?

My ignorance is almost complete on ELF.


>
> > On windoze I
> > can use /DELAYLOAD and the exe starts without bothering about the dll,
> > until there’s a reference, and I just have to handle an
> > ERROR_MOD_NOT_FOUND exception on attempts to call the main entry
> > functions of the dll.
>
> You can simulate the same thing with ELF .so by using a transfer vector
> whose every entry begins by pointing to a "dlopen the .so" function.
>

Here is what I understand: Dlsym returns a function pointer, which can
be used to call the function, and I can of course collect the pointers
in an array. A big disadvantage is that the compiler, as far as I
know, won't check any parameters of the calls to the .so. I don't know
anything about the proposed ELF solution.

Best regards,
Per

David Schwartz

unread,
Jun 23, 2008, 8:27:03 AM6/23/08
to
On Jun 23, 2:28 am, per...@gmail.com wrote:

> Here is what I understand: Dlsym returns a function pointer, which can
> be used to call the function, and I can of course collect the pointers
> in an array. A big disadvantage is that the compiler, as far as I
> know, won't check any parameters of the calls to the .so. I don't know
> anything about the proposed ELF solution.

The compiler will still check the parameters, so long as you ask it
to.

For example, assume that the library contains a function called
'some_library_func'. Assume that your code will only call this
function if the library exists and the function can be resolved in it
(you need some kind of 'loader' function that fills in all the
pointers and makes sure the library is there). Your wrapper looks like
this:

int some_library_func(int foo, void *bar)
{
return my_table->some_library_func(foo, bar);
}

This is one of several ways to do it that still preserves the types.
Another way:

int some_library_func(int foo, void *bar)
{
static void *(slf)(int, void *)=NULL;
if(slf==NULL)
{
if(library_ptr==NULL) abort(); // not supposed to happen
slf=((void) (*) (int, void *)) dlsym(library_ptr,
"some_library_func");
if(slf==NULL) abort(); // not supposed to happen
}
return (foo, bar);
}

Note: All examples untested.

DS

per...@gmail.com

unread,
Jun 23, 2008, 10:12:36 AM6/23/08
to
On 23 Juni, 14:27, David Schwartz <dav...@webmaster.com> wrote:
> On Jun 23, 2:28 am, per...@gmail.com wrote:
>
> > Here is what I understand: Dlsym returns a function pointer, which can
> > be used to call the function, and I can of course collect the pointers
> > in an array. A big disadvantage is that the compiler, as far as I
> > know, won't check any parameters of the calls to the .so. I don't know
> > anything about the proposed ELF solution.
>
> The compiler will still check the parameters, so long as you ask it
> to.
>
> For example, assume that the library contains a function called
> 'some_library_func'. Assume that your code will only call this
> function if the library exists and the function can be resolved in it
> (you need some kind of 'loader' function that fills in all the
> pointers and makes sure the library is there). Your wrapper looks like
> this:
>
> int some_library_func(int foo, void *bar)
> {
> return my_table->some_library_func(foo, bar);
>
> }

Thanks, David!
No solution without writing wrapper functions? I'm shuddering for
declaring a function pointer to the following example of function in
the library in question:
sword OCIEnvNlsCreate (OCIEnv **envp, ub4 mode, dvoid *ctxp,
dvoid *(*malocfp)(dvoid *ctxp, size_t size),
dvoid *(*ralocfp)(dvoid *ctxp, dvoid *memptr, size_t
newsize),
void (*mfreefp)(dvoid *ctxp, dvoid *memptr),
size_t xtramem_sz, dvoid **usrmempp,
ub2 charset, ub2 ncharset);
/Per

David Schwartz

unread,
Jun 23, 2008, 10:33:30 AM6/23/08
to
On Jun 23, 7:12 am, per...@gmail.com wrote:

> Thanks, David!
> No solution without writing wrapper functions? I'm shuddering for
> declaring a function pointer to the following example of function in
> the library in question:
> sword   OCIEnvNlsCreate (OCIEnv **envp, ub4 mode, dvoid *ctxp,
>                  dvoid *(*malocfp)(dvoid *ctxp, size_t size),
>                  dvoid *(*ralocfp)(dvoid *ctxp, dvoid *memptr, size_t
> newsize),
>                  void   (*mfreefp)(dvoid *ctxp, dvoid *memptr),
>                  size_t xtramem_sz, dvoid **usrmempp,
>                  ub2 charset, ub2 ncharset);

I know there are ways to automate/simplify this. I can't seem to find
any of them right now. Maybe someone reading this thread will chime in
with a suggestion?

DS

Rainer Weikusat

unread,
Jun 23, 2008, 10:42:13 AM6/23/08
to
David Schwartz <dav...@webmaster.com> writes:
> On Jun 23, 7:12 am, per...@gmail.com wrote:
>> No solution without writing wrapper functions? I'm shuddering for
>> declaring a function pointer to the following example of function in
>> the library in question:
>> sword   OCIEnvNlsCreate (OCIEnv **envp, ub4 mode, dvoid *ctxp,
>>                  dvoid *(*malocfp)(dvoid *ctxp, size_t size),
>>                  dvoid *(*ralocfp)(dvoid *ctxp, dvoid *memptr, size_t
>> newsize),
>>                  void   (*mfreefp)(dvoid *ctxp, dvoid *memptr),
>>                  size_t xtramem_sz, dvoid **usrmempp,
>>                  ub2 charset, ub2 ncharset);
>
> I know there are ways to automate/simplify this.

C actually supports function types, not only function pointer types.
This means a pointer to the function above can be declared (with the
help of typedef) as below:

typedef sword create_routine(OCIENV **, ub4, dvoid *,
dvoid *(*)(dvoid *, size_t),
dvoid *(*)(dvoid *, dvoid *, size_t),
void (*)(dvoid *, dvoid *),
size_t, void **, ub2, ub2);

create_routine *create_it;

A further simplification would be to additionally declare the types of
the passed function pointers, eg

typedef dvoid *maloc_routine(dvoid *, size_t);
typedef dvoid *raloc_routine(dvoid *, dvoid *, size_t);
typedef void free_routine(dvoid *, dvoid *);

typedef sword create_routine(OCIEnv **, ub4, dvoid *,
maloc_routine *,
raloc_routine *,
free_routine *,
size_t, dvoid **, ub2, ub2);

create_routine *create_it;

(idea stolen from one of the Steven's books)

Jens Thoms Toerring

unread,
Jun 23, 2008, 10:53:19 AM6/23/08
to
per...@gmail.com wrote:
> No solution without writing wrapper functions? I'm shuddering for
> declaring a function pointer to the following example of function in
> the library in question:
> sword OCIEnvNlsCreate (OCIEnv **envp, ub4 mode, dvoid *ctxp,
> dvoid *(*malocfp)(dvoid *ctxp, size_t size),
> dvoid *(*ralocfp)(dvoid *ctxp, dvoid *memptr, size_t
> newsize),
> void (*mfreefp)(dvoid *ctxp, dvoid *memptr),
> size_t xtramem_sz, dvoid **usrmempp,
> ub2 charset, ub2 ncharset);

That's a case were a typedef may make things a lot easier to read:

typedef sword ( * strange_func )( OCIEnv **, ub4, dvoid *,


dvoid * ( * )( dvoid *, size_t),
dvoid * ( * )( dvoid *, dvoid *, size_t ),
void ( *)( dvoid *, dvoid * ),

size_t, dvoid **, ub2, ub2 );

and afterwards just use 'strange_func' for defining pointers to
it, casting etc. And if you give up on Hungarian notation things
may become even more readable;-)
Regards, Jens
--
\ Jens Thoms Toerring ___ j...@toerring.de
\__________________________ http://toerring.de

per...@gmail.com

unread,
Jun 23, 2008, 1:52:29 PM6/23/08
to
On 23 Juni, 16:53, j...@toerring.de (Jens Thoms Toerring) wrote:
> That's a case were a typedef may make things a lot easier to read:
>
> typedef sword ( * strange_func )( OCIEnv **, ub4, dvoid *,
> dvoid * ( * )( dvoid *, size_t),
> dvoid * ( * )( dvoid *, dvoid *, size_t ),
> void ( *)( dvoid *, dvoid * ),
> size_t, dvoid **, ub2, ub2 );
>
> and afterwards just use 'strange_func' for defining pointers to
> it, casting etc. And if you give up on Hungarian notation things
> may become even more readable;-)

The typedef solves the function pointer declaration! But it seems I
have to do something like the following for each and every API
function (sorry about the Hungarian notation):

typedef sword typeOCIEnvCreate(OCIEnv **, ub4, dvoid *,


dvoid *(*)(dvoid *, size_t),
dvoid *(*)(dvoid *, dvoid *, size_t),
void (*)(dvoid *, dvoid *),

size_t, void **);
typeOCIEnvCreate * fOCIEnvCreate;
sword OCIEnvCreate (OCIEnv **envp, ub4 mode, dvoid *ctxp,


dvoid *(*malocfp)(dvoid *ctxp, size_t size),
dvoid *(*ralocfp)(dvoid *ctxp, dvoid *memptr, size_t
newsize),
void (*mfreefp)(dvoid *ctxp, dvoid *memptr),

size_t xtramem_sz, dvoid **usrmempp)
{
return fOCIEnvCreate(envp, mode, ctxp, malocfp, ralocfp, mfreefp,
xtramem_sz, usrmempp);
}

It seems error-prone and tedious, or a bit of work to generate the
code. A favourite quote by Terrence Parr is ringing in my ears
( something like "Why spend 5 days writing by hand what you can spend
5 years of your life automating").

Isn't there any other solution? On Windows it's so simple with
delayload; no wrappers, just handle an exception on the main API
functions.

/Per

Gil Hamilton

unread,
Jun 24, 2008, 7:12:37 AM6/24/08
to
per...@gmail.com wrote in
news:1747c30c-3da7-4bd7...@a1g2000hsb.googlegroups.com:

> On 23 Juni, 16:53, j...@toerring.de (Jens Thoms Toerring) wrote:

[snip - function wrapper stuff]


> It seems error-prone and tedious, or a bit of work to generate the
> code. A favourite quote by Terrence Parr is ringing in my ears
> ( something like "Why spend 5 days writing by hand what you can spend
> 5 years of your life automating").
>
> Isn't there any other solution? On Windows it's so simple with
> delayload; no wrappers, just handle an exception on the main API
> functions.

Create another stub version of the shared library that has definitions
for all functions without any real body. Something like this:

/* stub.c */
#include <stdio.h>
#include <stdlib.h>
#define F(funcname) \
void funcname(void) { \
fprintf(stderr, #funcname "called!\n"); \
abort(); }
F(foo)
F(bar)
F(baz)
F(OCIEnvNlsCreate)
...


You could probably build this stub.c automatically with a script using
the output from nm run against the *real* .so file. Then you include
the resulting (stub) .so file with your base product -- it satisfies the
loader but will abort if any of the symbols in the stub file are called.
In the event the customer buys the real "commercial product" .so, simply
replace the fake one with the real one.

This won't work exactly the same with a C++ library since the symbol
names get mangled by the compiler to (in effect) include the argument
types. But you can probably still do it by using nm to create the
stub.c file with already-mangled names.

GH

Fred Weigel

unread,
Jul 8, 2008, 1:55:01 PM7/8/08
to
per...@gmail.com wrote:

Perfry

You don't have to do the prototypes -- obviously, the prototypes are
correct in the library function header that you are using.

So, define each of the functions as:

#define OCIEnvCreate (*pOCIEnvCreate)
... more function pointer declarations

in an include file. Now, when the include file is processed, each of the
calls becomes "pointer to function" automatically. No source change is
then needed.

In a separate file:

#define DEFINE(f) int (*p##f)() = NULL

DEFINE(OCIEnvCreate);
... more pointer definitions ...

#define TABLE_ENTRY(f) #f, &f

struct {
char *name;
void *p
} table = {
TABLE_ENTRY(OCIEnvCreate),
... more table entries for dlsym parsing ...
0,0
};

In this separate file, DO NOT including the prototypes. Scan the table,
filling in function pointer pointers (after suitable casting) at load
time.

Warning: not tested in the slightest -- just adding some syntactic sugar
here.

You can also use "Foreign Function Interface" -- libffi, as well.

0 new messages