Search code examples
c#c++dllauto

Why Can't I Use Functions From a DLL That Contain auto as a Parameter or Return Type?


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?


Solution

  • 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.