DEV Community

J N
J N

Posted on

D and C++: Passing dynamic strings

The easiest way to get a dynamically created string back out of C++ is to pass a char pointer reference (char *&) out parameter to the function we are calling. This allows us to malloc properly within C++ as we don't know the string length to malloc in D.

This simple example creates a string consisting of all the numbers up to an integer n, separated by spaces.

ex.cpp

#include <cstdio>  // for snprintf
#include <numeric> // for iota example
#include <sstream> // string builder
#include <vector>

void numbers_upto(char*& out, int n) {
  std::vector<int> xs(n);
  std::iota(xs.begin(), xs.end(), 0);
  std::ostringstream buf;

  for(const int& x: xs) {
    buf << x << " ";
  }

  std::string str{buf.str()};
  size_t str_size = str.length();

  out = new char[str_size];
  snprintf(out, str_size, "%s", str.c_str());
}

ex.d

The D side is simple, but note we use a scope guard to free our string once it goes out of scope to avoid a memory leak. The corresponding type to char *& in D is ref char*.

extern (C++) {
  void numbers_upto(ref char* s, int n);
}

void main() {
  import std.conv : to;
  import std.stdio : writeln;
  import core.stdc.stdlib : free;

  char* s;
  scope(exit) {
    // ensure s is not null before trying to free
    if (s !is null) {
      free(s);
    }
  }

  numbers_upto(s, 10);
  writeln(to!string(s));
}

And to compile:

  1. g++ -c -o cpp.o ex.cpp
  2. dmd ex.d cpp.o -L-lstdc++
  3. ./ex

It should print:

0 1 2 3 4 5 6 7 8 9

As a bonus we can write a wrapper templated function to call functions of this style, and use a D contract to ensure the string returned from C++ is valid.

Updated ex.d

extern (C++) {
   void numbers_upto(ref char* s, int n);
}

void CheckedString(alias Fn, Args...)(ref char* s, Args args) 
out(; s !is null)
do {
  Fn(s, args);
}

void main() {
  import std.conv : to;
  import std.stdio : writeln;
  import core.stdc.stdlib : free;

  char* s;
  scope(exit) free(s);

  CheckedString!numbers_upto(s, 10);
  writeln(to!string(s));
}

We can remove the check in the scope guard as the program will be forced into an invalid state before the scope is checked if the contract assertion fails.

The CheckedString function takes a function, a ref char* and a set of arguments which will be passed to the function. The out part is a contract which runs after the function has completed. We simply check that the ref string is not null.

Compile in the same way and you should have the same result as before. To test the new functionality out, simply nullify out in the C++ code before the end of the numbers_upto function, like so: out = nullptr; and the D contract should fail as intended.

Discussion (2)

Collapse
markboer profile image
Mark Boer

Hey @fxcqz , really good to see some C++ on dev.to :-)

I have read about the D langauge and should really give it a try. I was wondering though, is this the only way to pass strings from C++ to D. The function you wrote is mostly the C style of doing things, in C++ i would expect a signature like:

std::string numbers_upto(int n)

Do you really need the intermediate char* for it to work?

Collapse
eljayadobe profile image
Eljay-Adobe

C is the common ABI ... the lingua franca between C++ and D. (And the lingua franca between many otherwise incompatible compiled programmings languages.)

If you want to give D a try, you can use dmd (Digital Mars), gdc (GNU GCC family), or ldc2 (LLVM family) compilers. q.v. D compilers