So, basically I have a c++ code which I wrap into CLI.
Wrapper:
public ref class Managed
{
Native* native;
public:
Managed();
~Managed();
bool FooManaged(String^ param1, String^ param2);
};
bool Managed::FooManaged(String^ param1, String^ param2)
{
return native->Foo(StringToChar(param1), StringToChar(param2));
}
char* StringToChar(String^ str)
{
char *ch;
pin_ptr<const wchar_t> wch = PtrToStringChars(str);
size_t convertedChars = 0;
size_t sizeInBytes = ((str->Length + 1) * 2);
ch = (char *)malloc(sizeInBytes);
wcstombs_s(&convertedChars, ch, sizeInBytes, wch, sizeInBytes);
return ch;
}
Native code:
Native::Foo(char* param1, char* param2)
{
// param1 error: cannot obtain value char*
}
Everything's OK on managed side, but on unmanaged I get this error. Any suggestions?
EDIT
Guys, this sample works just fine (I've put up a brief snippet). And I do know several other ways to perform the same thing. BUT! Every method I use doesn't work for letters:
String^ "1234567890" ---> char* 0x0000000 "1234567890"
String^ "name" ---> error
String^ "192.168. ..." ---> char* 0x0000000 "192.168. ..."
Simple way to do this is to use library functions:-
using System::Runtime::InteropServices::Marshal;
char *charString = (char*)Marshal::StringToHGlobalAnsi(managedString);
There was no issue with conversion itself. The full signature of function was:
Native::Foo(char* param1, short param2, char* param3, char* param4)
{
// param3 error: cannot obtain value char*
}
Here error appeared on 'param3'. Changing params order solved my problem:
Native::Foo(char* param1, char* param3, char* param4, short param2)
{
// everything's cool
}
Related
I'm making a class in c++-cli, and I added an extra argument to one of my functions. The name of the extra argument was int row,and when I add that I get this error:
LNK2022 metadata operation failed (80131187) : Inconsistent method declarations in duplicated types (types: query; methods: Read_DB)
When I remove that extra argument I added, the error goes away. If I remove the argument column_index, it also goes away. But if I remove one of the String^ arguments, it still stays. I'm not sure what the error is, here's both my .h and the functions definition in the .cpp file of code:
Header:
#ifndef DATA_BASE
#define DATA_BASE
using namespace System;
using namespace System::Data::SqlClient;
ref class ConnectDB{
protected:
SqlConnection^ cnn;
bool state;
public:
String^ db;
bool ConnectDataBase();
bool DisconnectDataBase(void);
};
ref class Query : public ConnectDB {
private:
~Query(void);
public:
bool Create_Table(String^ name, String^ columns);
String^ Read_DB(String^ column, String^ table, int column_index, int row);
bool Write_DB(String^ path, String^ msg);
};
#endif
cpp file:
String^ Query::Read_DB(String^ column, String^ table, int column_index, int row) {
String^ output;
String^ sql = "SELECT " + column + " FROM " + table;
try {
SqlCommand^ command;
SqlDataReader^ dataReader;
command = gcnew SqlCommand(sql, cnn);
dataReader = command->ExecuteReader();
std::cout << "Reading data from Database...\n";
int counter;
while (dataReader->Read()) {
counter++;
if(counter == row)
output = (String^)dataReader->GetValue(column_index);
}
//command->Dispose();
dataReader->Close();
}
catch (Exception^ e) {
Console::WriteLine(e);
std::cout << "Failed to query database\n";
return "0";
}
return output;
}
to fix the problem I just deleted the debug folder and restarted my application. It had something to do with changing the name of the object.
My problem is that on the line just above "return temp;" in my CreateNewDepartment function I get a weird compile error (red underline in Visual Studio). When I mouse over the compile error text ("deptId,") in "Department^ temp(deptId, var[0], var[1]);" the pop-up tip says:
a value type of "int" cannot be used to initialize an entity of type "Department ^"
Here is my code to create a new object:
Department^ CreateNewDepartment(SQLRETURN retCode, SQLHANDLE hStmt)
{
int deptId;
String^ Name;
String^ Location;
System::String^ bufN;
char buf[256];
SQLINTEGER numBytes;
for (int i = 1; i <= 3; i++)
{
retCode = SQLGetData(
hStmt,
i, // COLUMN NUMBER of the data to get
SQL_C_CHAR, // DATA TYPE that you expect to receive
buf, // BUFFER to put the data that you expect to receive
255, // BUFFER size in bytes (-1 for null terminator)
&numBytes // SIZE in bytes of data returned
);
if (CHECK(retCode, "SqlGetData", false))
{
bufN = gcnew String((char *)buf);
if (i == 1)
{
std::string s = msclr::interop::marshal_as<std::string>(bufN);
deptId = std::stoi(s, nullptr, 0);
}
else if (i == 2)
{
Name = bufN;
}
else if (i == 3)
{
Location = bufN;
}
}
}
Department^ temp(deptId, Name, Location);
return temp;
}
Department is defined thus:
ref class Department
{
private:
int _deptId;
String^ _deptName;
String^ _deptLocation;
public:
Department(int id, String^ deptName, String^ deptLocation);
<...snip...>
}
And the cstr and set functions:
Department::Department(int id, String^ deptName, String^ deptLocation)
{
setDeptId(id);
setDeptName(deptName);
setDeptLocation(deptLocation);
}
void Department::setDeptId(int id)
{
_deptId = id;
}
void Department::setDeptName(String^ name)
{
_deptName = name;
}
void Department::setDeptLocation(String^ location)
{
_deptLocation = location;
}
Sorry if this is a simple error or dumb question, but I'm new to CLI.
Department^ temp(deptId, Name, Location);
Apparently should have been:
Department^ temp(gcnew Department(deptId, Name, Location));
Although I have no idea why. lol
I am having trouble marshaling an array of char* by reference. The data is filled in correctly on the C++ unmanaged side. But when its returned by referernce to the managed side, I end up with a single pointer to the first array element.
//The function in C++
extern "C" DATAACCESSLAYERDLL_API void __stdcall DB_SchemaField_GetKeyValues(Schema::TSchemaFieldHandle hField, const char** &keys, const char ** &values)
{
Schema::CSchemaField *pField = CDataObjectFactory::GetObjectTpl<Schema::CSchemaField>(hField);
if (!pField) return;
Schema::TSchemaKeyValuePair::iterator itor = pField->GetKeyValues().begin();
int index = 0;
for (itor; itor != pField->GetKeyValues().end(); ++itor)
{
keys[index] = (*itor).first.c_str();
values[index] = (*itor).second.c_str();
index++;
}
return;
}
The pInvoke declaration
[System.Security.SuppressUnmanagedCodeSecurity()]
[DllImport("DataCore.dll")]
static private extern void DB_SchemaField_GetKeyValues(Int64 pField,
[In, Out] ref IntPtr[] keys, [In, Out] ref IntPtr[] values);
And finally.... the code which marshals
int keyValueCount = DB_SchemaField_GetKeyValuesCount(GetHandle());
if (keyValueCount > 0)
{
IntPtr[] KeysPtr = new IntPtr[keyValueCount];
IntPtr[] ValuesPtr = new IntPtr[keyValueCount];
DB_SchemaField_GetKeyValues(GetHandle(), ref KeysPtr, ref ValuesPtr);
for (int i = 0; i < keyValueCount; i++)
{
string key = Marshal.PtrToStringAnsi(KeysPtr[i]);
string value = Marshal.PtrToStringAnsi(ValuesPtr[i]);
if (!String.IsNullOrEmpty(key))
{
KeyValues.Add(key, value);
}
}
}
It is a mistake to pass the two const char* arrays by reference. That's one level of indirection too far for the marshaller. You need the following:
C++
extern "C" DATAACCESSLAYERDLL_API void __stdcall DB_SchemaField_GetKeyValues(
Schema::TSchemaFieldHandle hField, const char** keys, const char ** values)
C#
[System.Security.SuppressUnmanagedCodeSecurity()]
[DllImport("DataCore.dll")]
static private extern void DB_SchemaField_GetKeyValues(Int64 pField,
[Out] IntPtr[] keys, [Out] IntPtr[] values);
You'd better make sure you use the pointers that are returned immediately, because the C string returned by c_str() is only valid until the next modification of the std::string object.
I have a class which collects all paths to .txt files of a given folder and stores them into a vector. Most of the functions I use require the usage of TCHAR* to get/set current directory and so on.
The class looks like this:
typedef std::basic_string<TCHAR> tstring;
class folderManager
{
private:
TCHAR searchTemplate[MAX_PATH];
TCHAR directory[MAX_PATH];
WIN32_FIND_DATA ffd;
HANDLE hFind;
vector<tstring> folderCatalog;
vector<tstring> fileNames;
bool succeeded;
public:
// get/set methods and so on...
};
// Changed TCHAR* dir to tstring dir
void folderManager::setDirectory(tstring dir)
{
HANDLE hFind = NULL;
succeeded = false;
folderCatalog.clear();
fileNames.clear();
// Added .c_str()
SetCurrentDirectory(dir.c_str());
GetCurrentDirectoryW(MAX_PATH, directory);
TCHAR fullName[MAX_PATH];
StringCchCat(directory, MAX_PATH, L"\\");
StringCchCopy(searchTemplate, MAX_PATH, directory);
StringCchCat(searchTemplate, MAX_PATH, L"*.txt");
hFind = FindFirstFile(searchTemplate, &ffd);
if (GetLastError() == ERROR_FILE_NOT_FOUND)
{
FindClose(hFind);
return;
}
do
{
StringCchCopy(fullName, MAX_PATH, directory);
StringCchCat(fullName, MAX_PATH, ffd.cFileName);
folderCatalog.push_back(fullName);
fileNames.push_back(ffd.cFileName);
}
while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
succeeded = true;
}
This is where I need to do the conversion of System::String^ to TCHAR*
private: System::Void dienuFolderisToolStripMenuItem_Click(System::Object^
sender, System::EventArgs^ e)
{
FolderBrowserDialog^ dialog;
dialog = gcnew System::Windows::Forms::FolderBrowserDialog;
System::Windows::Forms::DialogResult result = dialog->ShowDialog();
if (result == System::Windows::Forms::DialogResult::OK)
{
// Conversion is now working.
tstring path = marshal_as<tstring>(dialog->SelectedPath);
folder->setDirectory(path);
}
}
marsha_as "Performs the marshaling on a specific data object to convert it between a managed and a native data type".
Here there is the table for possible type conversion.
I use it this way:
marshal_as<std::wstring>(value)
TCHAR can be char or wchar_t, both of them present in marshal_as specialization, I suppose you need to point TCHAR* as template parameter:
TCHAR* result = marshal_as<TCHAR*>(value)
Actually MSDN says that you have to use it this way:
#include <msclr\marshal.h>
using namespace System;
using namespace msclr::interop;
int main(array<System::String ^> ^args)
{
System::String^ managedString = gcnew System::String("Hello World!!!");
marshal_context ^ context = gcnew marshal_context();
const wchar_t* nativeString = context->marshal_as<const wchar_t*>(managedString);
//use nativeString
delete context;
return 0;
}
I'm looking for an annotation something like
-(SomeStruct *) structFromInternals __attribute__((returns_malloced_ptr))
{
SomeStruct *ret = malloc(sizeof(SomeStruct));
//do stuff
return ret;
}
to soothe the clang static analyzer beasts.
The only viable attributes link I can find is for GCC, but it doesn't even include ns_returns_retained, which is in an extension, I assume.
EDIT:
as to why this is needed, I have a scenario that I can't repro in a simple case, so it may have to do with a c lib in an Objective-C project... The gist is, I get a static analyzer warning that the malloc in createStruct is leaked:
typedef struct{
void * data;
size_t len;
}MyStruct;
void destroyStruct(MyStruct * s)
{
if (s && s->data) {
free(s->data);
}
if (s) {
free(s);
}
}
MyStruct * createStructNoCopy(size_t len, void * data)
{
MyStruct * retStruct = malloc(sizeof(MyStruct));
retStruct->len = len;
retStruct->data = data;
return retStruct;
}
MyStruct * createStruct(size_t len, void * data)
{
char * tmpData = malloc(len);
memcpy(tmpData, data, len);
return createStructNoCopy(len, tmpData);
}
MyStruct * copyStruct(MyStruct * s)
{
return createStruct(s->len, s->data);
}
The function annotation ownership_returns(malloc) will tell the Clang static analyser that the function returns a pointer that should be passed to free() at some point (or a function with ownership_takes(malloc, ...)). For example:
void __attribute((ownership_returns(malloc))) *my_malloc(size_t);
void __attribute((ownership_takes(malloc, 1))) my_free(void *);
...
void af1() {
int *p = my_malloc(1);
return; // expected-warning{{Potential leak of memory pointed to by}}
}
void af2() {
int *p = my_malloc(1);
my_free(p);
return; // no-warning
}
(See the malloc-annotations.c test file for some more examples of their use.)
At the moment, these annotations only take effect when the alpha.unix.MallocWithAnnotations checker is run (which is not run by default). If you're using Xcode, you'll need to add -Xclang -analyzer-checker=alpha.unix.MallocWithAnnotations to your build flags.