вернуть строку из функции c в VB .Net

#c #vb.net #pinvoke

#c #vb.net #pinvoke

Вопрос:

Я пытаюсь вызвать функцию C из VB.Net код, который возвращает строку с использованием P / Invoke, но возвращает только один символ.

Объявление функции C

   extern "C" __declspec(dllexport) LPSTR Get_GetDescription(HANDLE)
  

Определение функции C

   LPSTR Get_GetDescription(HANDLE resultBreakDown){
    return LPSTR(((CalcBreakDown*)resultBreakDown)->GetDescription().c_str()); 
}
  

VB.Net Код

     <DllImport("FeeEngineDll.dll", CallingConvention:=CallingConvention.Cdecl)> _
    Public Shared Function Get_GetDescription(ByVal resultBreakDown As IntPtr, ByVal indexSubs As Integer, ByVal indexLine As Integer) As <MarshalAsAttribute(LPStr)> String
    End Function
  

Есть ли какие-либо проблемы с типом возвращаемого значения или сортировкой?

Комментарии:

1. Оба. Код C фундаментально нарушен, он возвращает зависший указатель. Кроме того, это серьезный сбой в Vista и Win7, строковые буферы, возвращаемые в качестве возврата функции, должны быть выделены с помощью CoTaskMemAlloc().

Ответ №1:

 extern "C" __declspec(dllexport) LPSTR Get_GetDescription(HANDLE)
  

Возвращать подобный указатель довольно опасно, поскольку неясно, кому принадлежит память и, следовательно, кто должен взять на себя ответственность за ее освобождение.

Было бы безопаснее создать буфер в вашем коде VB и передать его в DLL, где значение может быть сохранено в memcpy. Таким образом, мы могли бы переписать часть C следующим образом:

 extern "C" __declspec(dllexport) void Get_GetDescription(HANDLE, LPSTR)

void Get_GetDescription(HANDLE resultBreakDown, LPSTR buffer){
    memcpy(buffer,
           ((CalcBreakDown*)resultBreakDown)->GetDescription().c_str(),
           ((CalcBreakDown*)resultBreakDown)->GetDescription().length() 1); 
}
  

А затем переделать код VB следующим образом:

 <DllImport("FeeEngineDll.dll", CharSet:=CharSet.Ansi, CallingConvention:=CallingConvention.Cdecl)> _
Public Shared Sub Get_GetDescription(ByVal resultBreakDown As IntPtr, <MarshalAs(UnmanagedType.LPStr)> ByVal szFilename As StringBuilder)
End Sub
  

Я добавил CharSet:=CharSet.Ansi в DllImport. Ваш код на C не использует символы Юникода, в то время как VB, вероятно, будет использовать, поэтому лучше указать это, вам, вероятно, не нужно это вводить, но мне нравится делать эти вещи явными.

Обратите внимание на использование StringBuilder вместо String , поскольку строки неизменяемы в VB. Наконец, вам нужно будет быть осторожным, чтобы выделить достаточно места в вашем конструкторе строк для описания:

 Dim buffer As StringBuilder = New StringBuilder(512)
  

Вы можете либо сделать это, используя большое число в вашем коде VB, как я только что сделал. Однако это вызовет проблемы, если ваш код на C когда-либо скопирует больше символов, чем вы выделили.

Другими лучшими вариантами было бы либо передать размер буфера в код C , чтобы он знал, сколько ему разрешено записывать, либо иметь функцию get size в коде C , которую можно использовать для определения того, сколько места следует выделить для буфера.

Комментарии:

1. .. Большое спасибо за подробное понимание вопроса… Я также могу написать код на неуправляемом c , чтобы освободить память, например. Аннулировать FreeUmanagedString(void* p){удалить[]p;} ….. и вызвать эту функцию из моего VB. Чистый код…. этот подход лучше или подход, который вы упомянули в своем ответе, лучше.

2. Я бы все равно очень одобрил подход, который я изложил. Как указал Ханс в своем комментарии к вопросу, существуют и другие потенциальные проблемы с выделением памяти в DLL и использованием ее в другом месте.

3. …я использовал ваш подход …. но возникает одна проблема… в дополнительном пространстве буфера появляются неравномерные символы…. когда я показываю текст, возвращенный из неуправляемого кода

4. Упс, прошу прощения, что опубликованный мной код не копирует нулевой завершающий байт для строки, теперь исправлено. Просто добавьте 1 к длине, чтобы скопировать дополнительный байт.