www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Should this always work?

reply frame <frame86 live.com> writes:
I always thought as long as an object implements an interface, it 
should be able to cast it from a void* if it really points to a 
supporting object.

I have the similar structure:


```d
interface AI {
     string doSomething();
}

template S() {
     void foo() {

     }
}

abstract class A : AI {
     string doSomething() {
         return "Hello, World";
     }
}

class B : A {
     mixin S;

     void other() {

     }
}

auto b = new B;
auto p = cast(void*) b;
auto c = cast(AI) p;

c.doSomething();
```

But in my code with the real object, this generates a RangeError, 
AcccesError, memory garbage:
```d
auto b = new B;
auto p = cast(void*) b;
auto c = cast(AI) p; // AI with corrupt data

c.doSomething(); // error
```

But this works:
```d
auto b = new B;
auto p = cast(void*) b;
auto c = cast(A) p; // A with correct data

c.doSomething(); // no error
```

If the runtime could not successfully cast it to AI, it should 
return null. Am I wrong here?
Apr 30 2021
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Saturday, 1 May 2021 at 04:55:10 UTC, frame wrote:
 I always thought as long as an object implements an interface, 
 it should be able to cast it from a void* if it really points 
 to a supporting object.
No. An interface is like a pointer to a pointer. So to get to the class, you have to go one more level of indirection. ```d import std.stdio; interface I { void doIt(); } class A : I { void doIt() { writeln("Doing it"); } } void main() { I i = new A; void** pi = cast(void**)i; A a = cast(A)(*pi); a.doIt(); } ```
 If the runtime could not successfully cast it to AI, it should 
 return null. Am I wrong here?
That only works when you're casting one class/interface reference to another. It doesn't work when you're casting raw pointers.
Apr 30 2021
next sibling parent reply frame <frame86 live.com> writes:
On Saturday, 1 May 2021 at 06:17:36 UTC, Mike Parker wrote:
 That only works when you're casting one class/interface 
 reference to another. It doesn't > > work when you're casting 
 raw pointers.
Thanks for clarification. In case of a void* to abstract class casting, I assume the compiler just inserts the right vtable, so this cast is unproblematic - or is there also a pitfall?
May 01 2021
parent reply Mike Parker <aldacron gmail.com> writes:
On Saturday, 1 May 2021 at 08:03:45 UTC, frame wrote:

 In case of a void* to abstract class casting, I assume the 
 compiler just inserts the right vtable, so this cast is 
 unproblematic - or is there also a pitfall?
I wouldn't expect there to be any consideration of vtables whatsoever. The compiler just reinterprets the pointer as a class reference, whether you're casting it to the original class or a superclass type. I mean, all of the functions available to the class are already in the vtable. There's no need to insert anything. Whether you're getting at a class instance through a subclass (C) reference or a superclass (A) reference, abstract or not, it's still only a single instance located at a single address. ```d import std.stdio; abstract class A { void doIt(); } class C : A { override void doIt() { writeln("Doing it"); } } void main() { C c = new C; void* pc = cast(void*)c; A a = cast(A)pc; a.doIt(); } ``` So the pitfall is the same: when you're casting class references to and from pointers, it's up to you to ensure that the type you cast to is appropriate. You get no help from the compiler here.
May 01 2021
parent reply frame <frame86 live.com> writes:
On Saturday, 1 May 2021 at 10:08:17 UTC, Mike Parker wrote:

 So the pitfall is the same: when you're casting class 
 references to and from pointers, it's up to you to ensure that 
 the type you cast to is appropriate. You get no help from the 
 compiler here.
I see, this can be dangerous - yet, the only way in my application (DLL problem). btw: why is it even allowed to create an instance via cast as an abstract thing?
May 01 2021
parent reply Mike Parker <aldacron gmail.com> writes:
On Saturday, 1 May 2021 at 15:21:28 UTC, frame wrote:
 btw: why is it even allowed to create an instance via cast as 
 an abstract thing?
No instance is being created by the cast. The instance already exists. The cast just allows you to treat the pointer to the instance as a specific type. It’s no different than casting from C to A really. You just have the void* as an intermediary.
May 01 2021
parent frame <frame86 live.com> writes:
On Saturday, 1 May 2021 at 15:52:52 UTC, Mike Parker wrote:

 No instance is being created by the cast. The instance already 
 exists. The cast just allows you to treat the pointer to the 
 instance as a specific type. It’s no different than casting 
 from C to A really. You just have the void* as an intermediary.
Yes, I know. In fact, this was a stupid question. Not sure what I was thinking off. I sometimes mix up languages in my head and came to weird conclusions :P On Saturday, 1 May 2021 at 16:06:05 UTC, Steven Schveighoffer wrote:
 An interface cast involves a thunk (constant pointer 
 adjustment) to get to the interface/object. The reason is 
 because a class with interfaces stores interface vtable 
 pointers inside the object, and your interface reference points 
 at that. You can see when you cast between Object (concrete) 
 type and Interface type, the pointer value changes.

 So this will not work. It *does* work for base classes, because 
 the class vtable pointer is stored at same point, and casting 
 around class references does not involve a thunk.
This was helpful to understand the ABI chapter better. Thanks, guys!
May 01 2021
prev sibling parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Saturday, 1 May 2021 at 06:17:36 UTC, Mike Parker wrote:
 On Saturday, 1 May 2021 at 04:55:10 UTC, frame wrote:
 I always thought as long as an object implements an interface, 
 it should be able to cast it from a void* if it really points 
 to a supporting object.
No. An interface is like a pointer to a pointer.
Can you elaborate on this one? I don't really get it. Is an object handle also like a pointer to a pointer? (I feel like I can learn something here.)
May 03 2021
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Tuesday, 4 May 2021 at 01:20:15 UTC, Q. Schroll wrote:
 On Saturday, 1 May 2021 at 06:17:36 UTC, Mike Parker wrote:
 On Saturday, 1 May 2021 at 04:55:10 UTC, frame wrote:
 I always thought as long as an object implements an 
 interface, it should be able to cast it from a void* if it 
 really points to a supporting object.
No. An interface is like a pointer to a pointer.
Can you elaborate on this one? I don't really get it. Is an object handle also like a pointer to a pointer? (I feel like I can learn something here.)
Off the top of my head the object layout is something like this: { pointer to vtable0 for the class; monitor mutex stuff; pointer to interface 1 vtable1; pointer to interface 2 vtable2; ... pointer to interface N vtableN; object data 1; object data 2; ... } A pointer to interface 1 is a pointer to the pointer to vtable1 in the object above.
May 04 2021
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/4/21 6:03 AM, Ola Fosheim Grøstad wrote:
 On Tuesday, 4 May 2021 at 01:20:15 UTC, Q. Schroll wrote:
 On Saturday, 1 May 2021 at 06:17:36 UTC, Mike Parker wrote:
 On Saturday, 1 May 2021 at 04:55:10 UTC, frame wrote:
 I always thought as long as an object implements an interface, it 
 should be able to cast it from a void* if it really points to a 
 supporting object.
No. An interface is like a pointer to a pointer.
Can you elaborate on this one? I don't really get it. Is an object handle also like a pointer to a pointer? (I feel like I can learn something here.)
Off the top of my head the object layout is something like this: { pointer to vtable0 for the class; monitor mutex stuff; pointer to interface 1 vtable1; pointer to interface 2 vtable2; ... pointer to interface N vtableN; object data 1; object data 2; ... } A pointer to interface 1 is a pointer to the pointer to vtable1 in the object above.
Yes, this is exactly how it is. See the ABI document: https://dlang.org/spec/abi.html#classes So a class reference is *also* a pointer to a pointer (to the vtable), and a interface reference is the same. However, the slight difference is, the pointer to the interface doesn't allow useful mechanisms until you apply the offset (necessitating a double indirection), whereas a class pointer (combined with compile-time type knowledge) the compiler can access member fields of the class. -Steve
May 04 2021
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On Tuesday, 4 May 2021 at 01:20:15 UTC, Q. Schroll wrote:
 On Saturday, 1 May 2021 at 06:17:36 UTC, Mike Parker wrote:
 No. An interface is like a pointer to a pointer.
Can you elaborate on this one? I don't really get it. Is an object handle also like a pointer to a pointer? (I feel like I can learn something here.)
So I have no deep knowledge of the implementation details. I only know that a class reference is a pointer under the hood, so it can be cast to `void*` and back, and that an interface reference has an extra level of indirection, so it can go to `void**` and back.
May 04 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/1/21 12:55 AM, frame wrote:
 I always thought as long as an object implements an interface, it should 
 be able to cast it from a void* if it really points to a supporting object.
 
 I have the similar structure:
 
 
 ```d
 interface AI {
     string doSomething();
 }
 
 template S() {
     void foo() {
 
     }
 }
 
 abstract class A : AI {
     string doSomething() {
         return "Hello, World";
     }
 }
 
 class B : A {
     mixin S;
 
     void other() {
 
     }
 }
 
 auto b = new B;
 auto p = cast(void*) b;
 auto c = cast(AI) p;
 
 c.doSomething();
 ```
 
 But in my code with the real object, this generates a RangeError, 
 AcccesError, memory garbage:
 ```d
 auto b = new B;
 auto p = cast(void*) b;
 auto c = cast(AI) p; // AI with corrupt data
 
 c.doSomething(); // error
 ```
 
 But this works:
 ```d
 auto b = new B;
 auto p = cast(void*) b;
 auto c = cast(A) p; // A with correct data
 
 c.doSomething(); // no error
 ```
 
 If the runtime could not successfully cast it to AI, it should return 
 null. Am I wrong here?
An interface cast involves a thunk (constant pointer adjustment) to get to the interface/object. The reason is because a class with interfaces stores interface vtable pointers inside the object, and your interface reference points at that. You can see when you cast between Object (concrete) type and Interface type, the pointer value changes. So this will not work. It *does* work for base classes, because the class vtable pointer is stored at same point, and casting around class references does not involve a thunk. If you want this to work, you have to know whether the void* pointer is pointing at an object, or an interface. If you know it's pointing at an object, you can get to the interface via: auto c = cast(AI)cast(Object)p; Which will perform the appropriate thunks. If you know it's a pointer to the AI interface directly, you can just cast it directly. -Steve
May 01 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Saturday, 1 May 2021 at 16:06:05 UTC, Steven Schveighoffer 
wrote:
 An interface cast involves a thunk (constant pointer 
 adjustment) to get to the interface/object
Yes, but it isn't a https://en.wikipedia.org/wiki/Thunk ?
May 04 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 4 May 2021 at 10:21:42 UTC, Ola Fosheim Grøstad wrote:
 On Saturday, 1 May 2021 at 16:06:05 UTC, Steven Schveighoffer 
 wrote:
 An interface cast involves a thunk (constant pointer 
 adjustment) to get to the interface/object
Yes, but it isn't a https://en.wikipedia.org/wiki/Thunk ?
The article literally gives this exact use-case as an example: https://en.wikipedia.org/wiki/Thunk#Object-oriented_programming
May 04 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/4/21 9:21 AM, Paul Backus wrote:
 On Tuesday, 4 May 2021 at 10:21:42 UTC, Ola Fosheim Grøstad wrote:
 On Saturday, 1 May 2021 at 16:06:05 UTC, Steven Schveighoffer wrote:
 An interface cast involves a thunk (constant pointer adjustment) to 
 get to the interface/object
Yes, but it isn't a https://en.wikipedia.org/wiki/Thunk ?
The article literally gives this exact use-case as an example: https://en.wikipedia.org/wiki/Thunk#Object-oriented_programming
Yeah, I wasn't aware of the more general usage, I thought it was always a pointer adjustment. But I also am not steeped in the terminology, just parroting what I've heard. -Steve
May 04 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Tuesday, 4 May 2021 at 13:58:59 UTC, Steven Schveighoffer 
wrote:
 Yeah, I wasn't aware of the more general usage, I thought it 
 was always a pointer adjustment. But I also am not steeped in 
 the terminology, just parroting what I've heard.
My understanding is that a thunk is the code object that fetches the value for you, in this case there might not be a thunk involved as the interface code can just apply a fixed offset?
May 04 2021
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Tuesday, 4 May 2021 at 14:18:30 UTC, Ola Fosheim Grøstad wrote:
 On Tuesday, 4 May 2021 at 13:58:59 UTC, Steven Schveighoffer 
 wrote:
 Yeah, I wasn't aware of the more general usage, I thought it 
 was always a pointer adjustment. But I also am not steeped in 
 the terminology, just parroting what I've heard.
My understanding is that a thunk is the code object that fetches the value for you, in this case there might not be a thunk involved as the interface code can just apply a fixed offset?
I guess in D terms it can be best explained as a compiler internal "delegate". Let's take the example of getting the this-pointer: Say, you want to write generic code gen, then you can let that code gen take a "delegate" that computes a this-pointer rather than writing many different code gens for different layouts. Then you trust the optimizer to get rid of it, or keep it if need be. That delegate would be a thunk. It may or may not exist at run-time based on the optimizer/language semantics. You could also do something similar with an offset or switch statement. The point is, the code gen have no idea of how to obtain the this-pointer, the thunk does. It is a separate external code piece that obtains the value of the this-pointer.
May 04 2021