我已经能够实现一个导入C ++ DLL函数并正确使用它来获取所需计算值的程序。我正在使用 intptr 指针将char *值返回到VB.net。
它工作正常,但是,我似乎无法休息或清除存储该函数结果的内存空间。当我第一次调用该函数时,它会给我正确的答案,而第二次调用时,它会给我第一个和第二个答案。
以下是我的代码的相关部分:
CPM.cpp-计算cpp文件中的返回变量的函数
char* CPMfn(char* sdatabase, int project_num)
{
/* Retrieve data from database and calculate CPM for the selected project number*/
char* testvector = getCPM(sdatabase, project_num);
return testvector;
}
CPM.h-导出函数的头文件
#pragma once
#ifdef CPM_EXPORTS
#define CPM_API __declspec(dllexport)
#else
#define CPM_API __declspec(dllimport)
#endif
extern "C" CPM_API char* CPMfn(char*, int);
VB.net代码导入DLL,声明函数并使用
'' Import C++ CPM Calculation function from CPM DLL
<DllImport("CPM.dll", CallingConvention:=CallingConvention.Cdecl)>
Private Shared Function CPMfn(ByVal dbstring As Char(), ByVal task As Int32) As System.IntPtr
End Function
'' Get CPM results from DLL function with database location string and selected project number
CPMresults = CPMfn(DBString, Val(Project_IDTextBox.Text))
CPMvalues = Marshal.PtrToStringAnsi(CPMresults)
If CPMvalues.Length() = 0 Then
MsgBox("No tasks for seleccted project")
Else
MsgBox(CPMvalues) ' Show CPM values
End If
当我连续运行它时,字符串只会越来越长,即第4个函数调用将返回项目1、2、3和4的值。
在过去的几个小时中,我已经在线检查了一下,试图弄清楚如何从C ++ DLL返回char *,然后如何清除intptr。
我只是没有运气建议的解决方案。我真的很感谢您的帮助。
谢谢!
答案 0 :(得分:3)
根据以下MSDN文档:
互操作性封送处理程序始终尝试释放非托管代码分配的内存。此行为符合COM内存管理规则,但不同于管理本机C ++的规则。
如果您在使用平台调用时预期到本机C ++行为(不释放内存),则会引起混淆,该调用会自动释放指针的内存。例如,从C ++ DLL调用以下非托管方法不会自动释放任何内存。
非托管签名
BSTR MethodOne (BSTR b) { return b; }
但是,如果将方法定义为平台调用原型,将每个
BSTR
类型替换为String
类型,然后调用MethodOne
,则公共语言运行库将尝试释放{{ 1}}两次。您可以使用b
类型而不是IntPtr
类型来更改封送行为。运行时始终使用
String
方法释放内存。如果您正在使用的内存未使用CoTaskMemFree
方法分配,则必须使用CoTaskMemAlloc
并使用适当的方法手动释放内存。同样,可以避免在永远不释放内存的情况下自动释放内存,例如使用IntPtr
中的GetCommandLine
函数时,该函数返回指向内核内存的指针。有关手动释放内存的详细信息,请参见Buffers Sample。
因此,DLL在每次返回时都需要动态分配新的Kernel32.dll
字符串,并且需要告知VB代码如何正确释放该字符串。有几种方法可以解决此问题:
让DLL返回一个char*
分配的char*
(或wchar_t*
)字符串,然后更改PInvoke以将返回值作为{{1 }}被封为CoTaskMemAlloc()
(或string
)。然后,.NET运行时将使用UnmanagedType.LPStr
为您释放内存。
更改DLL以返回分配有UnmanagedType.LPWStr
的COM CoTaskMemFree()
字符串,然后更改PInvoke以将返回值作为BSTR
编组为{ {1}}。然后,.NET运行时将使用SysAllocString()
为您释放内存。
如果要让DLL返回原始的string
(或UnmanagedType.BStr
)字符串,并让PInvoke将其视为SysFreeString()
(因为未使用{ {1}}到char*
),那么.NET运行时将无法知道如何分配字符串,因此无法自动释放内存。所以:
wchar_t*
使用完毕后必须传递回DLL,因为只有DLL才能知道内存的分配方式,因此只有DLL才能正确释放它。
让DLL使用IntPtr
分配CoTaskMemAlloc()
(或SysAllocString()
)字符串,然后.NET代码可以使用IntPtr
(或{{1 }})从char*
获取wchar_t*
,然后在使用完毕后将LocalAlloc()
传递给Marshal.PtrToStringAnsi()
。
有关更多详细信息,请参见以下文章(该文章是为C#编写的,但您可以将其改编为VB.NET):
答案 1 :(得分:0)
非常感谢@Remy Lebeau。我试过实现CoTaskMemAlloc()方法,它可以工作!我的代码如下:
在 cpp 文件中,我编辑了要使用CoTaskMemAlloc()分配的返回值
char* CPMfn(char* sdatabase, int project_num)
{
/* Retrieve data from database and calculate CPM for the selected project number*/
char* CPMvector = getCPM(sdatabase, project_num);
/* Store results in specially allocated memory space that can easily be deallocated when this DLL is called*/
ULONG ulSize = strlen(CPMvector) + sizeof(char);
char* ReturnValue = NULL;
ReturnValue = (char*)::CoTaskMemAlloc(ulSize);
// Copy the contents of CPMvector
// to the memory pointed to by ReturnValue.
int charlen = strlen(CPMvector);
strcpy_s(ReturnValue, charlen + 1, CPMvector);
// Return
return ReturnValue;
}
在 VB.net 文件中,我编写了Dllimport和Marshalling代码,如下所示:
'' Import C++ CPM Calculation function from CPM DLL
<DllImport("CPM.dll", CallingConvention:=CallingConvention.Cdecl, CharSet:=CharSet.Ansi)>
Private Shared Function CPMfn(ByVal dbstring As String, ByVal task As Int32) As <MarshalAs(UnmanagedType.LPStr)> String
End Function
'' Get CPM results from DLL function
Dim teststring As String = CPMfn(cDBString, Val(Project_IDTextBox.Text))
或者我也尝试过使用GlobalAlloc()
和Marshal.FreeHGlobal()
函数手动释放分配的内存,但是我得到了相同的结果(即,第一次调用= 1 ,2,3 \ n,第二个调用= 1,2,3 \ n,4,5,6 \ n而不是4,5,6 \ n)。
这是我使用GlobalAlloc()
方法的代码:
在.cpp文件中
ReturnValue = (char*)::GlobalAlloc(GMEM_FIXED, ulSize);
strcpy_s(ReturnValue, strlen(CPMvector) + 1, CPMvector);
在VB.net文件中
<DllImport("CPM.dll", CallingConvention:=CallingConvention.Cdecl)>
Private Shared Function CPMfn(ByVal dbstring As Char(), ByVal task As Int32) As System.IntPtr
End Function
Dim CPMresults As IntPtr = CPMfn(cDBString, Val(Project_IDTextBox.Text))
Dim CPMvalues As String = Marshal.PtrToStringAnsi(CPMresults)
Marshal.FreeHGlobal(CPMresults)
CPMresults = IntPtr.Zero
感谢到目前为止的所有帮助!