Well, I am intended to apply Marshalling between managed and unmanaged codes. That is, to retrieve the result from a c++ code to c# code. I am not intended to know the definition about managed and unmanaged code here, rather am willing to find the proper way out of doing so.
Section: Unmanaged code
File: unmanaged_operation.cpp
#pragma managed(push, off)
typedef struct _IOperation
{
double stOutput;
double* dblPtrValue;
BSTR* parseValue;
BSTR* evenValue;
BSTR* oddValue;
BSTR* stripValue;
BSTR* contextValue;
BSTR* rangeValue;
} IOperation, *LPIOperation;
#pragma managed(pop)
#if (_MANAGED == 1) || (_M_CEE == 1)
#include <vcclr.h>
using namespace System;
using namespace System::Runtime::InteropServices;
#endif
extern __declspec(dllexport) LPIOperation operation;
__declspec(dllexport) LPIFactory factory = new IFactory();
extern "C" __declspec(dllexport) void __stdcall Parse( BSTR* input)
{
BSTR* value = Do_Something_With_BSTR_Input_Value(input);
String^ _output = gcnew String(*value);
IntPtr ptr = Marshal::StringToBSTR(_output);
operation->parseValue = (BSTR*)ptr.ToPointer();
Marshal::FreeBSTR(ptr);
}
extern "C" __declspec(dllexport) void __stdcall Strip( BSTR* input)
{
BSTR* value = Do_Something_With_BSTR_Input_Value(input);
String^ _output = gcnew String(*value);
IntPtr ptr = Marshal::StringToBSTR(_output);
operation->stripValue = (BSTR*)ptr.ToPointer();
Marshal::FreeBSTR(ptr);
}
extern "C" __declspec(dllexport) void __stdcall Range( BSTR* input)
{
BSTR* value = Do_Something_With_BSTR_Input_Value(input);
String^ _output = gcnew String(*value);
IntPtr ptr = Marshal::StringToBSTR(_output);
operation->rangeValue = (BSTR*)ptr.ToPointer();
Marshal::FreeBSTR(ptr);
}
extern "C" __declspec(dllexport) void __stdcall Operate( double input)
{
double output = Do_Something_With_Double_Input_Value(input);
operation->stOutput = output;
}
extern "C" __declspec(dllexport) LPIOperation GetOperation()
{
return operation;
}
Section: Managed code
File: managed_operation.cs
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct IOperation
{
public double stOutput;
public double[] dblPtrValue;
public string parseValue;
};
Class Program
{
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern void Parse([In] String input);
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern void Strip([In] String input);
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern void Range([In] String input);
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern void Operate([In] Double input);
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetOperation();
[STAThread]
public static void Main(string[] args)
{
try
{
IOperation operation = new IOperation();
Parse("The parse value.");
Strip("The strip value.");
Range("The range value.");
Operate((double)2.45);
IntPtr ptr = GetOperation();
operation = (IOperation)(Marshal.PtrToStructure(ptr, typeof(IOperation)));
Console.WriteLine("{0}", operation.parseValue);
Console.WriteLine("{0}", operation.stOutput);
}
catch (Exception e)
{
throw e;
}
}
}
Let's say that the Do_Something_With_BSTR_Input_Value method in unmanaged_operation.cpp be:
BSTR* __stdcall Do_Something_With_BSTR_Input_Value( BSTR* input)
{
return input;
}
that only for testing purpose, rather the original cited. And I wanted to print the same value to Console, that I passed as a parameter in Parse, Strip or Range method in managed_operation.cs
Any suggestion with code example will highly be solicited.
What I have tried:
I used the following code for testing purpose in unmanaged_operation.cpp:
extern "C" __declspec(dllexport) void GetOperationPtr(LPIOperation output)
{
operation->stOutput = (double)2;
operation->parseValue = (BSTR*)"The BSTR string";
*output = *operation;
}
And used the following code in managed_operation.cs
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern void GetOperationPtr([Out] [MarshalAs(UnmanagedType.Struct] out IntPtr ptr);
[STAThread]
public static void Main(string[] args)
{
try
{
IOperation operation = new IOperation();
Parse("The parse value.");
Strip("The strip value.");
Range("The range value.");
Operate((double)7.45);
IntPtr ptr;
GetOperationPtr(ptr);
// The following line throws the exception
operation = (IOperation)(Marshal.PtrToStructure(ptr, typeof(IOperation)));
// The above line throws the exception
Console.WriteLine("{0}", operation.parseValue);
Console.WriteLine("{0}", operation.stOutput);
}
catch (Exception e)
{
throw e;
// Cannot marshal 'parameter #1': Invalid managed/unmanaged type combination
// (Int/UInt must be paired with SysInt or SysUInt).
}
}
Again I changed the IntPtr to object in GetOperationPtr definition as follows:
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall)]
private static extern void GetOperationPtr([Out] [MarshalAs(UnmanagedType.Struct] out object ptr);
and in the Main method:
Object ptr;
GetOperationPtr(ptr);
which caused the application terminated instantly without being executed further.
Again when I omitted the MarshalAs attribute from the GetOperationPtr definition, the parseValue
returns garbage value something like 䕃洭獥慳敧 or 옧ﺧ㲨Ѹ㲨Ѹ or 멄攓�ѵ�ѵ rather any visible result.
To get rid of this I added the Charset parameter to DllImport attribute for GetOperation definition, as:
[DllImport(@"unmanaged_operation.dll"), CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern IntPtr GetOperation();
and used the same code in Main method as described earlier; which in such case suffled the
output value for each instance of IOperation field, as:
operation.parseValue returned "The strip value." for the method Parse("The parse value.");
operation.stripValue returned "The range value." for the method Strip("The strip value.");
operation.rangeValue returned "The parse value." for the method Range("The range value.");