Opaque handles...

Adam D. Ruppe destructionator at gmail.com
Wed Sep 18 09:57:24 PDT 2013


On Wednesday, 18 September 2013 at 16:05:00 UTC, Manu wrote:
> This seems okay at first, but then you realise there are 
> serious problems with new (wants to return new memory, I can't 
> hook the new operator?),

There is an override for new, but it is deprecated in d2.
http://dlang.org/memory.html#newdelete

> I kinda hate this. the struct keyword totally gives the user 
> the wrong impression, and people have a natural instinct to 
> avoid passing structs around by value

...just don't do that? You don't write "ref Class obj" in D, so 
just pretend the struct is a class and do it the same way.

The only time you'd even need to know it is a struct is if you 
look at the source, and there you can write

struct /* but pretend it is a class! */ whatever {

}



Here's how I might do it. Given this simple C test header:

struct Test;

struct Test* makeTest(int num);
void addTestRef(struct Test* t);
void killTest(struct Test* t); // i should have called that 
releaseref lol
int getNumber(struct Test* t);
void setNumber(struct Test* t, int n);



We might use it in D like this:


// this represents the struct Test in C, our opaque pointer
// (the disabled stuff is to workaround a minor dmd bug)
struct c_Test {
	@disable this();
	@disable this(this);
}

// and this is our D wrapper
// always pass this by value
struct Test {
        // the internal reference
	private c_Test* c_ptr;

	// construction can only be done publicly through the static 
make method
	@disable this();
	private this(c_Test* ptr) {
		c_ptr = ptr;
	}

	public static Test make(int num) {
		return Test(makeTest(num));
	}

	// the alias this lets us call the rest of the binded C 
functions with UFCS without manually writing wrappers
	c_Test* getCPointer() { return c_ptr; } // it does a property so 
you can't assign to the internal pointer
	// you CAN break the refcounting with this, but since you have 
to specifically ask for the uglier c_Test* to trigger
	// this, it is unlikely to happen by accident.
	alias getCPointer this;

	// refcount stuff
	~this() {
		c_ptr.killTest();
	}
	this(this) {
		c_ptr.addTestRef();
	}
}

// you might notice the above is pretty generic, perhaps it could 
all be a single template so you don't rewrite it for too many 
types.

// and the C function prototypes
extern(C) {
	c_Test* makeTest(int num);
	void addTestRef(c_Test* t);
	void killTest(c_Test* t);

	int getNumber(c_Test* t);
	void setNumber(c_Test* t, int n);
}

// test program
import core.stdc.stdio;
void foo(Test t) {
	printf("foo on %d\n", t.getNumber());
	t.setNumber(20);
}

void main() {
	auto t = Test.make(12);
	auto t2 = Test.make(24);

	printf("about to call foo\n");
	foo(t);
	printf("done calling foo\n");

	printf("main with t  == %d\n", t.getNumber());
	printf("main with t2 == %d\n", t2.getNumber());
}




You don't have my c implementation but it isn't special and 
running the program produced the following output:

Test made with number 12
Test made with number 24
about to call foo
aref refcount now 2 on Test 12 # refcount increased properly for 
the function call
foo on 12
dtor refcount now 1 on Test 20 # and properly decreased when 
ending
done calling foo
main with t  == 20 # setting the number in foo() correctly did it 
by reference
main with t2 == 24
dtor refcount now 0 on Test 24 # and main is dead, so we release 
the ref automatically
Test killed with number 24 # refcount == 0, so C free()'d it
dtor refcount now 0 on Test 20
Test killed with number 20




Using UFCS for the C functions might be a little bit weird, but 
saves tediously writing them all out again to forward inside the 
struct.


More information about the Digitalmars-d mailing list