I have been able to achieve a program that imports a C++ DLL function and uses it correctly to get the required calculated values. I'm returning the char* values to VB.net using an intptr pointer.
It works fine, however, I cant seem to rest or clear the memory space where the result from the function is stored. When i call the function the first time it gives me the right answers, when it is called the second time, it gives me both the first and the second answers.
Here are relevant parts of my code:
CPM.cpp - The function that calculated the return variables in the cpp file
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 - the header file for exporting the function
#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 code to import the DLL, declare the function and use it
'' 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
As I run it consecutively the string just keeps getting longer i.e. 4th function call would return the values for project 1, 2, 3 and 4.
I've checked online for the past few hours trying to figure out how to return char* from C++ DLLs and then how to clear the intptr.
I just haven't had any luck with the solutions suggested. I would really really appreciate some help.
Thank you!
Per the following MSDN documentation:
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.
So, the DLL needs to dynamically allocate a new char*
string each time it returns, and the VB code needs to be told how to free that string properly. There are a few ways to handle this:
have the DLL return a char*
(or wchar_t*
) string that is allocated with CoTaskMemAlloc()
, and then change the PInvoke to take the return value as a string
marshaled as an UnmanagedType.LPStr
(or UnmanagedType.LPWStr
). The .NET runtime will then free the memory for you using CoTaskMemFree()
.
change the DLL to return a COM BSTR
string that is allocated with SysAllocString()
, and then change the PInvoke to take the return value as a string
marshaled as an UnmanagedType.BStr
. The .NET runtime will then free the memory for you using SysFreeString()
.
if you want to have the DLL return a raw char*
(or wchar_t*
) string and have PInvoke treat it as an IntPtr
(because it is not allocated using CoTaskMemAlloc()
to SysAllocString()
), then the .NET runtime will have no way of knowing how the string was allocated and so cannot free the memory automatically. So either:
the IntPtr
will have to be passed back to the DLL when done being used, since only the DLL will know how the memory was allocated, so only the DLL will be able to free it properly.
have the DLL allocate the char*
(or wchar_t*
) string using LocalAlloc()
, and then the .NET code can use Marshal.PtrToStringAnsi()
(or Marshal.PtrToStringUni()
) to get a string
from the IntPtr
, and then pass the IntPtr
to Marshal.FreeHGlobal()
when done using it.
See the following article for more details (it is written for C#, but you can adapt it for VB.NET):