C++ dll pass array to C# - c#

I have to pass from my C++ dll array to the C# program.
Here is the C++ code:
__declspec(dllexport) std::vector<std::string> __stdcall
getFilesByDirs
(
string directory,
std::string fileFilter,
bool recursively = true
)
{
int __ID = 0;
std::vector<std::string> filesArray;
if (recursively)
getFilesByDirs(directory, fileFilter, false);
directory += "\\";
WIN32_FIND_DATAA FindFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
std::string filter = directory + (recursively ? "*" : fileFilter);
hFind = FindFirstFileA(filter.c_str(), &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
return filesArray;
}
else
{
if (!recursively)
{
if(isGoodForUs(directory + std::string(FindFileData.cFileName)))
{
filesArray[__ID] = directory + std::string(FindFileData.cFileName);
__ID ++;
}
}
while (FindNextFileA(hFind, &FindFileData) != 0)
{
if (!recursively)
{
if(!isGoodForUs(directory + std::string(FindFileData.cFileName))) continue;
filesArray[__ID] = directory + std::string(FindFileData.cFileName);
}
else
{
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)>0 && FindFileData.cFileName[0]!='.')
{
if(!isGoodForUs(directory + std::string(FindFileData.cFileName))) continue;
getFilesByDirs(directory + std::string(FindFileData.cFileName), fileFilter);
}
}
__ID ++;
}
DWORD dwError = GetLastError();
FindClose(hFind);
}
return filesArray;
}
As you can see, there is a function that scanning for files by type.
It's saved in the string array and returning out.
Now, here is the C# code:
[DllImport(#"files.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr getFilesByDirs(string path, string exns, bool rec=true);
and then i am calling to this method:
IntPtr a = getFilesByDirs("C:\\", "*.docx");
The problem is that nothing pass to my C# program from the Dll.
Anyone can help with my issue?

You need to return them not as a vector but as a primitive type, such as a char**. The problem here is that, in order to consume it you need to know how many are in the collection. As Ed. S said in the comments, you may find it easier to work with CLI (if you can compile your C++ with /clr on). But if you can do that, then you can use .Net types in C++ (such as List) and marshall your char*'s to Strings on the C++ side.
Otherwise, you can return the raw char*'s an handle it on the .Net side. To consume the char*'s in C# (and maybe having done C++, you will feel comfortable with this feature) you can use the unsafe keyword so you can convert those char*'s to System::String's in unsafe and add them to your .Net collections.

Related

type conversion problem for marshaling datatypes from C# to C++

I'm currently working on an C# (.NET Framework 4.7.2) application using some business logic from an unmanaged C++ library. I try to pass data (interop) back and forth from C# to C++. I may not use C++/CLI, no common language runtime allowed in my project.
It works fine for int. Unfortunately as soon as I try to send another datatype I'm getting an conversion error e.g. float 4.2f becomes 1 and string "fourtytwo" turns into -1529101360.
My C# code looks like this:
// works fine, creates an instance of TestClass
var test = TestProxy.Wrapper_Create("test");
// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);
// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);
// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");
My C# Interop Proxy class to call the native (unmanaged) C++ code looks like this:
public static class TestProxy
{
private const string coreDLL = "test.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrapper_Create(string name);
[DllImport(coreDLL, EntryPoint = "?TryInt#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryInt(IntPtr instance, int n);
[DllImport(coreDLL, EntryPoint = "?TryFloat#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryFloat(IntPtr instance, float n);
[DllImport(coreDLL, EntryPoint = "?TryString#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryString(IntPtr instance, string n);
}
My native (unmanaged) C++ looks like that:
the header file:
#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif
#pragma once
extern "C"
{
class TESTCORE_API TestClass
{
private:
char* name;
public:
TestClass(char*);
int TryInt(int);
float TryFloat(float);
char* TryString(char*);
};
TESTCORE_API TestClass* Wrapper_Create(char* name);
}
the implementation file:
#include "stdafx.h"
#include "TESTCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
int TestClass::TryInt(int n)
{
return n; // works fine
}
float TestClass::TryFloat(float n)
{
return n; // something goes wrong here
}
char* TestClass::TryString(char* n)
{
return n; // something goes wrong here
}
extern "C"
{
TESTCORE_API TestClass * Wrapper_Create(char* name)
{
return new TestClass(name);
}
TESTCORE_API int TryInt(TestClass * instance, int n)
{
if (instance != NULL)
{
return instance->TryInt(n);
}
}
TESTCORE_API float TryFloat(TestClass * instance, float n)
{
if (instance != NULL)
{
return instance->TryFloat(n);
}
}
TESTCORE_API char* TryString(TestClass * instance, char* n)
{
if (instance != NULL)
{
return instance->TryString(n);
}
}
}
Do you know how to correctly marshal float, string from C# to C++ and back?
Thank you!
C++ doesn't have standard ABI. It's rarely a good idea to use C++ classes across DLLs, even when you have same language on both sides.
There're better ways.
Replace your __thiscall class methods with global functions, cdecl or stdcall whichever you like (but note C# and C++ have different defaults, if you'll do nothing C++ will use cdecl, C# will import as stdcall). You can pass "this" pointer of the class in the first argument, IntPtr in C#, just like you're doing now. Also if you'll write extern "C" or use a module definition file, they will have human-readable names.
If you want objects, use COM. Declare an interface that inherits from IUnknown, implement it in C++ (I usually use ATL), and export a global function to create an instance of that object (2 lines in ATL, CComObject<T>::CreateInstance followed by AddRef). No need to register, type libraries, you just need to implement IUnknown (but see this if you want to use them from multiple threads)
Update: strings are indeed harder. Apply [MarshalAs(UnmanagedType.LPTStr)] to the argument. Apply [return: MarshalAs(UnmanagedType.LPTStr)] to the function. Specify PreserveSig=true in your DllImport. Finally, modify the C++ code to return a copy of the string, i.e. call strlen then CoTaskMemAlloc (don't forget about the '\0') then strcpy.
Easier way to deal with strings is like this:
HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )
At least there're CComBSTR and _bstr_t built-in classes to deal with memory management.

String Parameter not Being Marshalled Correctly to C++ DLL

I have been provided with a DLL which is to be called by C#. The DLL contains two methods as follows
extern "C" {
__declspec(dllexport) BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr);
}
BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr) {
CString strResult = "";
char* sz;
::SetVars(bDiagErr, bProcErr);
if (sz = ::GroupInit((char*)bstrIniFile, 1))
strResult = sz;
return strResult.AllocSysString();
}
I am attempting to call these DLLs from C# by first defining the class:
[DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GroupInit(
string strCmdFile,
bool bAllowBadDiagCodes,
bool bAllowBadProcCodes
);
and doing
this.strCommandFilePath = "C:\\MyDir\\MyCommandFile.txt";
string s = Grouper.GrouperServer.GroupInit(this.strCommandFilePath, true, true);
But the DLL is returning the error: 'Cannot find command file: "C"' (the first character of the path only, which I have checked in the C++ DLL). For some reason the string this.strCommandFilePath is not being passed into the C++ method correctly.
What is wrong with the above call?
Edit to address comments.
The method being called in the if (sz = ::GroupInit((char*)bstrIniFile, 1)) statement is defined in a .c file and has the signature
char *GroupInit(char *szCmd, int iType)
{
...
}
It is a mistake to use TCHAR and related types here. The use case of TCHAR is for code that needs to compile for both Windows 9x which has no Unicode support, and Windows NT which does. Those days are long gone and TCHAR is obscuring the problem. What's more, the underlying code uses char* so it makes little sense to pretend that your wrapper code can do anything else. So switch to char.
On top of that you are casting away const. I guess because the function you call accepts a modifiable buffer for a parameter that it does not modify. Best solution is to fix the original library code that erroneously accepts char* and make it accept const char*. If you cannot do that then you'll need to cast away the const. But do that the C++ way with const_cast<>.
So, I'd have the C++ code like this:
BSTR GroupInit(const char* szIniFile, bool bDiagErr, bool bProcErr) {
CString strResult = "";
char* sz;
::SetVars(bDiagErr, bProcErr);
if (sz = ::GroupInit(const_cast<char*>(szIniFile), 1))
strResult = sz;
return strResult.AllocSysString();
}
And the C# code should be:
[DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GroupInit(
string strCmdFile,
bool bAllowBadDiagCodes,
bool bAllowBadProcCodes
);
Now, one wonders what happens to sz. Who is expected to deallocate that? Does it even need to be deallocated? Only you can answer those questions.

CLR Hosting: Call a function with an arbitrary method signature?

I need to take a C++ program, load CLR and call a function in a C# library. The function I need to call takes in a COM interface as the parameter.
My problem is, the CLR hosting interface only seems to let you call a method with this signature:
int Foo(String arg)
Example, this C++ code loads CLR and runs the P.Test function in "test.exe":
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(L"C:\\Test.exe", L"P", L"Test", L"", &retVal);
What I need to do is call a function with this method signature (note I own the C# code, so I can change it):
void SomeFunction(IFoo interface)
Where IFoo is a com interface. I could even do what I need to if I could call a function like this:
IntPtr SomeFunction();
I could have SomeFunction construct a correct delegate then use Marshal.GetFunctionPointerForDelegate. However, I can't figure out how to make the hosting interfaces do anything other than call a function with an int func(string) signature.
Does anyone know how to call a C# function from C++ code with a different signature?
(Note I cannot use C++/CLI for this. I need to use the hosting APIs.)
Edit: I promised to update my answer to include the code for passing 64-bit values, so here's goes..
I left the original answer if someone's interested in a less complicated solution for a 32-bit system.
Note: Since you're using CorBindToRuntimeEx, which is obsolete in .net 4.0, I'll assume a .net 2.0 compliant solution using good old Win32 API.
So, in order to pass data between C# and C++ (in our case - the IntPtr of a delegate), we'll create a small Win32 DLL project, named SharedMem, with two straight-forward methods.
SharedMem.h
#pragma once
#ifdef SHAREDMEM_EXPORTS
#define SHAREDMEM_API __declspec(dllexport)
#else
#define SHAREDMEM_API __declspec(dllimport)
#endif
#define SHAREDMEM_CALLING_CONV __cdecl
extern "C" {
SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV SetSharedMem(ULONGLONG _64bitValue);
SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV GetSharedMem(ULONGLONG* p64bitValue);
}
Now for the implementation file:
SharedMem.cpp
#include "stdafx.h"
#include "SharedMem.h"
HANDLE hMappedFileObject = NULL; // handle to mapped file
LPVOID lpvSharedMem = NULL; // pointer to shared memory
const int SHARED_MEM_SIZE = sizeof(ULONGLONG);
BOOL CreateSharedMem()
{
// Create a named file mapping object
hMappedFileObject = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
SHARED_MEM_SIZE,
TEXT("shmemfile") // Name of shared mem file
);
if (hMappedFileObject == NULL)
{
return FALSE;
}
BOOL bFirstInit = (ERROR_ALREADY_EXISTS != GetLastError());
// Get a ptr to the shared memory
lpvSharedMem = MapViewOfFile( hMappedFileObject, FILE_MAP_WRITE, 0, 0, 0);
if (lpvSharedMem == NULL)
{
return FALSE;
}
if (bFirstInit) // First time the shared memory is accessed?
{
ZeroMemory(lpvSharedMem, SHARED_MEM_SIZE);
}
return TRUE;
}
BOOL SetSharedMem(ULONGLONG _64bitValue)
{
BOOL bOK = CreateSharedMem();
if ( bOK )
{
ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem;
*pSharedMem = _64bitValue;
}
return bOK;
}
BOOL GetSharedMem(ULONGLONG* p64bitValue)
{
if ( p64bitValue == NULL ) return FALSE;
BOOL bOK = CreateSharedMem();
if ( bOK )
{
ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem;
*p64bitValue = *pSharedMem;
}
return bOK;
}
Note that for simplicity I'm just sharing a 64-bit value, but this is a general way of sharing memory between C# and C++. Feel free to enlarge SHARED_MEM_SIZE and/or add functions in order to share other data types as you see fit.
This is how we'll consume the above methods: we'll use SetSharedMem() on the C# side in order to set the delegate's IntPtr as a 64-bit value (regardless if the code runs on a 32 or a 64 bit system).
C# Code
namespace CSharpCode
{
delegate void VoidDelegate();
static public class COMInterfaceClass
{
[DllImport( "SharedMem.dll" )]
static extern bool SetSharedMem( Int64 value );
static GCHandle gcDelegateHandle;
public static int EntryPoint(string ignored)
{
IntPtr pFunc = IntPtr.Zero;
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
bool bSetOK = SetSharedMem( pFunc.ToInt64() );
return bSetOK ? 1 : 0;
}
public static void SomeMethod()
{
MessageBox.Show( "Hello from C# SomeMethod!" );
gcDelegateHandle.Free();
}
}
}
Note the use of GCHandle in order to prevent the delegate from being garbage-collected.
For good measures, we'll use the return value as a success/failure flag.
On the C++ side we'll extract the 64-bit value using GetSharedMem(), convert it to a function pointer and invoke the C# delegate.
C++ Code
#include "SharedMem.h"
typedef void (*VOID_FUNC_PTR)();
void ExecCSharpCode()
{
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL,
L"wks",
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost
);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
szPathToAssembly,
L"CSharpCode.COMInterfaceClass",
L"EntryPoint",
L"",
&retVal // 1 for success, 0 is a failure
);
if ( hrExecute == S_OK && retVal == 1 )
{
ULONGLONG nSharedMemValue = 0;
BOOL bGotValue = GetSharedMem(&nSharedMemValue);
if ( bGotValue )
{
VOID_FUNC_PTR CSharpFunc = (VOID_FUNC_PTR)nSharedMemValue;
CSharpFunc();
}
}
}
The Original Answer - Good for 32-bit Systems
Here's a solution that is based on using IntPtr.ToInt32() in order to convert the delegate func. ptr. to the int which is returned from the static C# EntryPoint method.
(*) Note the use of GCHandle in order to prevent the delegate from being garbage-collected.
C# Code
namespace CSharpCode
{
delegate void VoidDelegate();
public class COMInterfaceClass
{
static GCHandle gcDelegateHandle;
public static int EntryPoint(string ignored)
{
IntPtr pFunc = IntPtr.Zero;
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
return (int)pFunc.ToInt32();
}
public static void SomeMethod()
{
MessageBox.Show( "Hello from C# SomeMethod!" );
gcDelegateHandle.Free();
}
}
}
C++ Code
We'll need to convert the returned int value to a function pointer, so we'll start off by defining a void function ptr. type:
typedef void (*VOID_FUNC_PTR)();
And the rest of the code looks pretty much like your original code, with the addition of converting and executing the function ptr.
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL,
L"wks",
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost
);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
szPathToAssembly,
L"CSharpCode.COMInterfaceClass",
L"EntryPoint",
L"",
&retVal
);
if ( hrExecute == S_OK )
{
VOID_FUNC_PTR func = (VOID_FUNC_PTR)retVal;
func();
}
A Little Bit of Extra
You can also make use of the string input in order to choose which method to execute:
public static int EntryPoint( string interfaceName )
{
IntPtr pFunc = IntPtr.Zero;
if ( interfaceName == "SomeMethod" )
{
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
}
return (int)pFunc.ToInt32();
}
You can get even more generic by using reflection in order to find the correct method according to the given string input.

C# unmanaged function calls, Possible data loss, Ascii Vs. Unicode, Chinese folder names

So here is the deal, Im working a C# application that calls into a legacy C++ dll, which in turns loops through a directory pulling back the names of certain directories, i.e directories that have .lib, I have a directory with the following 3 folders: Default.Lib,中文文本帧的文件.lib,我们的.lib.
As you can see we have some chinese folder names, a string is built in memory by the c++ code as you can see below, it use strcat to build it in memory. however when control is returned back to the c# code, it appears part of that data is lost and the only two folders left are the first two. Default.Lib,中文文本帧的文件.lib, something with 我们的.lib gets lost in translation, I would greatly appreciate any insights anyone may have. thanks.
C# code snippet
lock (padLock)
{
ConnectSign(service);
int size = MaxFileListSize * 100;
byte[] mem = new byte[size];
string finalList;
int used = size;
int fileCount = 0;
string library = "*";
string extension = "*";
V7_FILE_LIST_TYPE type = V7_FILE_LIST_TYPE.LibraryList;
fixed (byte* listbytes = mem)
{
int error = NativeMethods.GetFileDirInfo(sign, type, fileServer, library, extension, &fileCount, listbytes, &used);
if (error != 0)
throw new V7ResponseException(error, sign, service, "GetFileDirInfo");
}
finalList = Encoding.Default.GetString(mem, 0, (int)used);
string[] libraryArray = finalList.Split(new char[] { '\n', '\0' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < libraryArray.Length; i++)
{
int index = libraryArray[i].LastIndexOf(".lib", StringComparison.OrdinalIgnoreCase);
if (index > 0)
libraryArray[i] = libraryArray[i].Substring(0, index);
//libraryArray[i] = libraryArray[i].Trim().ToLower(CultureInfo.CurrentCulture).Replace(".lib", string.Empty);
}
return libraryArray;
}
[DllImport("V7SSRpc.dll", CharSet = CharSet.Ansi, EntryPoint = "V7ssGetFileDirInfo", BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern int GetFileDirInfo(string sign, V7_FILE_LIST_TYPE type, string fileServer, string library, string extension, int* fileCount, byte* files, int* bytesUsed);
*****************************C++ DLL code--------------------------------------
//--------------------------------------------------------------------
// RETURN :
//
// PARAMS : eListType
// szServer
// szLib
// szExt
// *pdwFileCnt
// *pbyFileBuf
// *pdwFileBufSize
//
// REMARKS:
//
BOOL CVSign::apiGetFileDirInfo(V7_FILE_LIST_TYPE eListType, LPCSTR szServer, LPCSTR szLib, LPCSTR szExt,
DWORD *pdwFileCnt, char *pbyFileBuf, DWORD *pdwFileBufSize) const
{
BOOL bReturn=TRUE;
CString sServer(szServer);
CString sLib(szLib);
CString sExt(szExt);
CString sFileInfo, sTemp;
CStringArray asFiles;
CFileStatus status;
CV7Files V7Files;
DWORD dwBufUsed=0;
// SOME OTHER LOGIC (not posted)
USES_CONVERSION;
//CoInitialize(NULL);
//AVIFileInit();
CString sFilePath;
CV7SequenceFile V7Seq;
CV7FileInfo fileInfo;
// go through list of files and build the buffer with file names and other info
for (nFile=0; nFile<nFiles; nFile++)
{
// MORE OBSCURED LOGIC
sFileInfo += _T("\n");
// add file info to buffer
int nLen = sFileInfo.GetLength();
if (dwBufUsed+nLen<*pdwFileBufSize)
{
strcat(pbyFileBuf, T2CA(sFileInfo)); //<--- THIS IS THE IMPORTANT PART
int nTemp = sFileInfo.GetLength();
dwBufUsed += nTemp;
}
else
{
*pdwFileBufSize = 0;
AVIFileExit();
CoUninitialize();
return FALSE;
}
} // end for files
//AVIFileExit();
//CoUninitialize();
*pdwFileBufSize = dwBufUsed;
return bReturn;
} // end apiGetFileDirInfo()
I suspect the problem is that you're specifying Charset=CharSet.Ansi, so the default marshaling behavior is to convert the returned string to ANSI. That's going to cause a problem.
You probably want to specify string Charset=CharSet.Unicode, and possibly specify custom marshaling for some strings. See http://msdn.microsoft.com/en-us/library/s9ts558h.aspx#cpcondefaultmarshalingforstringsanchor5 for information on how to change the string marshaling behavior for individual parameters.

trouble using unmanaged c++ from c# using dllimport

i am having trouble importing c++ unmanaged dll into C# [winform]. Can someone help?
Basically i am just trying to create a safearray of strings in c++ and trying to send it to C#.
Here is my c++ code.
extern "C" __declspec(dllexport) BOOL GetStringArr(SAFEARRAY* arr)
{
SAFEARRAY* myArray;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = 5;
myArray = SafeArrayCreate(VT_BSTR, 1, rgsabound);
VARIANT* pvData = (VARIANT*)(myArray->pvData);
pvData[0].vt = VT_BSTR;
pvData[0].bstrVal = SysAllocString(L"FirstString");
pvData[1].vt = VT_BSTR;
pvData[1].bstrVal = SysAllocString(L"SecondString");
pvData[2].vt = VT_BSTR;
pvData[2].bstrVal = SysAllocString(L"ThirdString");
pvData[3].vt = VT_BSTR;
pvData[3].bstrVal = SysAllocString(L"FourthString");
pvData[4].vt = VT_BSTR;
pvData[4].bstrVal = SysAllocString(L"FifthString");
arr = myArray;
return true;
}
Here is my c# code.
[DllImport("MyData.dll", EntryPoint = "GetStringArr")]
public static extern bool GetStringArr([MarshalAs(UnmanagedType.SafeArray)] out Array strServerList);
i am getting exception when i call GetStringArr from C#. i am sure there is something silly i am doing. Can someone please help?
Thanks in advance.
Several problems in your C++ code. You are returning an array, that requires the argument to be SAFEARRAY**. You also are stuffing the array with the wrong data, you created an array of strings but you are writing VARIANTs. Not sure what the intention was, I'll keep variants in the code fix:
extern "C" __declspec(dllexport) BOOL GetStringArr(SAFEARRAY** arr)
{
SAFEARRAY* myArray;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = 5;
myArray = SafeArrayCreate(VT_VARIANT, 1, rgsabound);
VARIANT* pvData = 0;
SafeArrayAccessData(myArray, (void**)&pvData);
pvData[0].vt = VT_BSTR;
pvData[0].bstrVal = SysAllocString(L"FirstString");
// etc..
SafeArrayUnaccessData(myArray);
*arr = myArray;
return true;
}
C# code:
object[] array;
bool ok = GetStringArr(out array);
[DllImport(#"blah.dll", EntryPoint = "GetStringArr")]
[return: MarshalAs(UnmanagedType.U1)]
public static extern bool GetStringArr([MarshalAs(UnmanagedType.SafeArray)] out object[] strServerList);
Some problems on both the C and .NET side of things
On the C side
Incorrect argument indirection. Since you are allocating the SAFEARRAY descriptor in the function you need a SAFEARRAY**.
The SAFEARRAY is not being filled correctly. You created the SAFEARRAY descriptor with a base type of VT_BSTR, this means that the data elements should be BSTRs.
C Code
extern "C" __declspec(dllexport)
BOOL GetStringArr(SAFEARRAY** arr)
{
SAFEARRAY* myArray;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = 5;
myArray = SafeArrayCreate(VT_BSTR, 1, rgsabound);
BSTR* pvData = (BSTR*)(myArray->pvData);
pvData[0] = SysAllocString(L"FirstString");
pvData[1] = SysAllocString(L"SecondString");
pvData[2] = SysAllocString(L"ThirdString");
pvData[3] = SysAllocString(L"FourthString");
pvData[4] = SysAllocString(L"FifthString");
*arr = myArray;
return true;
}
On the .NET side
The Calling convention needs to be specified otherwise you will have stack issues
You should set the SafeArraySubType
You can use out string[] to get the pointer to the SAFEARRAY
.NET Code
class Program
{
static void Main(string[] args)
{
string[] data;
bool b = GetStringArr(out data);
}
[DllImport("MyData.dll",
CallingConvention = CallingConvention.Cdecl)]
public static extern bool GetStringArr(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)]
out string[] strServerList);
}
Do you have access to the native DLL's source? If so you can enable unmanaged debugging in your Managed projects options, and step thru the unmanaged code (preferably Debug build) to see what's going on. If nothing else you can enable Exceptions in the debugger options, and debug to see where the native exception gets thrown.
I recommend you add a C++/CLI project (assembly) to your C# solution. That will enable you to write code that live in both managed and unmanaged land simultaneously. That means that your C++/CLI code can create a List<string^> instead and add managed strings to it before you return it to C#. :-)

Categories

Resources