www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Unexpected behavior when using both alias this and object pointer

reply xiren7 <nirvana117 gmail.com> writes:
The problem and the code:

import std.stdio: writeln;
import core.stdc.stdlib: malloc;

struct Impl {
     ubyte[8] payload;
}

class Foo {
     Impl *impl;
     alias impl this;

     this()
     {
         impl = cast(Impl*) malloc(Impl.sizeof);
     }
}

class Foo2 {
     ubyte[8] payload;
}

void main()
{
     // alias T = Foo2;
     alias T = Foo;

     auto t = new T();

     // what I want to do is:
     // 1. cast t as pointer
     // 2. passe the pointer to a extern(C) callback function
     // 3. cast the pointer back to t in extern(C) callback 
function
     // 4. accesse the payload field
     auto payload = (cast(T) cast(void*) t).payload; // -> crashs

     // the right way to get the address of the t object
     writeln(*cast(void**) &t);          // -> 278E3373000

     // the unexpected behavior
     // the obvious(but wrong) way to get the address of the t 
object
     writeln(cast(void*) t);             // -> 278E164DAB0

     // because of alias this
     // cast(void*) t == cast(void*) t.payload
     writeln(cast(void*) t.payload);     // -> 278E164DAB0
}

My question is should let the compiler generate a warning about 
this unexpected(maybe) behavior?
Jan 12 2017
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
Hiding a Foo right after Impl can be a solution. However, you need to 
pass 't', not '&t' to the C function because

- Although it may be unexpected, cast(void*) is the specified way of 
getting the address of a class object

- Taking the address of a class reference (which 't' is one), is just 
the address of the reference itself

So, you either have to do something similar to the following or pass 
void* to the C function.

import std.stdio: writeln;
import core.stdc.stdlib: malloc;

struct Impl {
     ubyte[8] payload;
}

struct ImplWithOwner {
     Impl impl;
     Foo owner;
}

class Foo {
     ImplWithOwner *io;

     Impl *payload() {
     // Guaranteed by D for structs:
     assert(cast(void*)io == cast(void*)&io.impl);
         return &io.impl;
     }

     alias payload this;

     this() {
         io = cast(ImplWithOwner*) malloc(Impl.sizeof);
         io.owner = this;
     }
}

static Foo asFoo(Impl* p) {
     return (cast(ImplWithOwner*)p).owner;
}

extern(C)
void actual_C_function(Impl* data, void function(Impl*) callback) {
     data.payload[0] = 42;
     callback(data);
}

extern(C)
void myCallback(Impl* p) {
     auto foo = p.asFoo;
     assert(foo.io.impl.payload[0] == 42);
}

void main() {
     auto t = new Foo();
     actual_C_function(t, &myCallback);
}

Ali
Jan 12 2017
parent xiren7 <nirvana117 gmail.com> writes:
Thanks. Ali.
My previous post is not clear that I have to store class 
reference(object pointer) in void*.

My actual code is try to use libuv in D.

// genarated from uv.h, only two fields is used: 'data', 'type'.
// the document of 'data': "Space for user-defined arbitrary 
data. libuv does not use this field".
// uv_timer_t is the subclass of uv_handle_t in C. (uv_timer_t 
has all the fields defined in uv_handle_t)
struct uv_handle_t {align(8):
     union {
         struct {void* data; void* _; uv_handle_type type;}
         ubyte[96] _payload;
     }
}
struct uv_timer_t {align(8):
     union {
         struct {void* data; void* _; uv_handle_type type;}
         ubyte[160] _payload;
     }
}
...

// try to resemble the libuv object defined in C.
struct UVHandle {
     uv_handle_t _uvHandle;
     // subclass uv_handle_t
     alias _uvHandle this;

     void close() { // uv_close(&this) }
     bool isClosing() { // uv_is_closing(&this) }
     ...
}

// define Timer as 'final class', in order to force Timer 
allocated in gc.
final class Timer {
     UVHandle* uvHandle
     // subclass UVHandle
     alias uvHandle this;
     ...

     this()
     {
         // malloc memory(nogc) for uv_timer_t.
         uvHandle = malloc(uv_timer_t.sizeof)

         // store the timer object reference in 'data',
         // but 'alias uvHandle this' also overrides 'cast',
         // this is the problem described as my previous post.
         uvHandle.data = cast(void*) this;
     }
     ~this() { free(uvHandle); }

     void start(...) { // uv_timer_start(this.uvHandle, ...) }
     void stop() { // uv_timer_stop(this.uvHandle) }
     ...
}

My current solution is using 'mixin template' instead of 'alias 
this' as the way to subclass:

mixin template UVHandle() { ... }
public final class Timer {
     mixin UVHandle;
     ...
}
Jan 12 2017