I have spent the last day searching documentation, reviewing forum posts, and googling to try to do something that I am guessing could be easily done with the right information.
I have a very large existing C++ application that has an already defined COM server with many methods exposed. I am trying to use those COM methods in a C# application (I am experienced in C++, but a C# newbie).
So in my VS2010 C# application, I add the COM server as a reference. COM Methods are visible in the object browser, and passing single-valued strings, floats, and ints seems to work fine.
But I am stumped trying to read SAFEARRAY values passed out of the C++ COM server into the C# application. Eventually I need to pass string arrays from C++ server to the C# app, but in just testing passing an array of floats, I have code that builds but fails with the following exception when I try to cast the System.Object containing the array of floats to (float[]) ,
"exception {System.InvalidCastException: Unable to cast object of type 'System.Object[]' to type 'System.Single[]'."
With intellisence I can see that the Object contains the correct 8760 long array of floats, but I am unable to access that data in C#.
Here is the code on the C# side (d2RuleSet is an interface defined in the DOE2Com COM server). The exception above is thrown on the last line below.
DOE2ComLib.DOE2Com d2RuleSet;
d2RuleSet = new DOE2ComLib.DOE2Com();
System.Int32 i_Series =0;
System.Object pv_WeatherData;
float[] faWeatherData;
iOut = d2RuleSet.GetWeatherData(i_Series, out pv_WeatherData);
Type typeTest;
typeTest = pv_WeatherData.GetType();
int iArrayRank = typeTest.GetArrayRank();
Type typeElement = typeTest.GetElementType();
faWeatherData = (float[])pv_WeatherData;
Below is the section in the idl file defining the C++ COM method
HRESULT GetWeatherData( [in] int iSeries, [out] VARIANT* pvWeatherData, [out,retval] int * piErrorCode);
Below is the C++ code where the VARIANT data is loaded.
void CDOE2BaseClass::GetWeatherData( int iSeries, VARIANT* pvWeatherData, int* piErrorCode)
{
*piErrorCode = 0;
if (iSeries < 0 || iSeries >= D2CWS_NumSeries)
*piErrorCode = 1;
else if (m_faWeatherData[iSeries] == NULL)
*piErrorCode = 3;
else
{
SAFEARRAYBOUND rgsaBound;
rgsaBound.lLbound = 0;
rgsaBound.cElements = 8760;
// First lets create the SafeArrays (populated with VARIANTS to ensure compatibility with VB and Java)
SAFEARRAY* pSAData = SafeArrayCreate( VT_VARIANT, 1, &rgsaBound );
if( pSAData == NULL ) {
#ifndef _DOE2LIB
_com_issue_error( E_OUTOFMEMORY);
#else
//RW_TO_DO - Throw custom Lib-version exception
OurThrowDOE2LibException(-1,__FILE__,__LINE__,0,"OUT OF MEMORY");
#endif //_DOE2LIB
}
for (long hr=0; hr<8760; hr++)
{
COleVariant vHrResult( m_faWeatherData[iSeries][hr] );
SafeArrayPutElement( pSAData, &hr, vHrResult );
}
// Now that we have populated the SAFEARRAY, assign it to the VARIANT pointer that we are returning to the client.
V_VT( pvWeatherData ) = VT_ARRAY | VT_VARIANT;
V_ARRAY( pvWeatherData ) = pSAData;
}
}
Thanks in advance for help with this problem, I feel like I spent too much time on what should be a simple problem. Also please post up any links or books that that cover interop between native C++ and C# well (I think I have already ping-ponged through most of the Visual Studio/MSDN documentation, but maybe I missed something there too).
-----------------End Of Original Question------------------------------------------------------
I am editing to post the code from phoog's successful solution below, so others can read and use it.
int iOut = 0;
System.Int32 i_Series =0;
System.Object pv_WeatherData = null;
iOut = d2RuleSet.GetWeatherData(i_Series, out pv_WeatherData);
Type typeTest;
typeTest = pv_WeatherData.GetType();
int iArrayRank = typeTest.GetArrayRank();
Type typeElement = typeTest.GetElementType();
//float[] faWeatherData = (float[])pv_WeatherData;
float[] faWeatherData = ConvertTheArray((object[])pv_WeatherData);
....
float[] ConvertTheArray(object[] inputArray)
{
float[] result = new float[inputArray.Length];
for (var index = 0; index < result.Length; index++)
result[index] = (float)inputArray[index];
return result;
}
The marshalled SAFEARRAY is a System.Object[]; you can't reference-convert that to a System.Single[]. You have to cast the individual elements. This would work, assuming that all the elements of the argument array are in fact boxed floats:
float[] ConvertTheArray(object[] inputArray)
{
float[] result = new float[inputArray.Length];
for (var index = 0; index < result.Length; index++)
result[index] = (float)inputArray[index];
return result;
}
You could do that with a lot less typing using Linq, but as you're a C# newbie I thought a more basic solution might be better.
EDIT
Since you indicated in your comment that your object[] is referenced as an object, here's the usage example:
object obj = GetMarshalledArray();
float[] floats = ConvertTheArray((object[])obj);
EDIT 2
A shorter solution using Linq:
float[] ConvertTheArray(object[] inputArray)
{
return inputArray.Cast<float>().ToArray();
}
Related
I'm trying to copy an array of floats from my C# application to an array in a C-coded DLL.
Im used to programming in C#, not so much with C. However I have no problem doing the reverse procedure ie. reading an array of floats from a C coded DLL into my C# application. I've read several threads on this site but cant work out where Im going wrong.
C# CODE
[DllImport(#"MyDll")]
static extern int CopyArray(double[] MyArray);
double[] myValues = new double[100]
int a = 0;
while(a < 100)
{
myValues[a] = a;
a++;
}
CopyArray(myValues);
C++ DLL
This is the function header;
__declspec(dllexport) int CopyArray(float* Wavelengths);
This is the function code;
float theValues[100];
int CopyArray(float* theArray)
{
status = 0;
int count = 0;
while (count < 100)
{
theValues[count] = theArray[count];
++count;
}
return(status);
}
I'm expecting my C# array to end up in the C array "theValues" but that's not happening. There is nothing getting into "theValues" array.
A couple of things.
You are mixing data types and they are different lengths (float is 32bit and double is 64bit). Both types exist in both languages, but your caller and callee need to agree on the data type. Here is a list of data types and their managed/unmanaged equivalents.
The parameter you are sending is not a pointer. It might be translated to that automatically by the compiler, but there are several options. It is entirely possible that the compiler will pick one you don't want (more info here). The one you are probably looking for is [In]. If you want to get data back from C to C#, you will also want [Out]:
[DllImport(#"MyDll")]
static extern int CopyArray([In, Out]double[] MyArray);
Building off of my marshalling helloworld question, I'm running into issues marshalling an array allocated in C to C#. I've spent hours researching where I might be going wrong, but everything I've tried ends up with errors such as AccessViolationException.
The function that handles creating an array in C is below.
__declspec(dllexport) int __cdecl import_csv(char *path, struct human ***persons, int *numPersons)
{
int res;
FILE *csv;
char line[1024];
struct human **humans;
csv = fopen(path, "r");
if (csv == NULL) {
return errno;
}
*numPersons = 0; // init to sane value
/*
* All I'm trying to do for now is get more than one working.
* Starting with 2 seems reasonable. My test CSV file only has 2 lines.
*/
humans = calloc(2, sizeof(struct human *));
if (humans == NULL)
return ENOMEM;
while (fgets(line, 1024, csv)) {
char *tmp = strdup(line);
struct human *person;
humans[*numPersons] = calloc(1, sizeof(*person));
person = humans[*numPersons]; // easier to work with
if (person == NULL) {
return ENOMEM;
}
person->contact = calloc(1, sizeof(*(person->contact)));
if (person->contact == NULL) {
return ENOMEM;
}
res = parse_human(line, person);
if (res != 0) {
return res;
}
(*numPersons)++;
}
(*persons) = humans;
fclose(csv);
return 0;
}
The C# code:
IntPtr humansPtr = IntPtr.Zero;
int numHumans = 0;
HelloLibrary.import_csv(args[0], ref humansPtr, ref numHumans);
HelloLibrary.human[] humans = new HelloLibrary.human[numHumans];
IntPtr[] ptrs = new IntPtr[numHumans];
IntPtr aIndex = (IntPtr)Marshal.PtrToStructure(humansPtr, typeof(IntPtr));
// Populate the array of IntPtr
for (int i = 0; i < numHumans; i++)
{
ptrs[i] = new IntPtr(aIndex.ToInt64() +
(Marshal.SizeOf(typeof(IntPtr)) * i));
}
// Marshal the array of human structs
for (int i = 0; i < numHumans; i++)
{
humans[i] = (HelloLibrary.human)Marshal.PtrToStructure(
ptrs[i],
typeof(HelloLibrary.human));
}
// Use the marshalled data
foreach (HelloLibrary.human human in humans)
{
Console.WriteLine("first:'{0}'", human.first);
Console.WriteLine("last:'{0}'", human.last);
HelloLibrary.contact_info contact = (HelloLibrary.contact_info)Marshal.
PtrToStructure(human.contact, typeof(HelloLibrary.contact_info));
Console.WriteLine("cell:'{0}'", contact.cell);
Console.WriteLine("home:'{0}'", contact.home);
}
The first human struct gets marshalled fine. I get the access violation exceptions after the first one. I feel like I'm missing something with marshalling structs with struct pointers inside them. I hope I have some simple mistake I'm overlooking. Do you see anything wrong with this code?
See this GitHub gist for full source.
// Populate the array of IntPtr
This is where you went wrong. You are getting back a pointer to an array of pointers. You got the first one correct, actually reading the pointer value from the array. But then your for() loop got it wrong, just adding 4 (or 8) to the first pointer value. Instead of reading them from the array. Fix:
IntPtr[] ptrs = new IntPtr[numHumans];
// Populate the array of IntPtr
for (int i = 0; i < numHumans; i++)
{
ptrs[i] = (IntPtr)Marshal.PtrToStructure(humansPtr, typeof(IntPtr));
humansPtr = new IntPtr(humansPtr.ToInt64() + IntPtr.Size);
}
Or much more cleanly since marshaling arrays of simple types is already supported:
IntPtr[] ptrs = new IntPtr[numHumans];
Marshal.Copy(humansPtr, ptrs, 0, numHumans);
I found the bug by using the Debug + Windows + Memory + Memory 1. Put humansPtr in the Address field, switched to 4-byte integer view and observed that the C code was doing it correctly. Then quickly found out that ptrs[] did not contain the values I saw in the Memory window.
Not sure why you are writing code like this, other than as a mental exercise. It is not the correct way to go about it, you are for example completely ignoring the need to release the memory again. Which is very nontrivial. Parsing CSV files in C# is quite simple and just as fast as doing it in C, it is I/O bound, not execute-bound. You'll easily avoid these almost impossible to debug bugs and get lots of help from the .NET Framework.
I need to pass an array of int or long (doesn't matter) from a VB6 application to a C# COM Visible class. I've tried declaring the interface in C# like this:
void Subscribe([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)]int[] notificationTypes)
void Subscribe(int[] notificationTypes)
But both of them raised a Function or interface markes as restricted, or the function uses an Automation type not supported in Visual Basic.
How should I declare the C# method?
If you get desperate, code the signature in a dummy VB6 ActiveX dll project. Then generate the .NET Interop version of the vb6 component via Visual studio or the command line tool. Then use Reflector or dotPeek to pull the code out of the interop assembly. It is the long way around, but it works.
I ran into this problem 9 years later. The solution I came up with is to pass a pointer to the first element of the array, along with the upper bound (UBound in VB6). Then iterate the pointer to the upper bound and put each element into a list.
on the vb6 side use
Dim myarray(3) As float
Dim ptr As integer
Dim upperbound as integer
myarray(0) = 0.1
myarray(1) = 0.2
myarray(2) = 0.3
myarray(3) = 0.4
ptr = VarPtr(myarray(0))
upperbound = UBound(myarray)
SetMyFloat(ptr, upperbound)
C# code
public float MyFloat {get; set;}
public unsafe void SetMyFloat(float* ptr, int ubound)
{
MyFloat = PointerToFloatArray(ptr, ubound);
}
public unsafe float[] PointerToFloatArray(float* ptr, int ubound)
//this is to deal with not being able to pass an array from vb6 to .NET
//ptr is a pointer to the first element of the array
//ubound is the index of the last element of the array
{
List<float> li = new List<float>();
float element;
for (int i = 0; i <= ubound; i++)
{
element = *(ptr + i);
li.Add(element);
}
return li.ToArray();
}
I can pass a C# struct into FORTRAN just fine. I can even pass an array of a C# struct as an array of TYPE() in FORTRAN. Where I run into trouble is when I tried to return values back into C#. Here is an example:
The fortran dll is:
MODULE TESTING
TYPE VALUEREF
INTEGER*4 :: A
ENDTYPE VALUEREF
CONTAINS
SUBROUTINE TEST_REF(T,N)
!DEC$ ATTRIBUTES DLLEXPORT :: TEST_REF
!DEC$ ATTRIBUTES ALIAS:'TEST_REF' :: TEST_REF
!DEC$ ATTRIBUTES VALUE :: N
IMPLICIT NONE
INTEGER*4 :: A,I,N
TYPE(VALUEREF) :: T(N)
A = 100
DO I=1,N
T(I)%A = A + I
END DO
END SUBROUTINE
END MODULE
and the C# calling function that expects results is:
[StructLayout(LayoutKind.Sequential)]
public struct ValueRef
{
public int a;
}
[DllImport("mathlib.dll")]
static extern void TEST_REF(ValueRef[] t, int n);
void Main()
{
ValueRef[] T = new ValueRef[4];
for (int i = 0; i < T.Length; i++)
{
T[i].a = i;
}
Console.WriteLine("Initialize");
for (int i = 0; i < T.Length; i++)
{
Console.WriteLine(" A={0}", T[i].a);
}
Console.WriteLine("Call Fortran");
TEST_REF(T, T.Length);
for (int i = 0; i < T.Length; i++)
{
Console.WriteLine(" A={0}", T[i].a);
}
}
With results:
Initialize
A=0
A=1
A=2
A=3
Call Fortran
A=0
A=1
A=2
A=3
Debugging through the FORTRAN code, I see the initial values pass from C# to FORTRAN just fine. The the values get overridden with new values and control is passed back into C# where the old values are still contained within the ValueRef instances.
Why is it that I can pass and return an array of float, or int in a similar fashion, just fine. and I can pass and return singular structures with ref keyword, and I can pass but not return and array of struct?
PS. I am using Compaq Visual Fortran 6.5 & .NET 3.5
PS2. I appreciate any comments/ideas on this. I am 95% done with my project, and now I run into this issue. The whole point of this project is to use structures as much as possible to reduce the #of arguments passed to functions and retain certain aspects of OOP design.
I have done this in the past using a pointer, not an array. I think that your structures are being copied for the P/Invoke call:
[DllImport("mathlib.dll")]
static extern void TEST_REF(ValueRef* t, int n);
You will need to pin your array before calling the method.
fixed (ValueRef* pointer = t)
{
TEST_REF(pointer, n);
}
Edit:
Based on the comments the solution is to declare the external as
[DllImport("mathlib.dll")]
static extern void TEST_REF([Out] ValueRef[] t, int n);
Here is a MSDN reference on Marshaling of arrays, and how they default to [In].
My C# method needs to be invoked from C++
Originally my C# method takes a parameter of type double[], but when calling from C++ it becomes a SAFEARRAY
In C++ I need to take data from an array of doubles, and populate a SAFEARRAY. I have not found any sample code to do this.
Any help is appreciated
Following is the code to create a safearray in C++.
#include<oaidl.h>
void CreateSafeArray(SAFEARRAY** saData)
{
double data[10]; // some sample data to write into the created safearray
SAFEARRAYBOUND Bound;
Bound.lLbound = 0;
Bound.cElements = 10;
*saData = SafeArrayCreate(VT_R8, 1, &Bound);
double HUGEP *pdFreq;
HRESULT hr = SafeArrayAccessData(*saData, (void HUGEP* FAR*)&pdFreq);
if (SUCCEEDED(hr))
{
// copy sample values from data[] to this safearray
for (DWORD i = 0; i < 10; i++)
{
*pdFreq++ = data[i];
}
SafeArrayUnaccessData(*saData);
}
}
Free the pointer when you are finished like the following code-
SAFEARRAY* saData;
CreateSafeArray(&saData); // Create the safe array
// use the safearray
...
...
// Call the SafeArrayDestroy to destroy the safearray
SafeArrayDestroy(saData);
saData = NULL; // set the pointer to NULL
If you use ATL for C++, then better use CComSafeArray declared in "atlsafe.h". This is wrapper for SAFEARRAY. link text
Continuing on #Liton's answer, I want to stress his last sentence, i.e. ATL's CComSafeArray. It really can save you a lot of typing. CComSafeArray has C++ constructors, destructors, operator overloads including one for [ ] that gives you an read / write reference to any element in the SAFEARRAY. In short, you can really focus on your business logic and needn't worry about the SAFEARRAY plumbing:
#include <atlbase.h>
#include <atlsafe.h>
// ...
CComSafeArray<double> arr(10);
arr[0] = 2.0;
arr[1] = 3.0;
arr[2] = 5.0;
// ...
At the very least, even if you're not going to use CComSafeArray it's worthwhile to deconstruct its source code in <atlsafe.h> giving you better insight on the what, when, why and how on SAFEARRAY functions.
Passing SAFEARRAYs is not recommended. It is recommended to place the SAFEARRAY into a VARIANT. Further, the SAFEARRAY should hold VARIANT data. This gives the best of all worlds and makes passing VARIANT SAFEARRAY of VARIANTs more useful to other languages. E.g. C++ to VB / C# (Note it is up to the caller to free/destroy the SAFEARRAY)
Building on the previous code
// A VARIANT holding a SAFEARRAY of VARIANTs
VARIANT vRet;
SAFEARRAYBOUND Bound;
Bound.lLbound = 0;
Bound.cElements = 10;
SAFEARRAY * psaData = SafeArrayCreate(VT_VARIANT, 1, &Bound);
VARIANT HUGEP * pData = NULL;
HRESULT hr = SafeArrayAccessData(psaData, (void HUGEP * FAR *)&pData);
if (SUCCEEDED(hr))
{
for (short i = 0; i < 10; ++i,++pData)
{
::VariantInit(pData);
pData->vt = VT_I2;
pData->iVal = i;
}
SafeArrayUnaccessData(psaData);
}
vRet.vt = VT_ARRAY | VT_VARIANT;
vRet.parray = psaData;