#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 к длине, чтобы скопировать дополнительный байт.