Я пишу C # GUI для моего приложения C ++. Я извлекаю информацию о плагине, перечисляя доступные библиотеки DLL, динамически загружая их одну за другой, и вызывая функцию с именем «getdescstring», которая возвращает описание плагина, объединенное в строку.
Это работает безупречно на Windows. Однако, когда я пытаюсь запустить его на компьютере с Linux (я пробовал его на 64-битной Linux Mint (Mono 3.0.6) и 32-битной виртуальной машине Xubuntu (Mono 2.10.8), g ++ — 4.7 на оба) большинство возвращаемых строк повреждены следующим образом:
"grown|Barabasi-Albert grown network.|1,000|1.0|1.0|1.0||n|Number of nodes|numeric|k|Degree of neA\0\0…"
Если я вызываю функцию из отдельной тестовой программы C ++, она работает. Нет утечки памяти или повреждения с Valgrind. Когда я пытаюсь запустить всю программу с Mono через Valgrind, происходит сбой при инициализации, поэтому я не могу сообщить об этом.
Так что я подозреваю некоторое повреждение памяти где-то между Mono и DLL, но я не могу определить местоположение.
Обновить:
Моя интуиция заключается в том, что соглашения о вызовах как-то перепутаны. Поскольку 64-битные программы имеют уникальные соглашения о вызовах, возможно, Mono использует ms_abi, что может конфликтовать с sysv_abi в Unix. Однако для них не существует флагов соглашения о вызовах, поэтому, даже если это проблема, я не могу это исправить. Я могу установить CC на stdcall в Mono, но g ++ игнорирует любые атрибуты CC на 64-битных процессорах. Я пытался установить условные обозначения для stdcall на обоих концах в 32-разрядной виртуальной машине, но без изменений
Вот код, вызываемый в DLL (функция «description» возвращает std :: vector строк, но передача std :: vector непосредственно в C # казалась довольно неприятной)
extern "C" const char* getdescstring()
{
vector<vector<string> > descvec=description();
string descstr;
for(unsigned i=0;i<descvec.size();i++)
{
for(unsigned j=0;j<descvec[i].size();j++)
{
descstr+=descvec[i][j];
descstr+="|";
}
descstr+="|";
}
return descstr.c_str();
}
И это принимающая функция, которая преобразует заданную строку обратно:
public static List<List<string>> GetPluginDesc(string plugin)
{
List<List<string>> des = new List<List<string>>();
DllLoadUtils dl;
if (OS == platform.Windows) dl = new DllLoadUtilsWindows();
else dl = new DllLoadUtilsLinux();
IntPtr dllh = dl.LoadLibrary(plugin);
if (dllh == IntPtr.Zero) return null;
IntPtr proc = dl.GetProcAddress(dllh, "getdescstring");
if (proc == IntPtr.Zero) return null;
dsc descr = (dsc)Marshal.GetDelegateForFunctionPointer(proc, typeof(dsc));
String descstr = Marshal.PtrToStringAnsi(descr());
string[] descelem = descstr.Split('|');
List<string> ls = new List<string>();
foreach (string s in descelem)
{
//double pipe means EOL
if (s == "") { des.Add(ls); ls = new List<string>(); }
else ls.Add(s);
}
if (ls.Count > 0) des.Add(ls);
dl.FreeLibrary(dllh);
return des;
}
Я также пробовал этот подход (изменение делегата и функции в DLL для принятия значения char * и длины в качестве параметра), но результаты были еще хуже, никакие разумные данные не передавались:
dsc descr = (dsc)Marshal.GetDelegateForFunctionPointer (proc, typeof(dsc));
IntPtr sf = Marshal.AllocHGlobal(1024);
descr (sf,1024);
String descstr = Marshal.PtrToStringAnsi(sf);
Marshal.FreeHGlobal(sf);
Буду признателен за любую помощь.
Заранее спасибо!
Я также скопировал загрузчики DLL и объявление делегата здесь, если это может помочь:
interface DllLoadUtils {
IntPtr LoadLibrary(string fileName);
void FreeLibrary(IntPtr handle);
IntPtr GetProcAddress(IntPtr dllHandle, string name);
}public class DllLoadUtilsWindows : DllLoadUtils {
void DllLoadUtils.FreeLibrary(IntPtr handle) {
FreeLibrary(handle);
}
IntPtr DllLoadUtils.GetProcAddress(IntPtr dllHandle, string name) {
return GetProcAddress(dllHandle, name);
}
IntPtr DllLoadUtils.LoadLibrary(string fileName) {
return LoadLibrary(fileName);
}
[DllImport("kernel32")]
private static extern IntPtr LoadLibrary(string fileName);
[DllImport("kernel32.dll")]
private static extern int FreeLibrary(IntPtr handle);
[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress (IntPtr handle, string procedureName);
}
internal class DllLoadUtilsLinux : DllLoadUtils {
public IntPtr LoadLibrary(string fileName) {
return dlopen(fileName, RTLD_NOW);
}
public void FreeLibrary(IntPtr handle) {
dlclose(handle);
}
public IntPtr GetProcAddress(IntPtr dllHandle, string name) {
// clear previous errors if any
dlerror();
var res = dlsym(dllHandle, name);
var errPtr = dlerror();
if (errPtr != IntPtr.Zero) {
MessageBox.Show("dlsym: " + Marshal.PtrToStringAnsi(errPtr));
}
return res;
}
const int RTLD_NOW = 2;
[DllImport("libdl.so")]
private static extern IntPtr dlopen(String fileName, int flags);
[DllImport("libdl.so")]
private static extern IntPtr dlsym(IntPtr handle, String symbol);
[DllImport("libdl.so")]
private static extern int dlclose(IntPtr handle);
[DllImport("libdl.so")]
private static extern IntPtr dlerror();
}
делегат просто:
public delegate IntPtr dsc();
Вы возвращаетесь descstr.c_str()
который объявлен в стеке в вашем getdescstring
функция. Память, на которую она ссылается, становится недействительной, как только функция возвращается. Попробуйте сделать descstr.c_str()
поэтому он остается в области видимости (например, как глобальная переменная).
Других решений пока нет …