View mode: basic / threaded / horizontal-split · Log in · Help
July 08, 2007
version and extern problems
Bug 1311 http://d.puremagic.com/issues/show_bug.cgi?id=1311
is about using version declarations to control part of a following 
declaration (or series of declarations):

-------------------------
version(Windows)
    extern(Windows):
else
    extern(C):

typedef void function() foo;
--------------------------

This does not work now, and it was a bug that it ever did appear to 
work. The issue is that version declarations can only affect entire 
declarations, not parts of them. An extern statement with a : is 
implicitly the same as:

----------------
extern(Windows):
int a;
int b;
-----is same as---
extern(Windows)
{
   int a;
   int b;
}
-----------------

That cannot be split up with a version declaration; it would be like 
trying to make:

	version (Windows)
		int
	else
		long
	x;

work. The old behavior of dmd contained a serious (unreported) bug where 
such constructs would cause forward references to behave unpredictably.

So, the question is how to get the same effect? The alternatives are:

1.
version(Windows)
{
    extern(Windows):
       typedef void function() foo;
}
else
{
    extern(C):
       typedef void function() foo;
}

Yes, that means doing a cut & paste on the code in the braces. Not 
thrilling, but it works.

2. Stop using extern(Windows). The Windows calling convention is only 
necessary when a) calling Win32 API functions (which don't exist on 
Linux anyway) and b) calling someone else's C code that pointlessly and 
spuriously uses the Windows calling convention, and cannot be fixed.
There is no reason in new C/C++ code to ever use the Windows calling 
convention.

3. Create two source files, one for the extern(Windows) and the other 
for the extern(C). Have your makefile automatically copy one to the 
other, using sed to edit that one line. Import one under Windows, the 
other under Linux.

4. Wait for the future 2.0 macro feature, which should be able to deal 
with this nicely:

macro Foo()
{
    typedef void function() foo;
}
version(Windows)
{   extern(Windows): Foo(); }
else
{    extern(C): Foo(); }

5. Do the same as (4) using string mixins:

const string Foo =
"
    typedef void function() foo;
";
version(Windows)
{   extern(Windows): mixin(Foo); }
else
{    extern(C): mixin(Foo); }


6. Use template mixins:

template Foo()
{
    typedef void function() foo;
}
version(Windows)
{   extern(Windows): mixin Foo; }
else
{    extern(C): mixin Foo; }
July 08, 2007
Re: version and extern problems
Walter Bright wrote:
> Bug 1311 http://d.puremagic.com/issues/show_bug.cgi?id=1311
> is about using version declarations to control part of a following 
> declaration (or series of declarations):
> 
> -------------------------
> version(Windows)
>     extern(Windows):
> else
>     extern(C):
> 
> typedef void function() foo;
> --------------------------
> 
> This does not work now, and it was a bug that it ever did appear to 
> work. The issue is that version declarations can only affect entire 
> declarations, not parts of them. An extern statement with a : is 
> implicitly the same as:
> 
> ----------------
> extern(Windows):
> int a;
> int b;
> -----is same as---
> extern(Windows)
> {
>    int a;
>    int b;
> }
> -----------------

Right. But since version doesn't create a new scope, what's the problem?

> 
> That cannot be split up with a version declaration; it would be like 
> trying to make:
> 
>     version (Windows)
>         int
>     else
>         long
>     x;
> 
> work. The old behavior of dmd contained a serious (unreported) bug where 
> such constructs would cause forward references to behave unpredictably.

Then I misunderstood the usage of version. I was under the impression 
that it's a form of conditional compilation, i.e. on Windows the final 
result would be

int x;

Is that not possible?

> 
> So, the question is how to get the same effect? The alternatives are:
> 
> 1.
> version(Windows)
> {
>     extern(Windows):
>        typedef void function() foo;
> }
> else
> {
>     extern(C):
>        typedef void function() foo;
> }
> 
> Yes, that means doing a cut & paste on the code in the braces. Not 
> thrilling, but it works.

I implemented it as I did to avoid this. Using multiple declarations is 
error prone and a maintenance nightmare, particularly for the number of 
declarations I'm dealing with.

> 
> 2. Stop using extern(Windows). The Windows calling convention is only 
> necessary when a) calling Win32 API functions (which don't exist on 
> Linux anyway) and b) calling someone else's C code that pointlessly and 
> spuriously uses the Windows calling convention, and cannot be fixed.
> There is no reason in new C/C++ code to ever use the Windows calling 
> convention.

Unfortunately, I have no choice. This is not new D code, but bindings to 
existing cross-platform C libraries (namely OpenGL and DevIL). On 
Windows, these libraries all use the stdcall calling convention. 
Declaring them as extern(C) causes crashes. On other platforms, the 
libraries use the C calling convention, so they can't be declared 
extern(Windows) there.

Every D OpenGL binding I have seen uses this technique. That's how I 
first learned it. For example, all of Kenta Cho's D games 
(http://www.asahi-net.or.jp/~cs8k-cyu/index_e.html) use an OpenGL module 
with this at the top:

version (Win32) {
	private import std.c.windows.windows;
	extern(Windows):
}
else {
	extern(C):
}

followed by the function declarations. Most of his code already won't 
compile on newer versions without modification anyway. But still, now 
all of the D OpenGL bindings are broken.

> 
> 3. Create two source files, one for the extern(Windows) and the other 
> for the extern(C). Have your makefile automatically copy one to the 
> other, using sed to edit that one line. Import one under Windows, the 
> other under Linux.

Yuck! :)

> 
> 4. Wait for the future 2.0 macro feature, which should be able to deal 
> with this nicely:
> 
> macro Foo()
> {
>     typedef void function() foo;
> }
> version(Windows)
> {   extern(Windows): Foo(); }
> else
> {    extern(C): Foo(); }

That will be nice when it gets here, but there are already a lot of 
people using this library who aren't going to be willing to wait.

> 
> 5. Do the same as (4) using string mixins:
> 
> const string Foo =
> "
>     typedef void function() foo;
> ";
> version(Windows)
> {   extern(Windows): mixin(Foo); }
> else
> {    extern(C): mixin(Foo); }
> 
> 
> 6. Use template mixins:
> 
> template Foo()
> {
>     typedef void function() foo;
> }
> version(Windows)
> {   extern(Windows): mixin Foo; }
> else
> {    extern(C): mixin Foo; }

It looks like one of these is going to be the best alternative. More 
typing, but at least it keeps the declarations in one place.
July 08, 2007
Re: version and extern problems
Walter Bright wrote:
> Bug 1311 http://d.puremagic.com/issues/show_bug.cgi?id=1311
> is about using version declarations to control part of a following 
> declaration (or series of declarations):
> 
> -------------------------
> version(Windows)
>     extern(Windows):
> else
>     extern(C):
> 
> typedef void function() foo;
> --------------------------
> 
> This does not work now, and it was a bug that it ever did appear to 
> work. The issue is that version declarations can only affect entire 
> declarations, not parts of them. An extern statement with a : is 
> implicitly the same as:
> 
> ----------------
> extern(Windows):
> int a;
> int b;
> -----is same as---
> extern(Windows)
> {
>    int a;
>    int b;
> }
> -----------------


This seems like a real loss for the convenience of D.  One of the assets 
of the D language is that it makes a lot of things simple.  The example 
in the bug is fairly intuitive and easy to write.


> That cannot be split up with a version declaration; it would be like 
> trying to make:
> 
>     version (Windows)
>         int
>     else
>         long
>     x;
> 
> work. The old behavior of dmd contained a serious (unreported) bug where 
> such constructs would cause forward references to behave unpredictably.

This restriction seems reasonable.  It'd be really, really tough to 
handle.  In my mind, the issue is that the : operator might make sense 
if the attribute: only applied to it's contained scope.  In this case, 
however, that doesn't really make sense, because version statements 
don't really have their own scope...  Otherwise, variable definitions 
inside of them wouldn't be available externally.
July 08, 2007
Re: version and extern problems
"Mike Parker" <aldacron71@yahoo.com> wrote in message 
news:f6pr67$2t00$1@digitalmars.com...
>
> Then I misunderstood the usage of version. I was under the impression that 
> it's a form of conditional compilation, i.e. on Windows the final result 
> would be
>
> int x;
>
> Is that not possible?

No.  It's a form of conditional compilation, certainly, but it's not just 
like #ifdef.  Everything within a version block must be syntactically valid; 
"int" is not syntactically valid, so you can't put it in the version block.
July 08, 2007
Re: version and extern problems
"Jason House" <jason.james.house@gmail.com> wrote in message 
news:f6qq5d$1s0u$1@digitalmars.com...
>
> This seems like a real loss for the convenience of D.  One of the assets 
> of the D language is that it makes a lot of things simple.  The example in 
> the bug is fairly intuitive and easy to write.
>

I agree.  I think it's funny that none of the proposed solutions are 
anywhere near as simple or intuitive as the old, incorrect syntax.  Perhaps 
this is telling.
July 08, 2007
Re: version and extern problems
Should this work?

version(Windows)
{   const char[] external = "extern(Windows):"; }
else
{   const char[] external = "extern(C):"; }

mixin(external);
typedef void function() foo;

Walter Bright wrote:
> Bug 1311 http://d.puremagic.com/issues/show_bug.cgi?id=1311
> is about using version declarations to control part of a following 
> declaration (or series of declarations):
> 
> -------------------------
> version(Windows)
>     extern(Windows):
> else
>     extern(C):
> 
> typedef void function() foo;
> --------------------------
> 
> This does not work now, and it was a bug that it ever did appear to 
> work. The issue is that version declarations can only affect entire 
> declarations, not parts of them. An extern statement with a : is 
> implicitly the same as:
> 
> ----------------
> extern(Windows):
> int a;
> int b;
> -----is same as---
> extern(Windows)
> {
>    int a;
>    int b;
> }
> -----------------
> 
> That cannot be split up with a version declaration; it would be like 
> trying to make:
> 
>     version (Windows)
>         int
>     else
>         long
>     x;
> 
> work. The old behavior of dmd contained a serious (unreported) bug where 
> such constructs would cause forward references to behave unpredictably.
> 
> So, the question is how to get the same effect? The alternatives are:
> 
> 1.
> version(Windows)
> {
>     extern(Windows):
>        typedef void function() foo;
> }
> else
> {
>     extern(C):
>        typedef void function() foo;
> }
> 
> Yes, that means doing a cut & paste on the code in the braces. Not 
> thrilling, but it works.
> 
> 2. Stop using extern(Windows). The Windows calling convention is only 
> necessary when a) calling Win32 API functions (which don't exist on 
> Linux anyway) and b) calling someone else's C code that pointlessly and 
> spuriously uses the Windows calling convention, and cannot be fixed.
> There is no reason in new C/C++ code to ever use the Windows calling 
> convention.
> 
> 3. Create two source files, one for the extern(Windows) and the other 
> for the extern(C). Have your makefile automatically copy one to the 
> other, using sed to edit that one line. Import one under Windows, the 
> other under Linux.
> 
> 4. Wait for the future 2.0 macro feature, which should be able to deal 
> with this nicely:
> 
> macro Foo()
> {
>     typedef void function() foo;
> }
> version(Windows)
> {   extern(Windows): Foo(); }
> else
> {    extern(C): Foo(); }
> 
> 5. Do the same as (4) using string mixins:
> 
> const string Foo =
> "
>     typedef void function() foo;
> ";
> version(Windows)
> {   extern(Windows): mixin(Foo); }
> else
> {    extern(C): mixin(Foo); }
> 
> 
> 6. Use template mixins:
> 
> template Foo()
> {
>     typedef void function() foo;
> }
> version(Windows)
> {   extern(Windows): mixin Foo; }
> else
> {    extern(C): mixin Foo; }
July 08, 2007
Re: version and extern problems
Mike Wey wrote:
> Should this work?
> 
> version(Windows)
> {   const char[] external = "extern(Windows):"; }
> else
> {   const char[] external = "extern(C):"; }
> 
> mixin(external);
> typedef void function() foo;
> 

DMD 1.018 accepts that.  But maybe that's a bug too, since string mixins 
are supposed to be either an expression, a statement, or a declaration 
or definition?  According to the spec, it seems to be a valid 
AttributeSpecifier, which in turn is a valid DeclDef.  But Walter didn't 
suggest it as a workaround, which makes me worried.
July 08, 2007
Re: version and extern problems
torhu wrote:
> Mike Wey wrote:
>> Should this work?
>> 
>> version(Windows)
>> {   const char[] external = "extern(Windows):"; }
>> else
>> {   const char[] external = "extern(C):"; }
>> 
>> mixin(external);
>> typedef void function() foo;
>> 
> 
> DMD 1.018 accepts that.  But maybe that's a bug too, since string mixins 
> are supposed to be either an expression, a statement, or a declaration 
> or definition?  According to the spec, it seems to be a valid 
> AttributeSpecifier, which in turn is a valid DeclDef.  But Walter didn't 
> suggest it as a workaround, which makes me worried.

That the mixing trick works might be as bad as that this works:

---
version (Windows)
    extern (Windows):
---


Being able to insert 'extern (Windows):' using a string mixin seems 
equally bad to me.  Where's the important difference?
July 08, 2007
Re: version and extern problems
Walter Bright wrote:
> > So, the question is how to get the same effect? The alternatives are:
> > 
> > 1.
> > version(Windows)
> > {
> >      extern(Windows):
> >         typedef void function() foo;
> > }
> > else
> > {
> >      extern(C):
> >         typedef void function() foo;
> > }
> > 
> > Yes, that means doing a cut & paste on the code in the braces. Not 
> > thrilling, but it works.
> > 
> > 2. Stop using extern(Windows). The Windows calling convention is only 
> > necessary when a) calling Win32 API functions (which don't exist on 
> > Linux anyway) and b) calling someone else's C code that pointlessly and 
> > spuriously uses the Windows calling convention, and cannot be fixed.
> > There is no reason in new C/C++ code to ever use the Windows calling 
> > convention.
> > 
> > 3. Create two source files, one for the extern(Windows) and the other 
> > for the extern(C). Have your makefile automatically copy one to the 
> > other, using sed to edit that one line. Import one under Windows, the 
> > other under Linux.
> > 
> > 4. Wait for the future 2.0 macro feature, which should be able to deal 
> > with this nicely:
> > 
> > macro Foo()
> > {
> >      typedef void function() foo;
> > }
> > version(Windows)
> > {   extern(Windows): Foo(); }
> > else
> > {    extern(C): Foo(); }
> > 
> > 5. Do the same as (4) using string mixins:
> > 
> > const string Foo =
> > "
> >      typedef void function() foo;
> > ";
> > version(Windows)
> > {   extern(Windows): mixin(Foo); }
> > else
> > {    extern(C): mixin(Foo); }
> > 
> > 
> > 6. Use template mixins:
> > 
> > template Foo()
> > {
> >      typedef void function() foo;
> > }
> > version(Windows)
> > {   extern(Windows): mixin Foo; }
> > else
> > {    extern(C): mixin Foo; }

I don't like any of these solutions.  This is so straight forward with
the C preprocessor.  Even using the future macro feature means
duplicating definitions.

AFAIK, these problems only affect the linkage attributes, meaning
extern (C), extern (Windows), and export.

If Mike Wey's string mixin solution is allowed in future dmd versions, I
guess the situation isn't that bad.  As long as bug 1306 is fixed, anyway.

Another way would be to accept that this is a special case that needs a
pragmatic solution, and expand the syntax a bit:

extern (Windows, C):

According to the docs, A D compiler only need to support Windows and
Pascal conventions on Windows.  So on other platforms, the compiler
could just pick the first valid linkage for that platform.  A version ID
could be incorporated too, if there's ever a need for it:

extern (Windows : Windows; WeirdPlatform: WeirdLinkage; C):


The problem with 'export' is related.  I wonder if something similar
could work for that.  Make it conditional somehow:

export (DLLVERSION) int someVar;
export (version DLLVERSION) int someVar;
version (DLLVERSION)(export) int someVar;

The third version has the advantage of being applicable to other cases too:

version (Windows) (extern (Windows)) else (extern (C)) {
    /* openGL prototypes */
}


I don't know if this syntax is feasible, I'll leave that to the experts. 
 :)
July 08, 2007
Re: version and extern problems
Mike Wey wrote:
> Should this work?
> 
> version(Windows)
> {   const char[] external = "extern(Windows):"; }
> else
> {   const char[] external = "extern(C):"; }
> 
> mixin(external);
> typedef void function() foo;

No, it doesn't work (although it compiles).
« First   ‹ Prev
1 2 3 4 5
Top | Discussion index | About this forum | D home