Page 6 of 8 FirstFirst ... 45678 LastLast
Results 51 to 60 of 72

Thread: There's A Direct3D 9.0 Gallium3D State Tracker

  1. #51
    Join Date
    Sep 2008
    Location
    Netherlands
    Posts
    510

    Default

    Quote Originally Posted by BlackStar View Post
    That's what COM does on Windows, it allows you to export C++ objects from a dll (interfaces, to be exact). It *is* possible to achieve something similar on Linux (more info) and I guess Qt is using a similar approach.
    Ouch, that shit is ugly. Wouldn't anyone using C++ for libraries want to fix the dynamic linker to also support classes? Or is ld a case of "nobody touches that code"?
    The issue is that "source compatibility" requires an implementation of COM. Judging from the COM documentation, I can't imagine this task being all that simple.
    Well, it's D3D9 for Unix, not for Windows. The compatibility has to end somewhere, or you'd have to port Windows completely. And that's already a project called Wine.

  2. #52
    Join Date
    Oct 2007
    Location
    Under the bridge
    Posts
    2,153

    Default

    Since C++ doesn't define an ABI, any code that relied on such a feature would end up unportable to other compilers/linkers. If there was one thing C++0x (oops, C++1x now) ought to have fixed it was this - but I guess the commitee wanted to maintain backwards compatibility at all costs, so...

    As things stand, C++ will never gain support for real modules, which means we'll always need to rely on ugly hacks or fall back to extern "C" and hope the universe doesn't implode (because the C++ implementation threw an exception, for example).

    [/offtopic]

  3. #53
    Join Date
    Jan 2010
    Posts
    23

    Default

    Since you're insisting on making a complete ass of yourself, let me clarify with examples from my own code.
    The library has an allocator function (and automatic deallocator but peace be with that for now). The ONLY allocator function that's not a member of a class is Direct3DCreate9:
    Code:
    extern "C" IDirect3D9 *
    Direct3DCreate9( UINT sdk_version )
    {
        IDirect3D9 *d3d9 = NULL;
    
        /* Why? just because. */
        if (sdk_version != D3D_SDK_VERSION) { return NULL; }
    
        try {
            d3d9 = new IDirect3D9;
        } catch (std::bad_alloc e) {
            if (d3d9) { delete d3d9; }
            return NULL;
        }
    
        return d3d9;
    }
    It returns a pointer to an IDirect3D9 object. This object has member functions that you can call by simply using the -> operator as you would any static class. When you do that, ld.so will resolve the mangled function name in the library (automatically) and call that particular entry.
    In code, to the end user, it looks like this:
    Code:
    #include <d3d9.h>
    ...
    LPDIRECT3D9 d3d9;
    d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
    To achieve this, simply do what you do with every other C/C++ library. In gcc you would simply specify -ld3d9 and it will do all the work for you. None of that retarded bullshit.
    Now let's say I update one of the classes you're using by adding a couple of functions to it for my own amusement. What happens? Absolutely nothing happens. Your binary will still resolve the names of the functions you call just as it did before and all is good in wonderland. Since the allocator functions are all inside the library, your binary never knows the size of any given object, nor does it need to. All it needs to concern itself about is a subset of the vtable filled with names (not offsets as I first thought).

    I've tested this to be true on ELF binaries, and ELF binaries being the only thing I really care about for binary compat. we're pretty much good. Any wrapper like WINE will obviously use a wrapper still, but the difference will be that the wrapper will map every single call directly to a call into libd3d9.so

    EDIT:
    If you want to see first hand how close this looks to actual D3D on windows, look in ut.cpp. Be wary though, that the XD3D9 API will change very soon to map more closely to D3D9. It was very much a rush job.
    Last edited by zhasha; 01-23-2010 at 12:13 PM.

  4. #54
    Join Date
    Feb 2009
    Posts
    165

    Default

    Quote Originally Posted by zhasha View Post
    ...
    Your binary will still resolve the names of the functions you call just as it did before and all is good in wonderland. Since the allocator functions are all inside the library, your binary never knows the size of any given object, nor does it need to. All it needs to concern itself about is a subset of the vtable filled with names (not offsets as I first thought).
    ...
    Wow, this is very weird behaviour. I mean, when does the lookup for the member names happen exacly? If it happens when you call the function, I see this as a very "eww" and slow trick. And I can't imagine this happening when you cast the returned pointer to the interface... or does it?

    [edit] sorry for the kind of off-topic question here.

    [edit2]: Or do you mean we have to recompile our binary if you change something?

    [edit3]: I'm aware that the user's binary doesn't know about the real size of the final object, but my concern lies on you adding a member function somewhere in the middle of the existing ones, changing the offsets, and therefore becoming incompatible with the first user's *binary*.
    Last edited by mdias; 01-23-2010 at 12:50 PM.

  5. #55
    Join Date
    Sep 2008
    Location
    Netherlands
    Posts
    510

    Default

    Thanks for that explanation. So as long as you don't want to use 'new' to create objects, you're good. And you don't need that in Nine, so it's all good.

    On a completely unrelated note, the fact that you need factories to create objects from dynamically loaded classes in C++ is something I find ugly. I'm not convinced that this is unavoidable with a good linker implementation. Since the linker knows its own name-mangling scheme, there should not be a problem of mismatched name mangling. It should be possible to achieve source platform independence with a class-aware linker, just not binary platform independence.

    Something like this:
    Code:
    MyClass *foo;
    dlopen("myclass.so");
    dl_class_sym(MyClass, "MyClass");
    foo  = new MyClass();
    It would work on all platforms that support class loading like this. The only thing that won't work is taking a compiled .so from one platform and load it on another. But that's not that important, unless you're Wine.

  6. #56
    Join Date
    Feb 2009
    Posts
    165

    Default

    Quote Originally Posted by Remco View Post
    Thanks for that explanation. So as long as you don't want to use 'new' to create objects, you're good. And you don't need that in Nine, so it's all good.

    On a completely unrelated note, the fact that you need factories to create objects from dynamically loaded classes in C++ is something I find ugly. I'm not convinced that this is unavoidable with a good linker implementation. Since the linker knows its own name-mangling scheme, there should not be a problem of mismatched name mangling. It should be possible to achieve source platform independence with a class-aware linker, just not binary platform independence.

    Something like this:
    Code:
    MyClass *foo;
    dlopen("myclass.so");
    dl_class_sym(MyClass, "MyClass");
    foo  = new MyClass();
    It would work on all platforms that support class loading like this. The only thing that won't work is taking a compiled .so from one platform and load it on another. But that's not that important, unless you're Wine.
    Well, I'd kill for that feature on C++, but I don't think it's possible, no... Only the library that implements MyClass would know how to build a new instance of that class. You can have a static member function that does just that though, but that's coming back to the factory thing really...

    On wonderland you'd be able to do what you just said and a little bit more, like overriding member functions after instantiation and so on, but I don't even want to think about the complexity of that compiler-wise...

  7. #57
    Join Date
    Oct 2007
    Location
    Under the bridge
    Posts
    2,153

    Default

    Since the allocator functions are all inside the library, your binary never knows the size of any given object, nor does it need to. All it needs to concern itself about is a subset of the vtable filled with names (not offsets as I first thought).

    I've tested this to be true on ELF binaries, and ELF binaries being the only thing I really care about for binary compat. we're pretty much good. Any wrapper like WINE will obviously use a wrapper still, but the difference will be that the wrapper will map every single call directly to a call into libd3d9.so
    Does this work with e.g. Intel's compiler when libd3d9.so is built with GCC?

  8. #58
    Join Date
    Jan 2010
    Posts
    23

    Default

    Quote Originally Posted by mdias View Post
    Wow, this is very weird behaviour. I mean, when does the lookup for the member names happen exacly? If it happens when you call the function, I see this as a very "eww" and slow trick. And I can't imagine this happening when you cast the returned pointer to the interface... or does it?

    [edit] sorry for the kind of off-topic question here.

    [edit2]: Or do you mean we have to recompile our binary if you change something?

    [edit3]: I'm aware that the user's binary doesn't know about the real size of the final object, but my concern lies on you adding a member function somewhere in the middle of the existing ones, changing the offsets, and therefore becoming incompatible with the first user's *binary*.
    I might have been a bit unclear here:
    Presumably when you first call the function, the mangled name is resolved. This resolution is then stored so that further calls into the library don't need a lengthy procedure. The exact same thing needs to happen when using C libraries too, so in essence, the only difference is that C++ names are mangled to allow functions with the same names but different types.
    You can completely screw up the vtable and still maintain binary compatibility. Provided of course you don't change any current function declarations.

  9. #59
    Join Date
    Feb 2009
    Posts
    165

    Default

    Quote Originally Posted by zhasha View Post
    I might have been a bit unclear here:
    Presumably when you first call the function, the mangled name is resolved. This resolution is then stored so that further calls into the library don't need a lengthy procedure. The exact same thing needs to happen when using C libraries too, so in essence, the only difference is that C++ names are mangled to allow functions with the same names but different types.
    You can completely screw up the vtable and still maintain binary compatibility. Provided of course you don't change any current function declarations.
    In all honesty, I refuse to believe someone would make such a system. I know for a fact that in windows when you get a pointer to a function and call it, the asm code will call the procedure directly, and I suppose the exact same thing happens in linux.
    So, assuming what I say is true (which I can't personally confirm right now, but google seems to agree with me), calling the address returned by dlsym() will call the procedure directly, so there's no way something else can interfere here to resolve names. dlsym() is the only one who takes a symbol name and returns an address, therefore if you change a class' vtable, you're going to get screwed, because the class' member names are never taken into consideration, and the data in the vtable will not be what your client's binary is expecting.

    Actually, this (resolving virtual methods to addresses without exposing the implementation details of final classes) is why a vtable exists.

    In other words: Name resolving only needs to happen once to get a pointer do Direct3DCreate9, after that it's all binary vtable data, no more name resolving. This is because your client's binary could never know what kind of object Direct3DCreate9 returns, all it gets is a pointer, and it hopes it's a pointer to a valid expected interface, so you better not change the interface on the library, or else things will break. Unless you recompile the client's binary using the new interface info.
    Last edited by mdias; 01-23-2010 at 09:27 PM.

  10. #60
    Join Date
    Jan 2010
    Posts
    23

    Default

    Why is everyone convinced I'm trying to screw you all over? If you're seriously idiotic enough to think that executables store the memory address of all external functions statically, try compiling these 2 things:
    Compile with: g++ lib.cpp -shared -o libfoo.so
    lib.cpp
    Code:
    #include "lib.h"
    #include <stdio.h>
    
    void lib::foo(int i)
    {
    	printf("%d\n", i);
    }
    
    void lib::del()
    {
    	delete this;
    }
    
    lib *new_lib()
    {
    	return new lib;
    }
    lib.h
    Code:
    class lib
    {
    public:
    	void foo(int i);
    	void del();
    };
    
    lib *new_lib();
    Compile with: g++ foo.cpp -o foo -lfoo -L.
    foo.cpp
    Code:
    #include "lib.h"
    
    int main()
    {
        lib *l = new_lib();
        l->foo(10);
        l->foo(20);
        l->del();
        return 0;
    }
    then run LD_LIBRARY_PATH=. ./foo
    it'll output:
    Code:
    10
    20
    now let's throw some shit around:
    change lib.h to:
    Code:
    class lib
    {
    private:
        int j;
    
    public:
        lib();
        ~lib();
    
    	void foo(int i);
    	virtual void add(int i);
    	void del();
    };
    
    lib *new_lib();
    and change lib.cpp to:
    Code:
    #include "lib.h"
    #include <stdio.h>
    
    lib::lib() : j(5)
    {
        printf("constructor\n");
    }
    
    lib::~lib()
    {
        printf("destructor\n");
    }
    
    void lib::foo(int i)
    {
    	printf("%d\n", j);
    	add(i);
    }
    
    void lib::add(int i)
    {
        j += i;
    }
    
    void lib::del()
    {
    	delete this;
    }
    
    lib *new_lib()
    {
    	return new lib;
    }
    compile the lib again.
    run the same, untouched executable from before and you'll see:
    Code:
    constructor
    5
    15
    destructor
    oh, and just for arguments sake, try removing a function from the lib and see what the program outputs. I removed lib::del and got:
    Code:
    [zhasha@ztoshiba Desktop]$ LD_LIBRARY_PATH=. ./foo
    constructor
    5
    15
    ./foo: symbol lookup error: ./foo: undefined symbol: _ZN3lib3delEv
    EDIT: Now to put the final nail in the coffin for the myth you've started here. Try adding -Bsymbolic to the library compile line. What this does is resolve as many function pointers as close as it can on link time, or as the ld man page puts it:
    Code:
           -Bsymbolic
               When creating a shared library, bind references to global symbols to the definition within the shared library, if any.  Normally,
               it is possible for a program linked against a shared library to override the definition within the shared library.  This option is
               only meaningful on ELF platforms which support shared libraries.
    NOW STOP BLASTING YOUR UNFOUNDED CLAIMS AROUND.
    Last edited by zhasha; 01-24-2010 at 04:20 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •