PAssing string arrays from C++ through DLL

Hi ,

I am trying to make MS-SQL integration in UE4 , with C# dll import , and the result is quite impressive , however I am stuck at passing array from C++ to C# , it is throwing unhandled exception. Kindly let me know what are the best ways to pass dynamic string arrays from C++ to C#.

Here is a simple code that lets you insert data to sql db , this is called from blueprint :

C++


FString ULDAPAuthentication::InsertData(FString TableName, TArray<FKeyValuePair> Data)
{
	int length = Data.Num();
	std::string tablename(TCHAR_TO_UTF8(*TableName));

	FString CName = "";
	FString CValue = "";
	std::string *ColumnNames = new std::string[length];
	std::string *ColumnValues = new std::string[length];

	for (int i = 0; i < Data.Num(); i++)
	{
		std::string columnname(TCHAR_TO_UTF8(*Data*.KeyName));
		std::string columnvalue(TCHAR_TO_UTF8(*Data*.Value));
		
		
		ColumnNames->append(columnname.c_str());
		ColumnNames->append(columnvalue.c_str());
	
	}

	typedef bool(*_InsertData)(std::string tablename, std::string colname] , std::string value]);
	FString filePath = FPaths::Combine(*FPaths::GamePluginsDir(), TEXT("WinAuthentication.dll")); // Concatenate the plugins folder and the DLL file.
	void *DLLHandle = NULL;
	if (FPaths::FileExists(filePath))
	{
		DLLHandle = FPlatformProcess::GetDllHandle(*filePath);
	}
	if (DLLHandle != NULL)
	{
		_InsertData DLLFuncPtr = NULL;
		FString procName = "InsertData";
		DLLFuncPtr = (_InsertData)FPlatformProcess::GetDllExport(DLLHandle, *procName);
		if (DLLFuncPtr != NULL)
		{
		

			bool result = DLLFuncPtr(tablename, ColumnNames, ColumnValues);  -> **[FONT=Comic Sans MS]This line throws error. Error doesn't occur if the input parameters are string and not arrays , however if the length of the string exceeds 16 , C# replaces the string with some garbage values.**
			return "Successful";

		}
	}

	return "Failed";
}


C# dll version ->

   [DllExport("InsertData", CallingConvention = CallingConvention.Cdecl)]
        static public bool InsertData( [In, MarshalAs(UnmanagedType.LPStr)]String TableName,  [In, MarshalAs(UnmanagedType.LPStr)]String] ColumnNames ,  [In, MarshalAs(UnmanagedType.LPStr)]String] Values)
        {

            StringBuilder columnstring = new StringBuilder();
            columnstring.Append("(");

            StringBuilder valuestring = new StringBuilder();
            valuestring.Append("(");

            for (int i=0; i< ColumnNames.Length; i++)
            {
                columnstring.Append(ColumnNames*);
                valuestring.Append("'");
                valuestring.Append(Values*);
                valuestring.Append("'");

                if (i == ColumnNames.Length - 1)
                {
                    columnstring.Append(")");
                    valuestring.Append(")");
                }
                else
                {
                    columnstring.Append(" , ");
                    valuestring.Append(" , ");
                }
            }
            string Insertstring = "insert into " + TableName + "]" + columnstring +  "values" + valuestring ;

            try
            {
                database.ExecuteNonQuery(Insertstring);
                return true;

            }
            catch (Exception ex)
            {
                string error = ex.Message;
                return false;
            }
        }
    }

It seems that your definition of callback is incorrect.:



typedef bool(*_InsertData)(std::string tablename, std::string colname] , std::string value]);


Also, on a side note identifiers whose names start with underscore are reserved in C++, so it is best idea to use different name.

I haven’t worked with C# dlls, but judging by C# function definition it should be something like this:



typedef bool(*_InsertData)(const char* tablename, const char** colnames , const char ** values);


C# doesn’t know what the heck std::string is. So you shouldn’t be passing them in.
Also, it doesn’t “replace” them with garbage values, it tries to reinterpret data you put onto stack as expected arguments.
In general, you don’t want to use std::string when communicating with dll, unless you can be absolutely sure that dll is using same version of C++ runtime library as your program AND the same compiler. Otherwise funny things might start to happen, especially if your dll will try to modify or return std::strings.

Also… IIRC there are several pure C++ database libraries that can work without that kind of thing with less hassle. Perhaps you could try mysql connector or something similar?

Also see “Marshalling Strings”](Marshaling Strings | Microsoft Docs) article on msdn.

Hello , thanks for your reply. Have figured it out. It should be like this →

C++


	std::vector<std::string> ColumnNames;
	std::vector<std::string> ColumnValues;

char ** arrnames = new char*[ColumnNames.size()];
	for (size_t i = 0; i < ColumnNames.size(); i++) {
		arrnames* = new char[ColumnNames*.size() + 1];
		strcpy(arrnames*, ColumnNames*.c_str());
	}

	char ** arrvalues = new char*[ColumnValues.size()];
	for (size_t i = 0; i < ColumnValues.size(); i++) {
		arrvalues* = new char[ColumnValues*.size() + 1];
		strcpy(arrvalues*, ColumnValues*.c_str());
	}

bool result = DLLFuncPtr(length, tablename, arrnames, arrvalues);

C#


   [DllExport("InsertData", CallingConvention = CallingConvention.Cdecl)]
        static public bool InsertData(int columncount , String TableName, [In , MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)]String] ColumnNames , [In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)]String] Values)
      {
       //Body
       }

It actually shouldn’t be like this, because you forgot to delete every single array and now have multiple memory leaks in your program.
Use std::vector<char*> or TArray<char*> instead. TArray has GetData() method for situations like this.