I have C# project "X" ,I have exposed methods in it to the C++ project "Y".
X has method signature as follows -
public void WriteInformation(string sInfo)
{
m_logger.ErrorInfo("{0}", sInfo);
}
As I am exporting it to a C++ using .TLB file I checked declaration of this method in .tlh file which generates on #import of .tlb file.
virtual HRESULT __stdcall WriteInformation ( /*[in]*/ BSTR sMsg ) = 0;
I am calling this method in C++ project and passsing argument as follows -
oLog->WriteInformation(BSTR("Info write successful"));
Issue here is the string passed from C++ always becomes garbage or null , I debugged it and I can see value of sInfo is always garbage or null.
Please let me know what method should be followed to pass string from C++ to C#.
You try to pass an ANSI string in place of BSTR. BSTR must be a wide character string. Also you shouldn't pass a string literal, you should properly allocate a BSTR using SysAllocString() (or better yet) a wrapper class like ATL::CComBSTR or _bstr_t. Btw _bstr_t has a constructor that will accept const char* and do the ANSI->UTF16 conversion for you.
I dont think its possible to interact with C++ and C# directly. I had interacted using a C++/CLI wrapper.
Related
I am trying to use a DLL with an ANSI C compiler. One of the DLL functions takes a void pointer. In some sample Windows code that was provided with the DLL, the struct that gets passed to the function is defined as having three CString entities. I have told the author of the DLL that they should not be passing MFC classes through their DLL functions. They have told me just to replace the CString declarations in the struct with char arrays and it should be fine. I'm 99% sure that's wrong, but since I don't have VC++ and don't have any experience with MFC, and since I've seen some posts saying LPTSTR can be used in place of CString (What is `CString`?), I'm starting to wonder if I'm wrong.
Can someone please confirm for me that CString and LPTSTR are not interchangeable as arguments to a function? If you can provide the source for the definition of the CString class, that would be helpful so I can send it to the DLL's author and explain that the memory footprint of a char array is not the same as a CString class, and that you can't pass a pointer to a struct that was defined with char arrays and then treat it as a bunch of CString objects.
CString is an alias of the CStringT class template. Objects of this class are really better not to pass to the DLL. The character type of the string class can be TCHAR (for both ANSI and Unicode character strings - see explanation below). The definition of CString (and CStringT) can most likely be found in the atlstr.h header file.
LPTSTR is a regular pointer to a sequence of characters. The data type (TCHAR*) depends on the settings of the development environment: if the "Use Unicode Character set" option is selected, the TCHAR data type will be wchar_t (and LPTSTR will be wchar_t*, respectively). If the "Use Multi-byte character set" is selected, the TCHAR will be defined as char (and LPTSTR will be char*).
So the question about interchangeability between CString and LPTSTR is not so simple. It also depends on how the DLL is written. If the DLL was designed with the same environment settings as the main program, then CString and LPTSTR can really be interchangeable.
Also, remember that CStirng is a class with many methods, while LPTSTR is just a pointer.
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:
Default Marshaling Behavior
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.
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):
Returning Strings from a C++ API to C#
Thanks a lot #Remy Lebeau. I've tried implementing the CoTaskMemAlloc() method and it works!. My code is as follows:
In the cpp file i've edited the return value to be allocated using the 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;
}
In the VB.net file I've written the Dllimport and Marshalling code as follows:
'' 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))
Alternatively I've also tried freeing up the allocated memory manually using the GlobalAlloc() and Marshal.FreeHGlobal() functions but i'm getting the same results (i.e. first call = 1,2,3\n, second call = 1,2,3\n,4,5,6\n instead of just 4,5,6\n).
Here's my code with the GlobalAlloc() method:
In .cpp file
ReturnValue = (char*)::GlobalAlloc(GMEM_FIXED, ulSize);
strcpy_s(ReturnValue, strlen(CPMvector) + 1, CPMvector);
And in VB.net file
<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
Thanks for all the help so far!
I'm trying to call a DLL written in C++ from a VB6 application.
Here's the C++ example code for calling the DLL.
char firmware[32];
int maxUnits = InitPowerDevice(firmware);
However, when I try to call it from VB6 I get the error bad DLL calling convention.
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" (ByRef firmware() As Byte) As Long
Dim firmware(32) As Byte
InitPowerDevice(firmware)
Edit: The C++ Prototype:
Name: InitPowerDevice
Parameters: firmware: returns firmware version in ?.? format in a character string (major revision and minor revision)
Return: >0 if successful. Returns number of Power devices connected
CLASS_DECLSPEC int InitPowerDevice(char firmware[]);
Been a long time, but I think you also need to change your C function to be stdcall.
// In the C code when compiling to build the dll
CLASS_DECLSPEC int __stdcall InitPowerDevice(char firmware[]);
' VB Declaration
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" _
(ByVal firmware As String) As Long
' VB Call
Dim fmware as String
Dim r as Long
fmware = Space(32)
r = InitPowerDevice(fmware)
I don't think VB6 supports calling cdecl functions in any normal way - there may be hacks for doing it. May be you can write a wrapper dll which wraps the cdecl function with a stdcall function and just forwards the call.
These are some hacks - but I haven't tried it.
http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=49776&lngWId=1
http://planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=62014&lngWId=1
You need to pass a pointer to the beginning of the array contents, not a pointer to the SAFEARRAY.
Perhaps what you need is either:
Public Declare Function InitPowerDevice Lib "PwrDeviceDll.dll" ( _
ByRef firmware As Byte) As Long
Dim firmware(31) As Byte
InitPowerDevice firmware(0)
or
Public Declare Function InitPowerDevice CDecl Lib "PwrDeviceDll.dll" ( _
ByRef firmware As Byte) As Long
Dim firmware(31) As Byte
InitPowerDevice firmware(0)
The CDecl keyword only works in a VB6 program compiled to native code. It never works in the IDE or in p-code EXEs.
Since your error is "bad calling convention", you should try changing the calling convention. C code use __cdecl by default, and IIRC VB6 had a Cdecl keyword you could use with Declare Function.
Otherwise you can change the C code to use __stdcall, or create a type library (.tlb) with the type information and calling convention. That might be better than Declare Function because you use the C datatypes when defining the type library, but VB6 recognizes them just fine.
As far as the argument type is concerned, firmware() As Byte with ByVal (not ByRef) should be fine.
My firebreath plugin project has a wrapper class of an active X control.
One of the method takes a BSTR data type variable as input, but when I try to call the method and pass a BSTR, i get an error.
I have even included the "WTypes.h" header file. But that doesnot seem to fix the problem.
So someone suggest an alternative.
short MakeCall (BSTR sNumber,short * nConnectionId );
The Makecall function calls the Makecall function implemented by the active x control by using these paramters
The error
Error 1 error C2665: 'FB::variant_detail::conversion::convert_variant' : none of the 5 overloads could convert all the argument types c:\users\research\downloads\firebreath-firebreath-firebreath-1.6.0rc1-15-g411c7fe\firebreath-firebreath-411c7fe\src\scriptingcore\variant.h 842 1 axWrapper
PS: After i searched about the error i find that fire breath doesnot support wide characters i.e uni code. So is there a way to use the unicode data type in firebreath.
That error indicates that you are trying to convert a FB::variant into a BSTR, which is not something that FB::variant knows how to do. Most likely that means that you're trying to use a BSTR as a type in a method you registered on a JSAPIAuto object. JSAPIAuto will automatically convert types, but you have to use types that it understands, and BSTR is not one of them.
Instead, find whatever method is registered as a JSAPI method and change the BSTR to a std::wstring; then convert the wstring into a BSTR. This should work fine.
FireBreath does support unicode; you can use std::wstring for wide characters and std::string types coming from the browser will be UTF8. You can #include "utf8_tools.h" and use FB::utf8_to_wstring and FB::wstring_to_utf8 to convert between them.
Hope this helps
#include <atlbase.h>
should do the trick
I have this code in my VB6 app:
Private Declare Function FileGetParentFolder Lib "Z-FileIO.dll" _
(ByVal path As String) As String
Output.AddItem FileGetParentFolder(FileText.Text)
Output is a list, FileText is a text field containing a file path. My C++ DLL contains this function:
extern "C" BSTR ZFILEIO_API FileGetParentFolder(Path p)
{
try {
return SysAllocString(boost::filesystem::path(p).parent_path().c_str());
} catch (...) {
return SysAllocString(L"");
}
}
where Path is typedef'd as LPCSTR. The argument comes into my DLL perfectly, but whatever I try to pass back, the VB6 app shows only garbage. I tried several different methods with SysAllocStringByteLength, casting the SysAllocString argument to LPCWSTR and other variants. Either, I only see the first letter of the string, or I see only Y's with dots, just not the real string. Does anyone know what the real method is for creating and passing valid BSTRs from C++ to VB6?
Hopefully this will point you in the right direction. From memory...
VB6 uses COM BSTRs (2-byte wide character strings) internally, but when communicating with external DLLs it uses single- or multi-byte strings. (Probably UTF-8, but I don't remember for sure.) Your Path typedef to LPCSTR is an ANSI string, and that's why you can receive it correctly. The return value you generate is a wide-character string, but VB is expecting an ANSI string. You'll need to use WideCharToMultiByte to convert your return value before returning it.
Seems a little odd that VB does this implicit conversion, but that's the way it is. (As far as I remember.)
If you insist on using the function signature then you have to prepare a custom typelib for VB6 that includes this
[dllname("Z-FileIO.dll")]
module ZFileIO
{
[entry("FileGetParentFolder")]
BSTR FileGetParentFolder ([in] LPWSTR path);
};
In Declares param-types As String are automagically converted to ANSI string, i.e. LPSTR. The only way to pass/receive a unicode string (LPWSTR or BSTR) is by using typelib API function declaration.
Other than that you can always use As Long params in the declaration and expect LPWSTRs but then the consumer will have to wrap strings in StrPtr on every call to the API function.