views:

122

answers:

4

Hi guys, 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.

A: 

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.

Steve Townsend
A: 

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#. :-)

danbystrom
Hi danby,i am sorry but i cant really use List<string^>.Thanks in advance!
Alag20
OK... why not? --dan
danbystrom
+1  A: 

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); 
Hans Passant
object[] array; nullbool ok = GetStringArr(out array);'GetStringArr(out array)' threw an exception of type 'System.AccessViolationException' base {System.SystemException}: {"Attempted to read or write protected memory. This is often an indication that other memory is corrupt."}
Alag20
There's still something wrong with your C++ code. Debug it, enable unmanaged debugging and set a breakpoint on your function.
Hans Passant
But i copied all of your C++ code as you show above and it still doesnt work!
Alag20
Did you modify the C# code as well? You'll need to debug this, I can't help you without a lead. The code as posted worked fine.
Hans Passant
Hi Hans,Thanks for your help. i took your c++ and c# code letter by letter copy and paste from your post to start with to ensure it was working. The only change being i changed blah.dll to myexe.exe so that i was refereing to the correct file. Other than this, there is no difference. How can i try to debug into this? As for the code, i am basically just testing this in a new project with your code to eliminate all other issues!!!
Alag20
myexe.exe? You can't P/Invoke functions in an unmanaged EXE, only a DLL.
Hans Passant
i can - i have done that in past from unmanaged exe to unmanaged exe etc and from managed exe to managed exe before.
Alag20
Sorry, I have to vehemently disagree. There's no problem with a managed EXE but unmanaged code cannot be exported from an EXE. You are probably compiling the C++ code with the /clr option. Which generates managed code. No point in writing code like that in native C++ if you do that, just write C++/CLI code. A ref class. This conversation is derailing pretty badly, I think I'll sign-off now. Good luck with it.
Hans Passant
+1  A: 

Some problems on both the C and .NET side of things

On the C side

  1. Incorrect argument indirection. Since you are allocating the SAFEARRAY descriptor in the function you need a SAFEARRAY**.
  2. 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

  1. The Calling convention needs to be specified otherwise you will have stack issues
  2. You should set the SafeArraySubType
  3. 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);    
  }
Chris Taylor
string[] data;bool b = GetStringArr(out data);The above got the error below.'GetStringArr(out data)' threw an exception of type 'System.Runtime.InteropServices.SEHException' base {System.Runtime.InteropServices.ExternalException}: {"External component has thrown an exception."}Thanks in advance for your help!
Alag20
That means that something on the C side threw an exception (structured exception) which was translated to SEHException. Without the code it is difficult to guess what that exception might be. I have the above code in a test project and can confirm that it is running correctly, do you have something different? You should also provide the infomation contained in the SEHException, this might help.
Chris Taylor
Can you please attach a copy of your code at both ends please? i have just used the exact code without any mods in your code and it gave me that exception.The only difference is i am calling my dll a exe.
Alag20
@Alag20, you can download a test project from my skydrive - `http://cid-5bb13275dc6b8248.office.live.com/self.aspx/StackOverflow%20Share/SafeArrayTest.zip`
Chris Taylor
Thanks Chris. i am downloading it now. Will let you know soon!!!!!
Alag20
Chris, i downloaded this but it seems you have made this using the latest VS2010 where as i am still have VS 2005. Anywys i will try to recreate this in the morning in VS2005 hopefully.
Alag20