//
you're reading...
Interop Marshaling

Passing Structures between Managed and Unmanaged Code

1. Introduction.

1.1 C# developers who use interop frequently often require exchanging structures between managed and unmanaged code. This can sometimes be a difficult and confusing task.

1.2 Let’s take the following structure as an example :

public struct TestStruct
{
  public int[]    m_IntArray;
  public int      m_Int;
}

1.3 This structure looks simple but how do we pass such a structure to a C++ API defined as follows :

void __stdcall TestAPI(/*[in, out]*/ TestStruct* pTestStruct)
{
  for (int i = 0; i < 10; i++)
  {
    (pTestStruct -> m_IntArray)[i]++;
  } 

  (pTestStruct -> m_Int)++;
}

The above API is declared in C# as follows :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void TestAPI(ref TestStruct test_struct);

This API takes a TestStruct struct as a reference parameter. Internally it will perform modification of the input structure. When the call returns, the C# code is expected to see changes to the TestStruct struct.

1.5 If you simply run the following code :

TestStruct test_struct = new TestStruct();

test_struct.m_IntArray = new int[10]; 

for (int i = 0; i < test_struct.m_IntArray.Length; i++)
{
  (test_struct.m_IntArray)[i] = 0;
} 

test_struct.m_Int = 0;

TestAPI(ref test_struct);

You will very likely run into some error. The problem is that the C++ API treats the TestStruct’s m_IntArray member as a C-style array whereas the .NET runtime will convert it into a SAFEARRAY.

2. Interop Marshaling, Blittable/Non-Blittable Types, Reference/Value Types.

2.1 To understand the nature of the problem, we need to understand the concepts of blittable and non-blittable types as well as reference and value types and how they are related to interop marshaling.

2.2 By “blittable type”, we mean types that have a common representation in both managed and unmanaged memory and do not require conversion when they are passed between managed and unmanaged memory.

2.3 In short, their data can be copied directly from managed memory to unmanaged memory and vice versa without any special processing by the interop marshaler.

2.4 An example of a blittable type is the C# int type. Assuming 32-bit applications, an instance of a C# int takes up 4 bytes, the same as a C++ int type which also takes up 4 bytes. Furthermore, their binary representation in memory are the same. Hence a C# int variable in managed memory can be directly copied (blitted) to a C++ int variable in unmanaged memory. And vice versa.

2.5 Non-blittable-types are just the opposite of blittable-types : i.e. types that do not have a common representation in both managed and unmanaged memory and that may require conversion when they are passed between managed and unmanaged memory. The conversion is performed by the interop marshaler.

2.6 An example of a non-blittable type is the string. A .NET string is intrinsically unicode based and does not have a direct unmanaged equivalent. An unmanaged string may be C-style null-terminated, or it may be a BSTR (COM string). Furthermore an unmanaged string may be unicode-based or multi-byte character-based.

2.7 Another example is the array type. A .NET array is an object and has an internal representation that is not documented albeit the actual array data is as expected : a contiguous range of data packed back-to-back in memory somewhere. However, it has no direct unmanaged equivalent. If not specified using the MarshalAsAttribute, its default unmanaged  equivalent is the SAFEARRAY (see point 1.5 above).

2.8 You might be surprised to note that a .NET char type is actually non-blittable. This is because a .NET char is unicode-based. The same is true of a .NET Boolean which can be represented in unmanaged code in various formats.

2.9 The overarching thing about non-blittable types is that they do not have a single common unmanaged equivalent. Such types, when required to be marshaled over to unmanaged code, will need specifications in order for the interop marshaler to be able to successfully perform the marshaling. These specifications come in the form of MarshalAsAttribute‘s.

2.10 By “reference type”, we mean types the actual data of which are allocated in the managed heap. They are reference counted internally and are de-allocated by the Garbage Collector. Each programing language variable that is assigned to a value of such a type merely holds a reference to that value. The variable does not get assigned another copy of the value.

2.11 The opposite of a reference type is the “value type”. Value type data objects reside on the stack. They are not managed by the Garbage Collector. They are destroyed when they go out of scope. Each programming variable that is assigned a value-type value gets its own copy of that value.

2.12 Now, how are the concepts of blittability and reference and value types relevant to the using structs in unmanaged code ? The following is a summary :

  • When we want to share a managed struct with unmanaged code, a good optimization technique would be to directly pass the struct to the unmanaged code.
  • This can only be done if the member data of the managed struct are all contained within the memory block of the struct itself. That is, the struct self-contains all member data just like a C/C++ class/struct.
  • Now this is only possible if all members are blittable types. If any member is non-blittable, the entire struct is non-blittable.
  • The advantage of a completely blittable struct is that such a struct may be directly passed to unmanaged code. Now in order to directly pass it to unmanaged code, it needs to be pinned in memory, otherwise, not only can it be moved around by the GC, it may even be completely de-allocated by the GC. Pinning it in memory is especially important if the unmanaged API receives a pointer to the struct.
  • The unmanaged code may then reference the struct directly and may even make modifications to it if this is expected. When the unmanaged code returns, the struct may be unpinned from memory.
  • Now, if a struct is non-blittable, it will not be possible to directly pass it to unmanaged code. However, all is not lost. It will still be possible to “pass” the struct to unmanaged code. However, this “struct” is a memory copy of the struct. The creation of such a struct requires the help of the interop marshaler.
  • This copy must contain the actual data of all the members of the struct. It serves as a an unmanaged representation of the original struct. The copy is made with the help of the various MarshalAsAttribute’s.
  • It is this unmanaged representation that gets passed to the unmanaged code. Now, if the unmanaged code is expected to make modifications to this representation, then when the unmanaged code returns, the (now modified) data from the representation must be copied back to the original managed struct.
  • Eventually, the unmanaged representation must be freed.
  • The above description resembles COM paramater marshaling across apartments where parameter data may be re-created in the callee’s stack.

3. The Members of the TestStruct struct.

3.1 Now, let’s examine the TestStruct structure :

public struct TestStruct
{
  public int[] m_IntArray;
  public int m_Int;
}

This structure contains one int member :

public int m_Int;

and one managed array member :

public int[] m_IntArray;

3.2 In C#, the int type is a value type and is a blittable type. When used as a member of a struct, it can directly reside as part of the memory block of the struct itself. Hence m_Int form part of the body of the TestStruct structure. It is destroyed when an instance of the TestStruct struct is destroyed.

3.3 Therefore, when we have code like the following :

TestStruct test_struct = new TestStruct();
test_struct.m_Int = 0;

The value of member “m_Int” (i.e. zero) resides directly as part of the memory block for “test_struct”. The situation is exactly the same as that for the members of a C/C++ structure.

3.4 Now a managed array, when used as a member of a struct, renders the containing struct non-blittable. This is because a managed array is essentially non-blittable. Furthermore it is also a reference type. That is, its actual array data must exist in the managed heap and is merely referenced by the struct member.

3.5 Therefore, when we have code like the following :

test_struct.m_IntArray = new int[10];

The array of 10 int values resides in the managed heap somewhere and is referenced by “test_struct.m_IntArray”. The actual array data of 10 int’s is not part of the memory block of the test_struct object. This is totally unlike what we may be familiar with in C/C++.

3.6 This is the reason why we cannot pin down in memory a struct that contains an array :

// The following code will not work.
GCHandle gch = GCHandle.Alloc(test_struct, GCHandleType.Pinned);
IntPtr p = gch.AddrOfPinnedObject();

In fact, we cannot pin a struct that contains members which are reference types (including the string type).

To pin such a struct would also require that the referenced object be pinned. This might seem acceptable initially until you consider code like the following :

TestStruct test_struct = new TestStruct();
int [] int_array = new int[10];
test_struct.m_IntArray= int_array; //new int[10];

That is, “test_struct.m_IntArray” does not reference a newly allocated int array. Instead, it references an existing one. Pinning “test_struct.m_IntArray” would also logically require that “int_array” be pinned. This may not be a desireable thing.

4. How Do We Marshal the TestStruct structure to an Unmanaged API ?

4.1 Although TestStruct is not blittable (due to the presence of member int_array), it is nevertheless possible to pass such a structure to unmanaged code.

4.2 However, note well that under the covers, an unmanaged copy of TestStruct is created. This copy is actually a representation of TestStruct. We have seen that TestStruct does not have a flat memory layout due to the presence of an array member. Therefore a managed instance of TestStruct cannot be directly used by unmanaged code.

4.3 Hence, in order to pass such a structure to unmanaged code, an unmanaged version of TestStruct is required. The Interop Marshaler will allocate in memory a buffer which is large enough to hold such an unmanaged replica of TestStruct. This is done using Marshal.SizeOf().

4.4  In order to calculate the required size of the buffer, the interop marshaler must know how the individual members of the managed structure are to be represented in the unmanaged counterpart. This includes the positioning of each member and any padding between members. Now this is where the StructLayoutAttribute and the MarshalAsAttribute come in handy. Look up MSDN for the documentation of the StructLayoutAttribute and the MarshalAsAttribute in order to understand how to use them.

4.5 For the TestStruct structure, we applied these 2 attributes in the following way :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct
{
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
  public int[]    m_IntArray;
  public int      m_Int;
}

The StructLayoutAttribute indicate to the interop marshaler that the members of the unmanaged replica are to appear sequentially (in the same order as their appearance in the managed counterpart). The members are also to be packed back-to-back (due to Pack=1).

The MarshalAsAttribute applied to the “m_IntArray” member indicates that this member is to be represented as a C-style array of int type. The SizeConst argument indicates that the array is to have 10 elements.

4.6 When taken together, these information indicate that the unmanaged replica of TestStruct will have the following C/C++ representation :

#pragma pack(1)
struct TestStruct
{
  int m_IntArray[10];
  int m_Int;
};

4.7 Now that we have fully prepared TestStruct for interop marshaling, how do we use it as a parameter to the TestAPI() function ?

4.8 The following is a sample call :

// Allocate a TestStruct struct.
TestStruct test_struct = new TestStruct();
// Assign values into test_struct.
test_struct.m_IntArray = new int[10];
for (int i = 0; i < test_struct.m_IntArray.Length; i++)
{
  (test_struct.m_IntArray)[i] = 0;
} 

test_struct.m_Int = 0;
TestAPI(ref test_struct);

Notice that it is very similar to the code in point 1.5 above. This time, the code will go through successfully and you will see actual changes to the managed test_struct.

4.9 How were all these good effects brought about ? In fact, the only changes were to the declaration of TestStruct (via the StructLayoutAttribute and the MarshalAsAttribute).

4.10 These attributes established what the unmanaged representation of TestStruct will look like in unmanaged memory.

5. The Marshaling Process.

Now, how does the interop marshaler perform the copying of each struct member from the managed TestStruct to the unmanaged TestStruct ? This section describes a possible scenario of how this is actually done under the hood.

5.1 The interop marshaler allocate memory that provides an unmanaged representation of an instance of the TestStruct struct. This is done with the help of Marshal.AllocHGlobal(). The size is determined via Marshal.SizeOf() and it requires the data contained in the StructLayoutAttribute and various MarshalAsAttribute’s.

5.2 The interop marshaler then copies the actual data of the members of the TestStruct instance to this unmanaged memory. The m_Int member is blitted to the unmanaged buffer. The copying of the m_IntArray member is done with specifications provided by the MarshalAsAttribute which was applied to the m_IntArray member. The interop marshaler likely uses the Marshal.StructureToPtr() method.

5.3 The unmanaged API is then invoked passing along a pointer to the unmanaged memory. This pointer is likely an IntPtr type.

5.4 After the API call, because the API has modified the structure, the returned data in the unmanaged structure must be copied back to the managed TestStruct structure. The m_Int member, being blittable, is transferred directly to its managed counterpart. The m_IntArray member is copied back to its managed counterpart with information provided by the MarshalAsAtttibute information contained in the member. This reverse copying from unmanaged memory to the managed struct is likely performed via Marshal.PtrToStructure.

5.5 Finally, the unmanaged TestStruct struct is freed by the interop marshaler. This is done via Marshal.FreeHGlobal().

6. Example Code.

An example code, written to illustrate the concepts discussed in section 5 above, is given below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices; 

namespace CSConsoleClient01
{
  [StructLayout(LayoutKind.Sequential, Pack = 1)]
  public struct TestStruct
  {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public int[] m_IntArray;
    public int m_Int;
  } 

  class Program
  {
    [DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "TestAPI")]
    private static extern void TestAPI(IntPtr pTestStruct); 

    static void DemonstrateCallByIntPtr()
    {
      // Allocate a TestStruct struct.
      TestStruct test_struct = new TestStruct();
      // Assign values into test_struct.
      test_struct.m_IntArray = new int[10];
      for (int i = 0; i < test_struct.m_IntArray.Length; i++)
      {
        (test_struct.m_IntArray)[i] = 0;
      } 

      test_struct.m_Int = 0; 

      // Obtain the size of an unmanaged serialized
      // representation of a TestStruct struct.
      int iSize = Marshal.SizeOf(typeof(TestStruct));
      // Allocate memory (in the Global Heap) for the unmanaged
      // representation of a TestStruct struct.
      IntPtr pTestStruct = Marshal.AllocHGlobal(iSize);
      // Copy the full contents of the members of test_struct
      // to the unmanaged representation of TestStruct.
      // Data from the members of test_struct will be
      // serialized into a flat memory format and stored
      // into the unmanaged serialized representation.
      Marshal.StructureToPtr(test_struct, pTestStruct, false);
      // Call the IntPtr version API.
      TestAPI(pTestStruct);
      // Copy the modified contents of the unmanaged representation
      // of test_struct back to the members of the managed test_struct.
      test_struct = (TestStruct)(Marshal.PtrToStructure(pTestStruct, typeof(TestStruct)));
      // Free the unmanaged representation of test_struct.
      Marshal.FreeHGlobal(pTestStruct);
    } 

    static void Main(string[] args)
    {
      DemonstrateCallByIntPtr();
    }
  }
}

The code for TestAPI() and the TestStruct structure are listed below :

#pragma pack(1) struct TestStruct
{
  int m_IntArray[10];
  int m_Int;
}; 

void __stdcall TestAPI(/*[in, out]*/ TestStruct* pTestStruct)
{
  for (int i = 0; i < 10; i++)
  {
    (pTestStruct -> m_IntArray)[i] = i;
  } 

  pTestStruct -> m_Int = 100;
}
Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

34 thoughts on “Passing Structures between Managed and Unmanaged Code

  1. Hey,
    great blog had some trouble understanding interop but you really helped me however I was wondering What if in the example on this page int m_Int is actually a std::string or a char*. How would you marshal it in the c# code?
    Great blog again

    Posted by apmbaye | April 5, 2013, 8:50 pm
  2. hi, it is a great blog. And I have a problem with passing a pointer to structure. Here is the two c structures:

    typedef struct
    {
    int num; /* section number */
    int len; /* section taps length in use */
    int xId; /* section input data index */
    int tId; /* section taps index */
    } firSect;

    typedef struct _NSPFirState
    {
    int upFactor; /* up */
    int upPhase; /* parameters */
    int downFactor; /* down */
    int downPhase; /* parameters */
    int isMultiRate; /* multi-rate mode flag */
    void* isInit; /* init flag */

    int tapsLen; /* number of filter taps */
    int tapsBlk;
    void* taps; /* taps pointer in use */

    int dlylLen; /* delay line length */
    int inpLen; /* input buffer length */
    int useInp; /* input buffer length used */
    void* dlyl; /* extended dilter delay line */
    void* dlylLimit; /* delay line buffer end pointer */
    void* useDlyl; /* delay line pointer in use */

    firSect* sect; /* FIR sections */

    int tapsFactor; /* taps scale factor */
    int utapsLen; /* number of filter taps (user def) */
    } NSPFirState;

    And here is two method I need to call in C#:
    void nspsFirInit(const float *tapVals, int tapsLen, const float *dlyVals, NSPFirState *statePtr))
    double nspdFir(NSPFirState *statePtr,double samp))

    I handle them in c# like this:
    [DllImport(“nsp.dll”, EntryPoint = “nspsFirInit”, CallingConvention = CallingConvention.StdCall)]
    private static extern void nspsFirInit([MarshalAs(UnmanagedType.LPArray)]double[] tapVals, int tapsLen, [MarshalAs(UnmanagedType.LPArray)]double[] dlyVals, ref NSPFirState statePtr);

    [DllImport(“nsp.dll”, EntryPoint = “nspsFir”, CallingConvention = CallingConvention.StdCall)]
    private static extern double nspsFir(ref NSPFirState statePtr, double samp);

    struct firSect
    {
    public int num; /* section number */
    public int len; /* section taps length in use */
    public int xId; /* section input data index */
    public int tId; /* section taps index */
    } ;

    struct NSPFirState
    {
    public int upFactor; /* up */
    public int upPhase; /* parameters */
    public int downFactor; /* down */
    public int downPhase; /* parameters */
    public int isMultiRate; /* multi-rate mode flag */
    public IntPtr isInit; /* init flag */

    public int tapsLen; /* number of filter taps */
    public int tapsBlk;
    public IntPtr taps; /* taps pointer in use */

    public int dlylLen; /* delay line length */
    public int inpLen; /* input buffer length */
    public int useInp; /* input buffer length used */
    public IntPtr dlyl; /* extended dilter delay line */
    public IntPtr dlylLimit; /* delay line buffer end pointer */
    public IntPtr useDlyl; /* delay line pointer in use */

    //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    //public firSect[] sect; /* FIR sections */

    //public IntPtr sect = Marshal.AllocHGlobal(4 * sizeof(int));

    public unsafe firSect* sect;

    public int tapsFactor; /* taps scale factor */
    public int utapsLen; /* number of filter taps (user def) */
    };

    I have no idea how to pass the firSect* sect pointer to my c# code. Could you give some suggestions?

    Posted by shawn shen | July 10, 2014, 1:33 pm
    • Hello Shawn,

      1. The “NSPFirState.sect” should be declared as an IntPtr.

      2. At runtime, what we want is to allocate a firSect structure, fill its members, and then create in unmanaged memory an unmanaged structure which is equivalent to firSect.

      3. This is done by calling a memory allocation method like Marshal.AllocCoTaskMem() or Marshal.AllocHGlobal(). Followed by a call to Marshal.StructureToPtr().

      4. We then set “NSPFirState.sect” to this pointer to the unmanaged firSect.

      5. We then call the nspsFirInit() or nspsFir().

      6. On return from these function calls, the “NSPFirState.sect” field must be converted from an unmanaged firSect structure to its managed version. This is done by calling Marshal.PtrToStructure().

      7. Finally the unmanaged firSect structure must be freed by calling Marshal.FreeCoTaskMem() or Marshal.FreeHGlobal().

      8. The following is an example :

      static void DoTest()
      {
      NSPFirState _NSPFirState = new NSPFirState();
      firSect _firSect = new firSect();

      _firSect.num = 1;
      _firSect.len = 2;
      _firSect.xId = 3;
      _firSect.tId = 4;

      int iSize = Marshal.SizeOf(typeof(firSect));

      // Allocate unmanaged memory for the space
      // required for the firSect structure.
      _NSPFirState.sect = Marshal.AllocCoTaskMem(iSize);
      // Copy the contents of the firSect struct
      // into its unmanaged counterpart (pointed to
      // by p_firSect).
      Marshal.StructureToPtr(_firSect, _NSPFirState.sect, false);

      // Now call nspsFirInit().
      nspsFirInit(null, 0, null, ref _NSPFirState);

      // After the call to nspsFirInit(), _NSPFirState.sect
      // would still point to the unmanaged memory
      // which contains the unmanaged firSect struct.
      //
      // We must now convert _NSPFirState.sect to a managed
      // firSect struct.
      _firSect = (firSect)(Marshal.PtrToStructure(_NSPFirState.sect, typeof(firSect)));

      // Must free the unmanaged memory of p_firSect when no longer needed.
      Marshal.FreeCoTaskMem(_NSPFirState.sect);
      _NSPFirState.sect = IntPtr.Zero;
      }

      Posted by Lim Bio Liong | July 12, 2014, 5:26 am
  3. Hi Bio,
    Is it the same code architecture and structure in TestStruct, if you use a “2D string array” instead of the “1D int array” in your example .. ?
    Thanks.

    Posted by Steve | November 6, 2015, 5:44 pm
    • I know this is an old post, but I need to use a 2d char array instead of the 1D int array. Maybe you could helpme.
      Thanks in advance

      Posted by Gilbert | December 12, 2017, 8:42 pm
      • Hello Gilbert, on the unmanaged side, is the 2D char array intended to be an array of C strings ?

        Posted by Lim Bio Liong | December 13, 2017, 8:40 am
      • Hi Lim Bio Liong, it’s a nice surprice to see your reply, and yes, on the unmanaged side, is the 2D char array intended to be an array of C strings.

        I just stoped that implementation and I’m moving on other parts of the software I’m still building, your help will be great.
        Regards

        Posted by Gilbert | February 21, 2018, 8:01 pm
  4. Hi Lim Bio Liong,
    It’s a very good explanation and useful. Thanks for your time to share such a descriptive and clear information.
    Actually in one of my C# application I need to write wrapper to native c++ Dll. I need to pass below C++ structure(_TestStruct) to native c++ DLL. Please find the below details regarding c++ Implementation.
    API.H
    #define API_EXPORT __declspec(dllexport)
    extern “C”
    {
    API_EXPORT typedef int32_t (*Logger)(char const* format, …); //Delegates to be implemented in C# client application for log
    typedef struct _TestStruct //Structure contains function pointers
    {
    API_EXPORT int32_t (*Start)(Logger log);
    API_EXPORT int32_t (*Stop)(void);
    }TestStruct,*PTestStruct;
    API_EXPORT int32_t Create(PTestStruct* api); //Dll function to be called from C# application
    }
    API.CPP
    API_EXPORT int32_t Start(Logger log)
    {
    (Logger)(“Start Succfull”);
    }
    API_EXPORT int32_t Stop(void)
    {
    }
    API_EXPORT int32_t Create(PTestStruct* api)
    {
    if (api)
    {
    TestStruct copy = { 0 };
    {
    copy.Start = Start;
    copy.Stop = Stop;
    }
    *api = new TestStruct{ copy };
    return 1;
    }
    return 0;
    }
    How should I replace the above (_TestStruct) structure and write wrapper to call the Create function. Please give me some idea to approach the solution. Thanks…

    Posted by Keearu | May 23, 2018, 1:35 am
    • Hello Keearu,

      1. Thanks for your kind comments.

      2. If I’ve understood the descriptions of your code correctly, here are my comments :
      – You are trying to call functions in C++ unmanged code (e.g. Start(), Stop()).
      – The Start() function takes a pointer to a function as parameter.
      – This function parameter is invoked within Start().
      – You intend to pass a C# delegate as the function pointer parameter.

      3. So far so good. All the above are achievable, except :
      – The function pointer, declared in the C++ code as Logger, is a variable argument function.
      – To the best of my knowledge, this is not possible as far as interop is concerned.
      – For one thing, a C++ variable argument function (declared with elipses “…” to represent the varags) cannot be implemented in C#.
      – The calling convention required for this is simply not supported in C#.
      – A C# variable argument method uses the “params” keyword together with an array to handle variable number of parameters.
      – See : C# P/Invoke: Varargs delegate callback (https://stackoverflow.com/questions/6694612/c-sharp-p-invoke-varargs-delegate-callback)
      – And : Members with a Variable Number of Parameters (https://msdn.microsoft.com/en-us/library/ms229008(v=vs.100).aspx) which specifically warns :
      — Do not use the varargs methods, otherwise known as the ellipsis. Because the varargs calling convention is not CLS-compliant, it should not be used in public members. It can be used internally.

      4. In order that Logger be implementable in C#, it needs to be delared as :

      //Delegates to be implemented in C# client application for log
      extern “C” typedef int32_t (__stdcall *Logger)(char const* format, SAFEARRAY* psa_objects);

      where the variable parameter part is passed as a SAFEARRAY of VARIANTs.

      The C# delegate declaration would have to be :

      [UnmanagedFunctionPointer(CallingConvention.StdCall)]
      public delegate Int32 LoggerDelegate([In] [MarshalAs(UnmanagedType.LPStr)] string strFormat, [In] [MarshalAs(UnmanagedType.SafeArray)] params object[] parameters);

      5. Another observation I made from your code is that the Create() function is incorrect.
      – It is not possible to use the C++ “new” keyword to create the _TestStruct structure in order to pass it to your C# program.
      – You must use either CoTaskMemAlloc() or GlobalAlloc() to create the unmanaged structure.
      – It would be better, actually, to have the C# side create the unmanaged structure and then free it.
      – Just pass it to the C++ Create() function as an IntPtr.
      – Here is a sample :

      extern “C” API_EXPORT int32_t Create(PTestStruct api)
      {
      api -> Start = Start;
      api -> Stop = Stop;

      return 0;
      }

      In C# :

      [DllImport(“KeearuNativeDLL.dll”, CallingConvention = CallingConvention.StdCall, EntryPoint = “Create”)]
      private static extern void Create(IntPtr pTestStruct);

      // Allocate a TestStruct struct.
      _TestStruct test_struct = new _TestStruct();

      // Obtain the size of an unmanaged serialized
      // representation of a TestStruct struct.
      int iSize = Marshal.SizeOf(typeof(_TestStruct));
      // Allocate memory (in the Global Heap) for the unmanaged
      // representation of a TestStruct struct.
      IntPtr pTestStruct = Marshal.AllocHGlobal(iSize);
      // Copy the full contents of the members of test_struct
      // to the unmanaged representation of TestStruct.
      // Data from the members of test_struct will be
      // serialized into a flat memory format and stored
      // into the unmanaged serialized representation.
      Marshal.StructureToPtr(test_struct, pTestStruct, false);
      // Call the Create() API.
      Create(pTestStruct);

      5. I hope you have not been discouraged. If you want to, I can pass to you a sample code that I tried, following your line of logic, and which worked.

      – Bio.

      Posted by Lim Bio Liong | May 23, 2018, 7:49 pm
  5. Hi Bio,

    -> What You understand my description is correct. But, in 5th point you said need to modify my c++ Create() function.
    In Create(PTestStruct* api). Here “PTestStruct” is again pointer to _TestStruct. So it just like pointer in pointer(double pointer). so as per your suggestion I modified like below

    extern “C” API_EXPORT int32_t Create(PTestStruct* api)
    {
    //(api*) -> Start = Start;
    (api*) -> Stop = Stop;

    return 0;
    }

    -> To remove complexity for now I just commented Start().

    -> In _TestStruct we have all function pointers which are defined in native C++ dll. Instead of invoking those methods directly, we invoke with Create(PTestStruct* api) pointer we got from native DLL(like api.Start(),api.Stop(),..etc).

    -> Please find the below wrapper code. I got “An unhandled exception of type ‘System.AccessViolationException’ occurred in OCTTestAppWrapper.exe

    Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.” Following exception when I invoke “test_struct._stop()”.

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate int Stop();

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct _TestStruct
    {
    [MarshalAs(UnmanagedType.FunctionPtr)]
    public Stop _stop;
    }

    [DllImport(“KeearuNativeDLL.dll”, CallingConvention = CallingConvention.StdCall, EntryPoint = “Create”)]
    private static extern void Create(IntPtr pTestStruct);

    // Allocate a TestStruct struct.
    _TestStruct test_struct = new _TestStruct();

    // Obtain the size of an unmanaged serialized
    // representation of a TestStruct struct.
    int iSize = Marshal.SizeOf(typeof(_TestStruct));

    // Allocate memory (in the Global Heap) for the unmanaged
    // representation of a TestStruct struct.
    IntPtr pTestStruct = Marshal.AllocHGlobal(iSize);

    // Copy the full contents of the members of test_struct
    // to the unmanaged representation of TestStruct.
    // Data from the members of test_struct will be
    // serialized into a flat memory format and stored
    // into the unmanaged serialized representation.
    Marshal.StructureToPtr(test_struct, pTestStruct, false);

    // Call the Create() API.
    Create(pTestStruct);

    test_struct = (_TestStruct)(Marshal.PtrToStructure(pTestStruct, typeof(_TestStruct)));
    int returnVal = test_struct._stop(); //Got unhandled exception here

    Can you please suggest me cause of this exception and work around for that. Your comments are much helped me. Thanks a lot for your efforts and kindness.

    Posted by Keearu | May 25, 2018, 12:02 am
  6. Thanks BIo for your swift response. I got your points. Let me try from my end. Still, If I face any difficulties I will share you again.

    Posted by Keearu | May 25, 2018, 5:58 am
  7. Hey got the solution. I just changed

    [DllImport(“KeearuNativeDLL.dll”, CallingConvention = CallingConvention.StdCall, EntryPoint = “Create”)]
    private static extern void Create(ref IntPtr pTestStruct); //Added ref

    Its working fine now. Thanks Bio for your timely co operation.

    Posted by Keearu | May 25, 2018, 11:58 pm
    • Hello Keearu,

      1. Congratulations. Yes, you may declare the Create() function as you originally intended, i.e. :

      extern “C” API_EXPORT int32_t Create(PTestStruct* api)
      {
      (*api) -> Start = Start;
      (*api) -> Stop = Stop;

      return 0;
      }

      You would, of course, declare Create() in your C# code as :

      [DllImport(“KeearuNativeDLL.dll”, CallingConvention = CallingConvention.StdCall, EntryPoint = “Create”)]
      private static extern void Create(ref IntPtr pTestStruct);

      with the “ref” keyword.

      BTW, the following is how I implemented _TestStruct in C++ :

      //Structure contains function pointers
      typedef struct _TestStruct
      {
      int32_t (__stdcall *Start)(Logger log);
      int32_t (__stdcall *Stop)(void);
      }TestStruct, *PTestStruct;

      And in C# :

      struct _TestStruct
      {
      [MarshalAs(UnmanagedType.FunctionPtr)]
      public StartDelegate Start;

      [MarshalAs(UnmanagedType.FunctionPtr)]
      public StopDelegate Stop;
      }

      2. To help you with the Start() function, I have listed below how Start() and Logger() may be implemented :

      In C++ :

      #include

      //Delegates to be implemented in C# client application for log
      extern “C” typedef int32_t (__stdcall *Logger)(char const* format, SAFEARRAY* psa_objects);

      extern “C” API_EXPORT int32_t __stdcall Start(Logger log)
      {
      VARIANT var;

      VariantInit(&var);
      V_VT(&var) = VT_BSTR;
      V_BSTR(&var) = SysAllocString(L”Hello World”);

      SAFEARRAYBOUND rgsabound[1];

      rgsabound[0].lLbound = 0;
      rgsabound[0].cElements = 1;

      SAFEARRAY* psa_objects = (SAFEARRAY*)SafeArrayCreate
      (
      (VARTYPE)VT_VARIANT,
      (unsigned int)1,
      (SAFEARRAYBOUND*)rgsabound
      );

      long lIndexVector[1];

      lIndexVector[0] = 0;

      SafeArrayPutElement
      (
      (SAFEARRAY*)psa_objects,
      (long*)lIndexVector,
      (void*)(&var)
      );

      log(“{0:S}”, psa_objects);

      VariantClear(&var);

      return 0;
      }

      In C# :

      static int MyLogger(string strFormat, params object[] parameters)
      {
      Console.WriteLine(strFormat, parameters);
      return 0;
      }

      3. Hence, assuming that your C# code is Console based, if you run the following code :

      static void DemonstrateCallByRefIntPtr()
      {
      // Allocate a TestStruct struct.
      _TestStruct test_struct = new _TestStruct();

      // Obtain the size of an unmanaged serialized
      // representation of a TestStruct struct.
      int iSize = Marshal.SizeOf(typeof(_TestStruct));
      // Allocate memory (in the Global Heap) for the unmanaged
      // representation of a TestStruct struct.
      IntPtr pTestStruct = Marshal.AllocHGlobal(iSize);
      // Copy the full contents of the members of test_struct
      // to the unmanaged representation of TestStruct.
      // Data from the members of test_struct will be
      // serialized into a flat memory format and stored
      // into the unmanaged serialized representation.
      Marshal.StructureToPtr(test_struct, pTestStruct, false);
      // Call the IntPtr version API.
      Create(ref pTestStruct);
      // Copy the modified contents of the unmanaged representation
      // of test_struct back to the members of the managed test_struct.
      test_struct = (_TestStruct)(Marshal.PtrToStructure(pTestStruct, typeof(_TestStruct)));
      // Free the unmanaged representation of test_struct.
      Marshal.FreeHGlobal(pTestStruct);

      // Declare an instance of the LoggerDelegate delegate.
      LoggerDelegate logger_delegate = new LoggerDelegate(MyLogger);
      // Use GCHandle to hold the delegate object in memory.
      GCHandle gch = GCHandle.Alloc(logger_delegate);
      // Obtain an unmanaged function pointer for the delegate as usual.
      IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(logger_delegate);
      // Set the function pointer in unmanaged code.
      Int32 iRetTemp = test_struct.Start(intptr_delegate);
      // Perform Garbage Collection.
      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();
      gch.Free();

      }

      You should see displayed on the console :

      Hello World

      4. All the best to you.

      – Bio,

      Posted by Lim Bio Liong | May 25, 2018, 7:26 pm
  8. Kudos Bio.
    You have more patience and talent. Thanks for your detailed explanation. If any thing comes in the future I will let you know.

    Posted by Keearu | May 26, 2018, 6:55 am
    • Hello Keearu,

      1. Just one more matter : I had left out a call to SafeArrayDestroy() in my sample Start() function. Please see the new code below :

      extern “C” API_EXPORT int32_t __stdcall Start(Logger log)
      {
      VARIANT var;

      VariantInit(&var);
      V_VT(&var) = VT_BSTR;
      V_BSTR(&var) = SysAllocString(L”Hello World”);

      SAFEARRAYBOUND rgsabound[1];

      rgsabound[0].lLbound = 0;
      rgsabound[0].cElements = 1;

      SAFEARRAY* psa_objects = (SAFEARRAY*)SafeArrayCreate
      (
      (VARTYPE)VT_VARIANT,
      (unsigned int)1,
      (SAFEARRAYBOUND*)rgsabound
      );

      long lIndexVector[1];

      lIndexVector[0] = 0;

      SafeArrayPutElement
      (
      (SAFEARRAY*)psa_objects,
      (long*)lIndexVector,
      (void*)(&var)
      );

      log(“{0:S}”, psa_objects);

      // This will clear away the BSTR contained inside var.
      VariantClear(&var);

      // This will destroy the SAFEARRAY.
      // It will no longer be needed.
      SafeArrayDestroy(psa_objects);
      psa_objects = NULL;

      return 0;
      }

      2. Notice my call to SafeArrayDestroy() right after VariantClear(). This call is important because otherwise the SAFEARRAY will remain in memory and with each call to Create(), memory leak will result.

      3. Sorry for the oversight.

      – Bio.

      Posted by Lim Bio Liong | May 26, 2018, 6:45 am
      • Thanks Bio for your kind suggestions. I will consider this in my implementation.

        Posted by Keearu | May 30, 2018, 8:01 am
  9. Hi Bio,
    I struck again, need to import below unamanaged function in C# application.
    .CPP
    extern “C” API_EXPORT int ReadMemory(void *pObj, void* Mem, char * memBuffer, size_t noOfbites);
    I tried below wrapper. But, its not working.
    [DllImport(“ToolDll.dll”, CallingConvention = CallingConvention.StdCall)]
    public static extern int ReadMemory(IntPtr pObj, IntPtr GMem, [MarshalAs(UnmanagedType.LPStr)]string cMem, int copySizeInBytes);
    string pixelBuffer = string.Empty;
    int retCode = ReadMemory(pObj, PixelData, pixelBuffer, 2048 * 256 * 1);
    We get image pixel data in pixelBuffer. we need to save that data as “.bmp” format. But, we got Invalid value error while executing ReadMemory function. I guess there is some conversion issue(unmanaged char* to C# string). Can you please suggest me. What is the best way to handle this.
    Thanks…

    Posted by Keearu | June 2, 2018, 7:48 am
    • Hello Keearu,

      1. I assume that ReadMemory() is supposed to fill cMem (the 3rd parameter) with byte values which is returned to the C# console client.

      2. If so, you should then declare it in C# as follows :

      [DllImport(“ToolDll.dll”, CallingConvention = CallingConvention.StdCall)]
      public static extern int ReadMemory(IntPtr pObj, IntPtr GMem, [In][Out] byte [] cMem, int copySizeInBytes);

      2.1 Normally, if you want to call an unmanaged API that returns a string, e.g. GetPrivateProfileString(), you would use the StringBuilder class.

      2.2 However, in your case, you want to call ReadMemory() in order to obtain bitmap data. A StringBuilder object will not do because it will expect the output from ReadMemory() to be NULL terminated. Hence you may not get all the bytes of the bitmap as there may be embedded NULL bytes in the bitmap data.

      3. Call ReadMemory() as follows :

      byte [] byMem = new byte [10000];

      ReadMemory(pObj, PixelData, byMem, byMem.Length);

      3.1 Normally when passing a reference type managed data to unmanaged code, you will need to pin it in memory. But in the case of a byte array, you will not need to.

      3.2 See the following for more details :

      Best method for sending byte[] to unmanaged code

      https://social.msdn.microsoft.com/Forums/vstudio/en-US/b3ff0ab7-ad4d-4b4e-abcc-67134c0bac17/best-method-for-sending-byte-to-unmanaged-code?forum=clr

      – Bio.

      Posted by Lim Bio Liong | June 2, 2018, 10:44 am
      • Hi Bio,

        I tried above changes. But, still same Invalid value error. I didn’t get data back in “byMem” (ReadMemory(pObj, PixelData, byMem, byMem.Length);). Please suggest me how to get fixed this issue.

        Thanks…

        Posted by Keearu | June 5, 2018, 1:51 am
      • Hello Keearu,

        Please show your C++ code (how ReadMemory() was implemented) and your C# code (how the C# code called ReadMemory() including how byMem was declared).

        – Bio.

        Posted by Lim Bio Liong | June 4, 2018, 6:22 pm
  10. HI BIo.

    Thanks for your response. Unfortunately I don’t have C++ code with me. Inside this C++ DLL they called OpenCL DLL. Below is the C++ function declaration.
    extern “C” API_EXPORT int ReadMemory(void *pObj, void* Mem, char * memBuffer, size_t noOfbites);

    Please find the following C# code.

    [DllImport(“ToolDll.dll”, CallingConvention = CallingConvention.StdCall)]
    public static extern int ReadMemory(IntPtr pObj, IntPtr GMem,[In][Out] byte[] cMem, int copySizeInBytes);

    byte[] pixelBuffer = new byte[2048 * 256];

    int retCode = ReadMemory(pObj, PixelData, pixelBuffer , 2048 * 256);

    Posted by Keearu | June 4, 2018, 8:27 pm
  11. ” int retCode = ReadMemory (pObj, PixelData, pixelBuffer , 2048 * 256)” function does just like memcpy. Copying GPU data into CPU buffers.

    Here parameters are
    1. pObj is Object to OpenCl dll. This value coming correctly.
    2. Pixel data (2nd parameter) holds the address of GPU memory. This pointer value is also coming correctly.
    3. pixelBuffer is CPU buffer where the GPU image data copied into pixelBuffer (Expecting issue here)
    4. Size of buffer

    retCode = 0 successfully
    But we always get -30 OpenCl error code

    Code OpenCL Error Flag Function(s) Description
    -30 CL_INVALID_VALUE clGetDeviceIDs, clCreateContext This depends on the function: two or more
    coupled parameters had errors.

    In C++ test application It’s working fine.

    Posted by Keearu | June 5, 2018, 12:18 am
    • Hello Keearu,

      1. To demonstrate the viability of using a C# byte array for a call to ReadMemory(), I suggest that you try out a simple test as follows :
      – Create a simple test DLL named e.g. KeearuNativeDLL.dll.
      – Create an API in KeearuNativeDLL.dll named ReadMemory() with the exact same parameters as the one from ToolDll.dll.

      2. The ReadMemory() API should be a simple dummy one like the following :

      extern “C” API_EXPORT int ReadMemory(void *pObj, void* Mem, char * memBuffer, size_t noOfbites)
      {
      for (int i = 0; i < noOfbites; i++)
      {
      memBuffer[i] = 1;
      }

      return 0;
      }

      That is, it sets all bytes in memBuffer to a value of 1.

      3. Call this dummy ReadMemory() as follows in C# :

      [DllImport("KeearuNativeDLL.dll", CallingConvention = CallingConvention.StdCall)]
      public static extern int ReadMemory(IntPtr pObj, IntPtr GMem, [In][Out] byte [] cMem, int copySizeInBytes);

      void DoTest()
      {
      byte [] byMem = new byte [10000];

      ReadMemory(IntPtr.Zero, IntPtr.Zero, byMem, byMem.Length);
      }

      – Check that the call is successful and that byMem is returned with all values set to 1.

      4. If this works out, it should mean that the call to the actual ReadMemory() of ToolDll.dll will work correctly with the same declaration and way of calling.

      5. Sorry that I cannot comment on the CL_INVALID_VALUE (-30) error value as I have no knowledge of OpenCL.

      – Bio.

      Posted by Lim Bio Liong | June 5, 2018, 1:40 am
      • Hi Bio,

        As you said I created seperate DLL and C# test application with same signature. Its working fine in sample DLL. But same signature and same declaration getting CL_INVALID_VALUE (-30) error with Tool.dll. I don’t know why its happening. Really frustrating.

        Posted by Keearu | June 5, 2018, 11:03 pm
      • Hello Keearu,

        1. Best to consult the team that developed ToolDll.dll.

        2. It is possible that the error resulted from the other parameters.

        3. Sorry that I can’t help you more at this time.

        – Bio.

        Posted by Lim Bio Liong | June 6, 2018, 4:51 am
  12. Hi Bio,

    My issue got fixed. There is an issue with size of the buffer. Once it updated. Everything works fine. Thanks for your timely support. You really guide me in a right direction, I am really glad for that. Thanks again.

    Posted by Keearu | June 7, 2018, 6:09 pm
  13. Hi Bio,

    In C# application Need to get a image data from managed DLL callback. Below describes the Code to understand.

    C++:
    typedef int32_t(__cdecl *CallBack_Event)(char*, int, int); //(ImageBuffer, height, width)

    C#:
    public delegate int CallBack([In][Out]byte[] imageBuff, int height, int width);

    [DllImport(“Tool.dll”, CallingConvention = CallingConvention.StdCall)]
    public static extern int FrameReadyEvent(CallBack callback);

    CallBack _callBack = new CallBack(CallBackEvent);
    FrameReadyEvent(_callBack);

    int CallBackEvent([In][Out]byte[] imageBuff, int height, int width)
    {
    //Saving byte array as bmp
    }

    When I saved data in managed DLL it is saving perfectly. But in wrapper application height and width values are coming correct. But, didn’t get full imageBuffer, just received 1 byte. Can you please suggest me.

    Thanks…

    Posted by Keearu | June 12, 2018, 12:37 am
    • Hello Keearu,

      1. In this situation, where the unmanaged code will actually be passing the char array to the managed side and then have it returned, the unmanaged side will need to have a way to inform the managed side the size of the char array.

      2. The CallBack delegate must be declared as follows :

      [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
      public delegate int CallBack([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)][In][Out]byte[] imageBuff, int ArraySize, int height, int width);

      – Note the Cdecl declaration. This is important since the function type declaration in the C++ code indicate __cdecl calling convention.
      – Declare the imageBuf as being marshaled both [In] and [Out].
      – Also declare that it is marshaled as a long pointer to an Array (LPArray).
      – The SizeParamIndex indicate which parameter is used to indicate the size of the array when marshaling from unmanaged code to managed code.
      – This is important since the C# side CallBack delegate instance (i.e. CallBackEvent()) will actually be called from the C++ side.
      – In this case, SizeParamIndex is set to 1 which indicates that the 2nd parameter “ArraySize” will be used to indicate the size of the array when marshaling it from unmanaged code to managed code.

      3. A sample code for CallBackEvent() on the C# side is as follows :

      static int CallBackEvent([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)][In][Out]byte[] imageBuff, int ArraySize, int height, int width)
      {
      //Saving byte array as bmp
      for (int i = 0; i < ArraySize; i++)
      {
      imageBuff[i] = 1;
      }

      return 0;
      }

      4. On the C++ side, do the following :

      – Declare the CallBack_Event() function type as follows :

      typedef int32_t (__cdecl *CallBack_Event)(char*, int, int, int);

      – A sample code for FrameReadyEvent is as follows :

      extern "C" API_EXPORT int __stdcall FrameReadyEvent(CallBack_Event callback)
      {
      char char_array[100] = { 0 };

      // Note the second parameter is 100 which is the size
      // of char_array.
      int32_t iRetTemp = callback(char_array, 100, 10, 10);

      return 0;
      }

      – Bio.

      Posted by Lim Bio Liong | June 12, 2018, 4:23 pm
      • Wow Awesome Bio. Its working. I gain so much of knowledge from you.
        Thanks a lot.

        Posted by Keearu | June 13, 2018, 10:38 pm
  14. Hi Bio,
    Thanks for your support so far. I need to read following C++ structure(_FHandle) from C# application please look into below files.
    Here call back function (public delegate int Callback(IntPtr handle);) is created in c# application this callback was invoked from C++ DLL.
    C++
    struct _FData
    {
    int32_t Parm1;
    int32_t Parm2;
    int64_t Parm3;
    int32_t Parm4;
    int32_t Parm5;
    int32_t Parm6;
    int32_t Parm7;
    struct
    {
    double dParam1;
    double dParam2;
    double dParam3;
    double dParam4;
    double dParam5;
    }
    Padd;
    struct
    {
    double dParam6;
    double dParam7;
    double dParam8;
    }
    ExtraPadd;
    double dParam9;
    double dParam10;
    }FData;
    struct _FHandle
    {
    ntptr_t Data;
    intptr_t Content;
    _FData *FData;
    intptr_t res;
    }FHandle;
    C#
    public struct Padd
    {
    double dParam1;
    double dParam2;
    double dParam3;
    double dParam4;
    double dParam5;
    }
    public struct ExtraPadd
    {
    double dParam6;
    double dParam7;
    double dParam8;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _FData
    {
    int32_t Parm1;
    int32_t Parm2;
    int64_t Parm3;
    int32_t Parm4;
    int32_t Parm5;
    int32_t Parm6;
    int32_t Parm7;
    [MarshalAs(UnmanagedType.Struct)]
    public Padd padd;
    [MarshalAs(UnmanagedType.Struct)]
    public ExtraPadd extraPadd;
    double dParam9;
    double dParam10;
    };
    [StructLayout(LayoutKind.Sequential)]
    public struct _FHandle
    {
    public IntPtr Data;
    public IntPtr Content;
    [MarshalAs(UnmanagedType.Struct)]
    public IntPtr FData;
    public IntPtr Res;
    }
    public delegate int Callback(IntPtr handle);
    Log _cBack = new Log(CallBackFunction);
    int respCode = Start(_cBack);
    int CallBackFunction(IntPtr handle)
    {
    _FHandle h = (_FHandle)(Marshal.PtrToStructure(handle, typeof(_FHandle)));
    _FData frData = (_FData)(Marshal.PtrToStructure(h.FData, typeof(_FData)));
    }
    I tried to read structure in above manner. I got full _FHandle data. And in _FData few parameters are coming correctly. But, padd and extraPadd data are not coming properly.
    Can you please suggest me what is the correct way of reading above nested structures. Thanks…

    Posted by Keearu | June 21, 2018, 7:41 pm
  15. Hi Bio,

    In the above example I tried to read _Fdata structure in different approach like below. In _Fdata structure Padd and ExtraPadd are two non pointer bittable structures. Seems it should be stored in a continuous memory. So I modified C# structure like below.

    public struct _FData
    {
    int Parm1;
    int Parm2;
    long Parm3;
    int Parm4;
    int Parm5;
    int Parm6;
    int Parm7;

    double dParam1; // Padd structure variables
    double dParam2;
    double dParam3;
    double dParam4;
    double dParam5;

    double dParam6; //ExtraPadd structure variables
    double dParam7;
    double dParam8;

    double dParam9;
    double dParam10;
    };

    Even though I can’t get the data from dParam1 to dParam10. But I can able to read param1 to param7 data. Since long I was stuck here. Can you please suggest me to fix this issue.

    Thanks…

    Posted by Keearu | June 27, 2018, 9:00 pm

Trackbacks/Pingbacks

  1. Pingback: Passing Managed Structures With Arrays To Unmanaged Code Part 1 « limbioliong - June 29, 2011

  2. Pingback: Passing a Pointer to a Structure from C# to C++ Part 1. « limbioliong - August 20, 2011

  3. Pingback: Passing Managed Structures With Strings To Unmanaged Code Part 2 « limbioliong - August 24, 2011

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: