I was experimenting with dynamic memory allocation for the programming language I am creating. My main project is made in C# but I have a C++ DLL containing the methods to create variables. The DLL methods are loaded in with C#'s System.Runtime.InteropServices.DllImport()
attribute. I discovered that when building my C++ file into a DLL (using g++ v6.3.0), any functions that returned auto
or had auto
parameters would not export into the DLL. I checked with dumpbin -exports
and found that they were not included. I managed to fix the DLL, but it left me wondering why the auto
type would not export.
I know that it is not just a C# issue (I have tested it with other languages such as Python, and this was before I figured out that the problem was a compilation problem), and it is not just a g++ issue because other compilers also fail to export the functions. Oddly enough, no error is thrown anywhere during the process.
Original code:
// Variable Manipulation methods
extern "C" {
auto CreateVariable(auto value) {
// Create a variable and return its address
// Here, C++ deduces that "value" is any type and the function creates a new object of that type, pointing to the value
return new (typeof(value))(value);
}
auto GetVariable(auto address) {
// Return the value of a variable from the specified address
// Here, C++ deduces that "address" is a pointer of some sort
return *addr;
}
void SetVariable(auto address, auto value) {
// Set the variable using its specified address
// Here, C++ deduces that "address" is a pointer and "value" is any type
*addr = value;
}
void DeleteVariable(auto address) {
// Delete the variable.
// Here, C++ deduces that "address" is a pointer of some sort
delete addr;
}
}
I expected to be able to use
[DllImport("dll_path.dll")]
public static extern IntPtr CreateVariable([MarshalAs(UnmanagedType.Any)] object value);
[DllImport("dll_path.dll")]
public static extern object GetVariable(IntPtr address);
[DllImport("dll_path.dll")]
public static extern void SetVariable(IntPtr address, [MarshalAs(UnmanagedType.Any] object value);
[DllImport("dll_path.dll")]
public static extern void DeleteVariable(IntPtr address);
in my C# program, but it kept throwing System.EntryPointNotFoundException
, saying that the entry point could not be found. Naturally, I suspected that C# was just being picky about DLLs, but I tested with other languages like Python's ctypes
module, and it threw the same error. I did figure out the solution: using the windows.h BYTE
type (unsigned char
).
My question is: why can't I export functions with auto
parameters or auto
return types?
First of all, auto
is not some dynamic type in C++. The auto
keyword is some sort of placeholder for the compiler to deduce:
// int i1 = 0; // same thing in the eyes of the compiler
auto i1 = 0;
But the compiler only deduce types when it is initialized with a value.
// ???? i2;
// i2 = 0;
auto i2; // error! deduce what type?
i2 = 0; // cannot call operator= with unknown type
But you can have auto
in a lambda type, what is different there?
// type of `lambda` is an unnamed type.
auto lambda = [](auto var) { std::cout << var; };
lambda(1);
lambda("string");
Even if that looks dynamic, is it not. The generic lambda is implemented using a template:
// v----- unnamed
struct {
template<typename T>
auto operator()(auto var) const {
std::cout << var;
}
} lambda{};
That means the compiler will generate new static code on the fly for an auto
parameter. What is means is even if you upgraded to C++20, which allows for:
auto CreateVariable(auto value) {
// ...
}
No function would actually exist. It's just a template waiting to be instanciated.
There would be no function to export from your dll, as it would be just some templates.
What you are looking for is something like this:
struct CSharpObject;
auto CreateVariable(CSharpObject* value) -> CSharpObject* {
// reflect on value to check the type
// construct a new instance of CSharpObject with the
// right set of metadata for c# to understand
}
Unfortunately, C++ don't understand the dynamic, reflectable and garbage collected C# object, and C# don't understand the static nature of template and value types of C++.
What you will need is to provide a set of function that operate on a set of known types:
auto CreateVariableInt(int value) -> int* {
return new int{value};
}
auto GetVariableInt(int* address) -> int {
return *addr;
}
auto CreateVariableDouble(double value) -> double* {
return new double{value};
}
auto CreateVariableDouble(double* address) -> double {
return *address;
}
// so on and so forth for every supported types.
Then on the C# side, keep metadata on what type is contained, and call the right function.