random thoughts, formed in the twisted mind of a coder... RSS 2.0
# Sunday, 25 January 2009

When I wrote the article "Marshalling: Using native DLLs in .NET", I had no clue that so many people have problems using native DLLs in managed code. I receive comments and email very frequently about this topic. Most of the times people ask me to help them writing the C# code that does the import of the unmanaged DLL.

To keep the story short... I'm in the process of writing a Marshalling Compiler (of some sort). It's an online compiler which takes a C style header as input and delivers C# code as output. This C# output code consists of function imports, struct definitions and enum definitions.

Know that it's not a perfect compiler and it probably never will be. Though I'll try to keep improving the functionality until most people can use it.

Go to the Marshalling Compiler

For more information on Marshalling in general, please read "Marshalling: Using native DLLs in .NET"

If you encounter any errors which shouldn't occur, please post your comments on this page. But before you do, please first check if your C style header is correct and please also check that the error wasn't reported by someone else.

 

version 0beta1:

This is the initial version on the web. It converts a C style header, but the output is simple. It still lacks the generation of code that will dereference pointers and so. Please use it to test it. There is also a test header file for download at the compiler page.

 

Sunday, 25 January 2009 15:11:54 (W. Europe Standard Time, UTC+01:00)  #    Comments [11]
.Net | Marshalling
Tuesday, 17 February 2009 09:06:53 (W. Europe Standard Time, UTC+01:00)
Ashish Jain, has found a bug in the Marshalling Compiler.

The compiler always declares the class containing the imports like this:

public class Imported
{

This is of course flawed when using unsafe types or references. The class should be declared like this:

internal unsafe class Imported
{

You could omit the "internal" modifier, but it would be wise to use it in a clean-coding-kind-of-way... ish..
Tuesday, 21 April 2009 15:39:59 (W. Europe Daylight Time, UTC+02:00)
I tried to use your compiler (to call a legacy dll) but it failed with the message:
#0: Error: 'The compiler does not support the use of nested arrays with an assigned depth. Invalid examples are: int[10][11], int[10][]. Valid examples are: int[10], int[], int[][10]'

The header-file I tried with was:

struct structSeasonCoeffs
{
float fDUD[4][5][12][5];
float fFAM[4][14][12];
float fFAKP[4][29][16][6];
float fFAKABP[4][2][6];
float fVDARRAY[4][5][12][2];
};

struct structIonosphereData
{
int nKA[9][20];
float fUU1[9][13][76];
float fUU2[9][13][76];
};

void File2StructNoiseData(char *path, structSeasonCoeffs* pSeasonCoeffs);

void File2StructIonosphereData(char *path, int nMonth, structIonosphereData* pstIonsphereData);


I have managed to call the first method (File2StructNoiseData), with success, with this C# code:
[StructLayout(LayoutKind.Sequential)]
public struct structSeasonCoeffs
{
unsafe fixed float fDUD[1200]; // float[4, 5, 12, 5]
unsafe fixed float fFAM[672]; // float[4,14,12]
unsafe fixed float fFAKP[11136]; // float[4,29,16,6]
unsafe fixed float fFAKABP[48]; // float[4,2,6]
unsafe fixed float fVDARRAY[480]; // float[4,5,12,2]
};

[DllImport(@"C:\Projekt\TCT\src\Straca\vendor\HF\readHF.dll", EntryPoint=@"?File2StructNoiseData@@YAXPADPAUstructSeasonCoeffs@@@Z", CharSet=CharSet.Auto, SetLastError=true)]
private unsafe static extern void File2StructNoiseData([MarshalAs(UnmanagedType.LPStr)] StringBuilder path,
ref structSeasonCoeffs pSeasonCoeffs);

structSeasonCoeffs _pstSeasonCoeffs = new structSeasonCoeffs();
StringBuilder sb = new StringBuilder(path + @"\HFCalculationData\Noisedat\");
File2StructNoiseData(sb, ref _pstSeasonCoeffs);

But when I try the same (almost) with the other one (C#):
[StructLayout(LayoutKind.Sequential, Size=17964)]
public struct structIonosphereData
{
unsafe fixed int nKA[180]; // int[9, 20]
unsafe fixed float fUU1[8892]; // float[9, 13, 76]
unsafe fixed float fUU2[8892]; // float[9, 13, 76]
};

[DllImport(@"C:\Projekt\TCT\src\Straca\vendor\HF\readHF.dll", EntryPoint=@"?File2StructIonosphereData@@YAXPADHPAUstructIonosphereData@@@Z")]
private static extern void File2StructIonosphereData([MarshalAs(UnmanagedType.LPStr)] StringBuilder path, int nMonth, ref structIonosphereData pstIonsphereData);

sb = new StringBuilder(path + @"\HFCalculationData\Ionosphere\");
structIonosphereData pstIonosphereData = new structIonosphereData();
File2StructIonosphereData(sb, 3, ref pstIonosphereData);


Then I get this exception: MarshalDirectiveException with message "Cannot marshal 'parameter #3': Internal limitation: structure is too complex or too large."

What is the problem? Is there some kind of limit at 16Kb? Why? And how can I get around it? The first struct is ~15.5Kb and the second is ~18Kb. Or can't I multiply the dimensions together ([9][13][76] --> [8892])? It seems to work fine with the first struct.

Please help if you can!
Best regards,
Andreas

(sorry about the spaces/indentation, don't know how to get it more readable)
Andreas
Wednesday, 22 April 2009 09:11:44 (W. Europe Daylight Time, UTC+02:00)
Hi Andreas,

You might want to compile your header like this:

-----------------------------

struct structSeasonCoeffs
{
float fDUD[][][][5];
float fFAM[][][12];
float fFAKP[][][][6];
float fFAKABP[][][6];
float fVDARRAY[][][][2];
};

struct structIonosphereData
{
int nKA[][20];
float fUU1[][][76];
float fUU2[][][76];
};

extern void File2StructNoiseData(char *path, structSeasonCoeffs* pSeasonCoeffs);

extern void File2StructIonosphereData(char *path, int nMonth, structIonosphereData* pstIonsphereData);

-----------------------------

It results in the following code:

-----------------------------

using System;
using System.Runtime.InteropServices;

public class Imported
{

[StructLayout(LayoutKind.Explicit, Size=124)]
public struct structSeasonCoeffs
{
[FieldOffset(0)] void*[] fDUD;
[FieldOffset(20)] void*[] fFAM;
[FieldOffset(68)] void*[] fFAKP;
[FieldOffset(92)] void*[] fFAKABP;
[FieldOffset(116)] void*[] fVDARRAY;
}

[StructLayout(LayoutKind.Explicit, Size=688)]
public struct structIonosphereData
{
[FieldOffset(0)] void*[] nKA;
[FieldOffset(80)] void*[] fUU1;
[FieldOffset(384)] void*[] fUU2;
}

[DllImport(path_to_dll)]
public static extern void File2StructNoiseData([MarshalAs(LPStr)] string path, void* pSeasonCoeffs);

[DllImport(path_to_dll)]
public static extern void File2StructIonosphereData([MarshalAs(LPStr)] string path, int nMonth, void* pstIonsphereData);

}

-----------------------------

Now, you'll probably get some memory errors when you run this, because in the header file I stripped all nested arrays with a fixed size.
So what you have to do now, is count all the bytes and manually adapt the C# code.

For example, the line:
. float fDUD[4][5][12][5];

contains 4 pointers to 5 pointers to 12 pointers to 5 floats
Now, a float takes 4 bytes and on a 32 bit system, a pointer also takes 4 bytes.
This means that you have 4*5*12*5*4 = 4800 bytes

And if you look at the C# code:
. [FieldOffset(0)] void*[] fDUD;
. [FieldOffset(20)] void*[] fFAM;

You see that you have to change the field offset of fFAM to 4800. And so on for every field.
Also you have to change the line:
. [StructLayout(LayoutKind.Explicit, Size=124)]

Change the Size to the total number of bytes of the complete struct.

Though, now the really hard part is... how are you going to reconstruct your floats from the nested arrays?


Thursday, 23 April 2009 13:46:56 (W. Europe Daylight Time, UTC+02:00)
Thank you for your help! It helped me to find the problem. There where two problems:

* I had incorrectly multiplied the number of floats to Size=17964 and forgot to multiply with 4 (the size of the float itself!)
* I used ref structIonosphereData pstIonsphereData instead of void* pstIonsphereData in the function call

After that both calls worked!

But one new problem appeared. The second function should be called 12 times, one for each month. And therefore create 12 different structs with data. Something like this:

structIonosphereData[] pstIonosphereDataList = new structIonosphereData[12];
for(int i = 1; i <= 12; i++) {
File2StructIonosphereData(path + @"\HFCalculationData\Ionosphere\", i, &pstIonosphereDataList[i]);
}

But this doesn't work. I can't create an array for 12 different structs with fixed values. I tried different approaches but nothing seems to work. Any idéas?

Best regards,
Andreas
Andreas
Friday, 24 April 2009 11:08:49 (W. Europe Daylight Time, UTC+02:00)
Hi Andreas,

Does the function File2StructIonosphereData create a structure (allocates memory), or does it fill a passed structure with data?
If the last is the case, then you still need to create the structure in C#. In your code you've only created an array, but not the structure types yet.

Furthermore, you could probably better use an IntPtr instead of &pstIonosphereDataList[i].
Please read an other article of me: http://blog.rednael.com/2008/08/29/MarshallingUsingNativeDLLsInNET.aspx
There is a special edit where I explain on how to pass structures.


Friday, 29 January 2010 13:13:49 (W. Europe Standard Time, UTC+01:00)
Hi,
I've one c++ unmanaged dll and two headers (.h). I'd like to write a wrapper for c# project.
I use the Marshalling compiler and i got some problems

1) Marshalling enum
enum UNIPRO_Sex {MALE = '0', FEMALE = '1', SEX_UNKNOWN = '2'}

2) Marshalling struct
typedef struct rhythm_data
{
unsigned short NumberOfBytes;
unsigned char *RhythmData;
} RHYTHM_DATA;

typedef struct sectionG
{
int SectionExists;
RHYTHM_DATA *RhythmData;
} SECTIONG;

3) something wrong in offset count
typedef struct sectionB
{
int SectionExists;
bool LongVersion;
int NumberOfTags;
} SECTIONB;

is wrapped as

[StructLayout(LayoutKind.Explicit, Size=12)]
public struct sectionB
{
[FieldOffset(0)] int SectionExists;
[FieldOffset(4)] [MarshalAs(Bool)] bool LongVersion;
[FieldOffset(8)] int NumberOfTags;
}

A bool isn't equivalent to 1 Byte? I'd expect the following result

[StructLayout(LayoutKind.Explicit, Size=12)]
public struct sectionB
{
[FieldOffset(0)] int SectionExists;
[FieldOffset(4)] [MarshalAs(Bool)] bool LongVersion;
[FieldOffset(5)] int NumberOfTags;
}

How can i solve these issues? Please help me.
pyth
Monday, 01 February 2010 15:37:26 (W. Europe Standard Time, UTC+01:00)
Hi pyth,

Just simply check the checkbox below the input field, named: bool-is-one-byte

Thursday, 18 March 2010 05:03:31 (W. Europe Standard Time, UTC+01:00)
HI All,

I have a problem in passinf the structure in c++, it throws an error "Cannot marshal 'parameter #2': Internal limitation: structure is too complex or too large."

Note I have a main structure in which I have array sub structure, Enum.

Please help me in sorting this issue
Pavan Mailwar
Wednesday, 05 May 2010 20:30:09 (W. Europe Daylight Time, UTC+02:00)
I've tried your marshal compiler and liked it. I find a bug or at least a potential for enhancement:

this c - header line:
extern "C" int* Create(unsigned char *key, int keyLen);

Will be compiled to:

[DllImport(path_to_dll)]
public static extern void* Create([MarshalAs(LPStr)] string key, int keyLen);

first thing: the void* which would be better to marshal to IntPtr? Second: MarshalAs(LPStr) which should be: MarshalAs(UnmanagedType.LPStr).

Martin
Monday, 07 June 2010 18:33:33 (W. Europe Daylight Time, UTC+02:00)
When I compile the following code, the .NET coded structure does not allocate any space for the nested TDISKINFO[2] structures.


typedef unsigned long DWORD;

typedef struct TDISKINFO
{
char Drive[8];
char Name[32];
DWORD SerialNumber;
} ;

typedef struct TMACHINEINFO
{
DWORD IV;
char ComputerName[32];
bool HasSerialNumber;
char Pad1[3];
DWORD Features;
DWORD SerialNo[3];
DWORD dwProcessorType;
DWORD dwProcessorLevel;
DWORD wProcessorRevision;
char Hostname[20];
bool HasEthernet;
char Pad2[1];
char EthernetAdr[6];
int nDisks;
TDISKINFO Disks[2];
int MajorVer;
int MinorVer;
int BuildA;
int BuildB;
int BuildC;
} ;


Tuesday, 29 June 2010 22:49:41 (W. Europe Daylight Time, UTC+02:00)
This is an awesome tool. Thanks for taking the time to create this. Also, I'm glad I read your "Marshalling: Using native DLLs in .NET" article before running into this tool; your article contains much needed information which I would have missed if I ran into this tool before reading your article. Folks, if you haven't read "Marshalling: Using native DLLs in .NET", I encourage you to read it before using this tool.

Note to users: White space matters.

Consider the following code taken from a c++ header...

typedef struct testStruct
{
char compDate[11];
};


...is different from...

typedef struct testStruct
{
char compDate [11]; /* Get rid of space in front of opening bracket. */
};

The first block of code compiles without problems, but the second block of code does not. Notice the space between the variable and the opening bracket of the array index; this space will cause failure.

-Mike
All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2017
Martijn Thie
Sign In
Statistics
Total Posts: 18
This Year: 0
This Month: 0
This Week: 0
Comments: 183
All Content © 2017, Martijn Thie
DasBlog theme adapted from 'Business' (originally by delarou)