Thread overview
Passing opaque struct between functions/modules
Jan 23, 2013
Sarath Kumar
Jan 23, 2013
Ali Çehreli
Jan 24, 2013
Sarath Kumar
Jan 24, 2013
Mike Parker
Jan 24, 2013
Sarath Kumar
Jan 24, 2013
Mike Parker
Jan 24, 2013
Sarath Kumar
Jan 24, 2013
Maxim Fomin
January 23, 2013
DMD v2.61; openSUSE 12.1

Source:
-------
libA.d:

module libA;

extern (C)
{
        struct Opaque;

        Opaque* getObject();

        void doSomething(Opaque *);
}
----------
libB.d:

module libB;

extern (C)
{
        struct Opaque;

        void doAction(Opaque *);
}
-----------
bug.d

import libA, libB;

int main(string[] args)
{
        auto opaque = libA.getObject();
        libA.doSomething(opaque); // this is okay
        libB.doAction(opaque);  // but this is not, compiler error here
        return 0;
}

When I compile the above files, I get the below error.
$ rdmd bug.d
bug.d(7): Error: function libB.doAction (Opaque*) is not callable using argument types (Opaque*)
bug.d(7): Error: cannot implicitly convert expression (opaque) of type Opaque* to Opaque*

If I do an explicit cast, libB.Opaque*, for opaque in line 7, bug.d, I get completely different set of errors.

$ rdmd bug.d
libB.d(5): Error: struct libB.Opaque unknown size
libB.d(5): Error: struct libB.Opaque no size yet for forward reference
libB.d(5): Error: struct libB.Opaque unknown size
libB.d(5): Error: struct libB.Opaque no size yet for forward reference
libA.d(5): Error: struct libA.Opaque unknown size
libA.d(5): Error: struct libA.Opaque no size yet for forward reference

Can someone please tell me the right way to pass an opaque object between module's functions.

--
Thanks,
Sarath
January 23, 2013
On 01/23/2013 08:33 AM, Sarath Kumar wrote:

> Can someone please tell me the right way to pass an opaque object
> between module's functions.

I am assuming that you are interfacing with a C library. That library must have a D binding file. I am assuming that it is your libA.d:

// libA.d
module libA;

extern (C)
{
        struct Opaque;

        Opaque* getObject();

        void doSomething(Opaque *);
}

There is also a library that implements the struct and the functions. Although you would have it in the C library, here is a D module to imitate it:

// libA_impl.d
// These are presumably defined in a C library. Simply imitating it with this
// D module
extern (C)
{
    struct Opaque
    {
        int i;
        double d;
    }

    Opaque* getObject()
    {
        return new Opaque(42, 1.5);
    }

    void doSomething(Opaque *)
    {}
}

Here is the D module that makes use of that library by importing its D binding:

// libB.d
module libB;

import libA;

void doAction(Opaque* o)
{
    doSomething(o);
}

void main()
{
    Opaque* o = getObject();
    doAction(o);
}

Here is how to build the program:

$ dmd libA.d libB.d libA_impl.d -ofmy_prog

Note that normally libA_impl.d would not be included. Instead, the C library would be used. For example, if we are talking about a libmy_c_lib.a:

$ dmd libA.d libB.d -L-lmy_c_lib -ofmy_prog

Yes, with the confusing -L in there. ;)

Ali

January 24, 2013
On Wednesday, 23 January 2013 at 16:33:08 UTC, Sarath Kumar wrote:
> DMD v2.61; openSUSE 12.1
>
> Source:
> -------
> libA.d:
>
> module libA;
>
> extern (C)
> {
>         struct Opaque;
>
>         Opaque* getObject();
>
>         void doSomething(Opaque *);
> }
> ----------
> libB.d:
>
> module libB;
>
> extern (C)
> {
>         struct Opaque;
>
>         void doAction(Opaque *);
> }
> -----------
> bug.d
>
> import libA, libB;
>
> int main(string[] args)
> {
>         auto opaque = libA.getObject();
>         libA.doSomething(opaque); // this is okay
>         libB.doAction(opaque);  // but this is not, compiler error here
>         return 0;
> }
>
> When I compile the above files, I get the below error.
> $ rdmd bug.d
> bug.d(7): Error: function libB.doAction (Opaque*) is not callable using argument types (Opaque*)
> bug.d(7): Error: cannot implicitly convert expression (opaque) of type Opaque* to Opaque*
>
> If I do an explicit cast, libB.Opaque*, for opaque in line 7, bug.d, I get completely different set of errors.
>
> $ rdmd bug.d
> libB.d(5): Error: struct libB.Opaque unknown size
> libB.d(5): Error: struct libB.Opaque no size yet for forward reference
> libB.d(5): Error: struct libB.Opaque unknown size
> libB.d(5): Error: struct libB.Opaque no size yet for forward reference
> libA.d(5): Error: struct libA.Opaque unknown size
> libA.d(5): Error: struct libA.Opaque no size yet for forward reference
>
> Can someone please tell me the right way to pass an opaque object between module's functions.

The error message here is deceiving. Declare the struct in one module only and then import it in every module that uses it. So, for example, keep the declaration in libA, remove it from libB and in libB add "import libA;".
January 24, 2013
On Wednesday, 23 January 2013 at 17:14:31 UTC, Ali Çehreli wrote:
> On 01/23/2013 08:33 AM, Sarath Kumar wrote:
>
> > Can someone please tell me the right way to pass an opaque
> object
> > between module's functions.
>
> I am assuming that you are interfacing with a C library. That library must have a D binding file. I am assuming that it is your libA.d:
>
>
> There is also a library that implements the struct and the functions. Although you would have it in the C library, here is a D module to imitate it:
>
> // libA_impl.d
> // These are presumably defined in a C library. Simply imitating it with this
> // D module
> extern (C)
> {
>     struct Opaque
>     {
>         int i;
>         double d;
>     }
>
>     Opaque* getObject()
>     {
>         return new Opaque(42, 1.5);
>     }
>
>     void doSomething(Opaque *)
>     {}
> }
>

I don't know the contents of the struct as libA and libB are 3rd party C libraries.

January 24, 2013
On Thursday, 24 January 2013 at 09:04:09 UTC, Mike Parker wrote:
>
> The error message here is deceiving. Declare the struct in one module only and then import it in every module that uses it. So, for example, keep the declaration in libA, remove it from libB and in libB add "import libA;".

I can't import libA into libB. So, I had moved the forward declaration into a separate D file and did a public import into libA and libB. This works. But this is a workaround. I will file a bug.

--
Thanks,
Sarath
January 24, 2013
On Thursday, 24 January 2013 at 09:39:39 UTC, Sarath Kumar wrote:
> On Thursday, 24 January 2013 at 09:04:09 UTC, Mike Parker wrote:
>>
>> The error message here is deceiving. Declare the struct in one module only and then import it in every module that uses it. So, for example, keep the declaration in libA, remove it from libB and in libB add "import libA;".
>
> I can't import libA into libB. So, I had moved the forward declaration into a separate D file and did a public import into libA and libB. This works. But this is a workaround. I will file a bug.
>

The only potential bug I see here is in the error message. What you're seeing is a conflict that arises from D's name mangling. The doSomething in libA is expecting a parameter of type libA.Opaque* and getObject is returning the same, whereas the functions in libB are expecting a parameter of type libB.Opaque*. You can see this if you do the following:

writeln(doSomething.mangleof);
writeln(doAction.mangleof);

This is why your code is failing when you call getAction, because you're passing it a libA.Opaque* returned from libA.getObject. The solution is to declare opaque struct in a single place and import it everywhere you need it. That's why your public imports work. I would suggest that you not use the public import in this case, but work out some other scenario. In Derelict (a collection of C bindings) for example, I tend to follow this pattern for any lib a bind to:

types.d <- all type & constant declarations go here
functions.d <- all function declarations go here, imports the types module

Then I have a module named according to the C library's primary header that publicly imports both. For SDL2, for example:

module derelict.sdl2.sdl;

public {
    import derelict.sdl2.types;
    import derelict.sdl2.functions;
}

This way, all types are declared in a single location so there's no danger of mixing up different mangles of the same type.
January 24, 2013
On Wednesday, 23 January 2013 at 16:33:08 UTC, Sarath Kumar wrote:
> DMD v2.61; openSUSE 12.1
>
> Source:
> -------
> libA.d:
>
> module libA;
>
> extern (C)
> {
>         struct Opaque;
>
>         Opaque* getObject();
>
>         void doSomething(Opaque *);
> }
> ----------
> libB.d:
>
> module libB;
>
> extern (C)
> {
>         struct Opaque;
>
>         void doAction(Opaque *);
> }
> -----------
> bug.d
>
> import libA, libB;
>
> int main(string[] args)
> {
>         auto opaque = libA.getObject();
>         libA.doSomething(opaque); // this is okay
>         libB.doAction(opaque);  // but this is not, compiler error here
>         return 0;
> }
>
> When I compile the above files, I get the below error.
> $ rdmd bug.d
> bug.d(7): Error: function libB.doAction (Opaque*) is not callable using argument types (Opaque*)
> bug.d(7): Error: cannot implicitly convert expression (opaque) of type Opaque* to Opaque*
>
> If I do an explicit cast, libB.Opaque*, for opaque in line 7, bug.d, I get completely different set of errors.
>
> $ rdmd bug.d
> libB.d(5): Error: struct libB.Opaque unknown size
> libB.d(5): Error: struct libB.Opaque no size yet for forward reference
> libB.d(5): Error: struct libB.Opaque unknown size
> libB.d(5): Error: struct libB.Opaque no size yet for forward reference
> libA.d(5): Error: struct libA.Opaque unknown size
> libA.d(5): Error: struct libA.Opaque no size yet for forward reference
>
> Can someone please tell me the right way to pass an opaque object between module's functions.
>
> --
> Thanks,
> Sarath

You have hit one of the D problems regarding extern(). The first problem is that extern(C)-declared objects inside functions are still mangled, the other problem is that extern(C) Type var; still creates var object, unlike C does (except when Type is a function pointer).

The following D program is compiled and linked:

extern(C) int i;

void main()
{
	i = 0;
}

However, a C equivalent would result in linking error due to unresolved i reference.

So, what you are really doing is unusable. By the way, doing this way you are actually defining two different structs, libA.Opaque and libB.Opaque. Compiler is right in this case, because these types are incompatible, but it should print full names to show the difference explicitly.

If you are working with C library, you can use following scheme:
------main.d------------
extern extern(C) struct Opaque;

extern extern(C) Opaque* getObject();


void main()
{
	Opaque* ptr = getObject();
}
-----struct.c-----------
struct Opaque
{
	int i;
	double d;
};

struct Opaque op;

struct Opaque* getObject()
{
	return &op;
}
------------------------

and if you are using D code, you should use .di files.
January 24, 2013
On Thursday, 24 January 2013 at 11:52:57 UTC, Mike Parker wrote:
>
> The only potential bug I see here is in the error message. What you're seeing is a conflict that arises from D's name mangling. The doSomething in libA is expecting a parameter of type libA.Opaque* and getObject is returning the same, whereas the functions in libB are expecting a parameter of type libB.Opaque*.

  It is a bug because even after casting, the compiler gives an error. The compiler wants to know the size of the opaque object, even though I'm dealing with a pointer only. As Maxim Fomin mentioned, the compiler is trying to create an instance of the object even when it is declared as extern (C) and it is a pointer.

--
Thanks,
Sarath