Использование Win32 dll в приложении c # (возвращение char * в проблему c #) - PullRequest
4 голосов
/ 01 февраля 2010

Я работаю над приложением c # (использую Win 32 DLL в моем приложении) ... Я пытаюсь что-то вроде этого В DLL (test.dll):

char* Connect(TCHAR* lpPostData)
{
char buffer[1000];
.....
return buffer;
}

В приложении c #:

[DllImport("test.dll", EntryPoint = "Connect", CharSet = CharSet.Unicode)]
 [return: MarshalAs(UnmanagedType.LPWStr)]
 public static extern string Connect(StringBuilder postdata);

string returnedData = Connect(postdata);

Но возврат данных не происходит должным образом .... Просьба любой орган может сказать, где я иду не так Заранее спасибо

Ответы [ 5 ]

2 голосов
/ 01 февраля 2010

TCHAR * in говорит мне, что это ввод Unicode (UNICODE определен в CE), но char * говорит мне, что это, скорее всего, байтовый массив (или строка ascii), и кто бы ни создавал этот API, его нужно шлепнуть для смешивания двух, так как это действительно очень плохой дизайн.

Вы, конечно, не можете упорядочить возвращение как строку широких символов, потому что она не одна. На рабочем столе вы бы использовали предложение Тони, но MSDN (и практика) ясно показывает , что он недоступен в CF (не знаю, почему MS думала, что нам это не понадобится).

Smart Device Framework имеет его . Другой вариант - использовать Marshal для копирования из возвращенного указателя в байтовый массив, а затем использовать Encoding.ASCII, чтобы превратить этот массив в строку. Конечно, это указывает на еще один очевидный недостаток этого API, заключающийся в том, что он не должен возвращать строку .

РЕДАКТИРОВАТЬ 1

Поскольку я вижу другие предложения относительно того, что вы должны делать, с которыми я не совсем согласен, я полагаю, что должен привести вам пример:

Ваш родной вызов должен выглядеть примерно так:

extern "C" 
__declspec(dllexport) 
const BOOL __cdecl Connect(TCHAR* lpPostData, 
                         TCHAR *returnBuffer, 
                         DWORD *returnSize) 
{ 
  // validate returnSize, returnBuffer, etc
  // write your data into returnBuffer

  TCHAR *data = _T("this is my data");

  _tcscpy(returnBuffer, data);
  *returnSize = (_tcslen(data) + 1) * sizeof(TCHAR);

  return succeeded;
} 

Обратите внимание, что я просто возвращаю код успеха. Текстовые данные передаются в виде указателя вместе с длиной для него (поэтому API знает, сколько места он может использовать, и может возвращать, сколько он использовал). Также не то, чтобы я соответствовал моим типам данных строковых переменных, и я использую макросы TCHAR, которые в CE станут wchar_t, что согласуется с остальной частью ОС (у которой почти нет API-интерфейсов ASCII для начала).

Большая часть набора API WIn32 работает точно так же.

Ваша декларация P / Invoke очень проста:

[DllImport("test.dll", SetLastError=true)] 
private static extern bool Connect(string postData, 
                                   StringBuilder data, 
                                   ref int length);

Использовать его также просто:

void Foo()
{
  int length = 260;
  StringBuilder sb = new StringBuilder(length);
  if(Connect("Bar", sb, ref length))
  {
    // do something useful
  }
} 

Обратите внимание, что StringBuilder должен быть инициализирован до некоторого размера, и этот размер - то, что вы передаете в третьем параметре.

2 голосов
/ 01 февраля 2010

Вы пытаетесь вернуть переменную в стеке; это не сработает. Вы можете объявить буфер статическим, просто чтобы убедиться, что маршалинг работает.

1 голос
/ 02 февраля 2010

Есть несколько недостатков, как указано в различных ваших ответах. Подводя итог, вот что я бы предложил.

На нативной стороне объявите что-то вроде этого (при условии, что вы кодируете в C ++):

extern "C"
__declspec(dllexport)
const char* __cdecl Connect(const char* lpPostData)
{
    static char buffer[1000];
    ...
    return buffer;
}

extern "C" дает понять компилятору, что он не должен манипулировать именем функции при ее экспорте, но должен обрабатывать его так же, как он будет обращаться с функцией C.

__ceclspec(dllexport) делает функцию видимой в DLL как экспортированную точку входа. Затем вы можете ссылаться на него из сторонних проектов.

__cdecl гарантирует, что вы используете способ C для передачи аргументов в стеке. Вы можете сильно испортить ситуацию, если вызывающая сторона не принимает ту же схему передачи аргументов.

Используйте char последовательно: либо больной char (текст ANSI), либо wchar_t (16-битный текст Unicode). И никогда, никогда не возвращайте указатель на локальную переменную, как вы это делали в своем примере. Как только функция вернется, эта ячейка памяти больше не будет действительной и может содержать мусор. Кроме того, если пользователь изменяет данные, хранящиеся в нем, это может привести к сбою программы, поскольку это приведет к разрушению стека вызовов.

На стороне управляемого C # вот что я рекомендую:

[DllImport("test.dll", EntryPoint="Connect",
           CharSet=CharSet.Ansi, ExactSpelling=true,
           CallingConvention=CallingConvention.Cdecl)]
private static extern System.IntPtr Connect(string postData);

, который будет привязан к вашей точке входа Connect, используйте строковое соглашение ANSI (8 бит на символ, что ожидает char) и убедитесь, что вызывающая сторона ожидает __cdecl вызывающую конвекцию.

Вам придется маршалировать IntPtr к string объекту, вызвав:

string value = Marshal.PtrToStringAnsi (Connect (postData));

Примечание: в моем исходном сообщении я рекомендовал объявить Connect как возвращающее string, но как исправленный комментатор Mattias , это неправильный способ сделать это. См. Эту статью в CLR Inside Out , чтобы понять, как CLR обрабатывает retval s типа string. Спасибо за указание на это мне (и спасибо romkyns тоже).

Вы также можете скопировать строку из buffer в часть памяти, выделенную через CoTaskMemAlloc в собственном коде, и вернуть указатель на нее. Затем вы могли бы просто объявить функцию DllImport как возвращающую string, и вам не нужно было бы выполнять дальнейшую сортировку в управляемом мире.

0 голосов
/ 02 февраля 2010

Вот пример того, как вы можете сделать то, что предложил Тони:

[DllImport("test.dll", EntryPoint="my_test_func",
           CharSet=CharSet.Ansi, ExactSpelling=true,
           CallingConvention=CallingConvention.Cdecl)]
private static extern IntPtr my_test_func_imported();

public static string my_test_func()
{
    return Marshal.PtrToStringAnsi(my_test_func_imported());
}

На неуправляемой стороне:

const char *my_test_func(void);

(это импорт функции C из "test.dll")

P.S. Как указывает ctacke - это, скорее всего, не подходит для .NET CF.

0 голосов
/ 01 февраля 2010

Возвращаемый тип вашей функции должен быть IntPtr, а затем использовать Marshal.PtrToStringAnsi, чтобы получить строку из вашего IntPtr.

...