如何正确使用 intptr 到 return char* 值从 C++ DLL 到 Vb.net
How to properly use intptr to return char* value from c++ DLL to Vb.net
我已经能够实现一个导入 C++ DLL 函数并正确使用它来获取所需计算值的程序。我正在 return 使用 intptr 指针将 char* 值设为 VB.net。
它工作正常,但是,我似乎无法休息或清除存储函数结果的内存 space。当我第一次调用该函数时,它给了我正确的答案,当它第二次调用时,它给了我第一个和第二个答案。
以下是我的代码的相关部分:
CPM.cpp - 计算 cpp 文件
中的 return 变量的函数
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 次函数调用将 return 项目 1、2、3 和 4 的值。
在过去的几个小时里,我一直在网上查看,试图弄清楚如何从 C++ DLL 中 return char*,然后如何清除 intptr。
我只是对建议的解决方案没有任何运气。我真的很感激一些帮助。
谢谢!
根据以下 MSDN 文档:
The interop marshaler always attempts to free memory allocated by unmanaged code. This behavior complies with COM memory management rules, but differs from the rules that govern native C++.
Confusion can arise if you anticipate native C++ behavior (no memory freeing) when using platform invoke, which automatically frees memory for pointers. For example, calling the following unmanaged method from a C++ DLL does not automatically free any memory.
Unmanaged signature
BSTR MethodOne (BSTR b) {
return b;
}
However, if you define the method as a platform invoke prototype, replace each BSTR
type with a String
type, and call MethodOne
, the common language runtime attempts to free b
twice. You can change the marshaling behavior by using IntPtr
types rather than String
types.
The runtime always uses the CoTaskMemFree
method to free memory. If the memory you are working with was not allocated with the CoTaskMemAlloc
method, you must use an IntPtr
and free the memory manually using the appropriate method. Similarly, you can avoid automatic memory freeing in situations where memory should never be freed, such as when using the GetCommandLine
function from Kernel32.dll
, which returns a pointer to kernel memory. For details on manually freeing memory, see the Buffers Sample.
因此,DLL 需要在每次 return 时动态分配一个新的 char*
字符串,并且需要告知 VB 代码如何正确释放该字符串。有几种方法可以解决这个问题:
让 DLL return 一个 char*
(或 wchar_t*
)字符串分配给 CoTaskMemAlloc()
,然后更改 PInvoke 以获取return 值作为 string
编组为 UnmanagedType.LPStr
(或 UnmanagedType.LPWStr
)。然后,.NET 运行时将使用 CoTaskMemFree()
.
为您释放内存
将 DLL 更改为 return 分配有 SysAllocString()
的 COM BSTR
字符串,然后更改 PInvoke 以获取 return值作为 string
编组为 UnmanagedType.BStr
。然后,.NET 运行时将使用 SysFreeString()
.
为您释放内存
如果您想要 DLL return 原始 char*
(或 wchar_t*
)字符串并让 PInvoke 将其视为 IntPtr
(因为它不是使用 CoTaskMemAlloc()
分配到 SysAllocString()
),那么 .NET 运行时将无法知道字符串是如何分配的,因此无法自动释放内存。所以要么:
IntPtr
在使用完毕后必须返回给DLL,因为只有DLL知道内存是如何分配的,所以只有DLL能够妥善释放它。
让 DLL 使用 LocalAlloc()
分配 char*
(或 wchar_t*
)字符串,然后 .NET 代码可以使用 Marshal.PtrToStringAnsi()
(或 Marshal.PtrToStringUni()
) 从 IntPtr
获得 string
,然后在使用完后将 IntPtr
传递给 Marshal.FreeHGlobal()
。
更多详细信息请参阅以下文章(它是为 C# 编写的,但您可以将其改编为 VB.NET):
非常感谢@Remy Lebeau。我已经尝试实施 CoTaskMemAlloc() 方法并且它有效!我的代码如下:
在 cpp 文件中,我编辑了要使用 CoTaskMemAlloc()
分配的 return 值
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
感谢迄今为止的所有帮助!
我已经能够实现一个导入 C++ DLL 函数并正确使用它来获取所需计算值的程序。我正在 return 使用 intptr 指针将 char* 值设为 VB.net。
它工作正常,但是,我似乎无法休息或清除存储函数结果的内存 space。当我第一次调用该函数时,它给了我正确的答案,当它第二次调用时,它给了我第一个和第二个答案。
以下是我的代码的相关部分:
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 次函数调用将 return 项目 1、2、3 和 4 的值。
在过去的几个小时里,我一直在网上查看,试图弄清楚如何从 C++ DLL 中 return char*,然后如何清除 intptr。
我只是对建议的解决方案没有任何运气。我真的很感激一些帮助。
谢谢!
根据以下 MSDN 文档:
The interop marshaler always attempts to free memory allocated by unmanaged code. This behavior complies with COM memory management rules, but differs from the rules that govern native C++.
Confusion can arise if you anticipate native C++ behavior (no memory freeing) when using platform invoke, which automatically frees memory for pointers. For example, calling the following unmanaged method from a C++ DLL does not automatically free any memory.
Unmanaged signature
BSTR MethodOne (BSTR b) { return b; }
However, if you define the method as a platform invoke prototype, replace each
BSTR
type with aString
type, and callMethodOne
, the common language runtime attempts to freeb
twice. You can change the marshaling behavior by usingIntPtr
types rather thanString
types.The runtime always uses the
CoTaskMemFree
method to free memory. If the memory you are working with was not allocated with theCoTaskMemAlloc
method, you must use anIntPtr
and free the memory manually using the appropriate method. Similarly, you can avoid automatic memory freeing in situations where memory should never be freed, such as when using theGetCommandLine
function fromKernel32.dll
, which returns a pointer to kernel memory. For details on manually freeing memory, see the Buffers Sample.
因此,DLL 需要在每次 return 时动态分配一个新的 char*
字符串,并且需要告知 VB 代码如何正确释放该字符串。有几种方法可以解决这个问题:
让 DLL return 一个
char*
(或wchar_t*
)字符串分配给CoTaskMemAlloc()
,然后更改 PInvoke 以获取return 值作为string
编组为UnmanagedType.LPStr
(或UnmanagedType.LPWStr
)。然后,.NET 运行时将使用CoTaskMemFree()
. 为您释放内存
将 DLL 更改为 return 分配有
SysAllocString()
的 COMBSTR
字符串,然后更改 PInvoke 以获取 return值作为string
编组为UnmanagedType.BStr
。然后,.NET 运行时将使用SysFreeString()
. 为您释放内存
如果您想要 DLL return 原始
char*
(或wchar_t*
)字符串并让 PInvoke 将其视为IntPtr
(因为它不是使用CoTaskMemAlloc()
分配到SysAllocString()
),那么 .NET 运行时将无法知道字符串是如何分配的,因此无法自动释放内存。所以要么:IntPtr
在使用完毕后必须返回给DLL,因为只有DLL知道内存是如何分配的,所以只有DLL能够妥善释放它。让 DLL 使用
LocalAlloc()
分配char*
(或wchar_t*
)字符串,然后 .NET 代码可以使用Marshal.PtrToStringAnsi()
(或Marshal.PtrToStringUni()
) 从IntPtr
获得string
,然后在使用完后将IntPtr
传递给Marshal.FreeHGlobal()
。
更多详细信息请参阅以下文章(它是为 C# 编写的,但您可以将其改编为 VB.NET):
非常感谢@Remy Lebeau。我已经尝试实施 CoTaskMemAlloc() 方法并且它有效!我的代码如下:
在 cpp 文件中,我编辑了要使用 CoTaskMemAlloc()
分配的 return 值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
感谢迄今为止的所有帮助!