Calling a C function from C# with struct' as a parameter.

T

Treadstone

Hi,

Being a newbie to C# and having worked with C all the way, I am stuck
with an interop problem.

I have a DLL exporting certain functions (and written in C).
The signature of 1 such function is as follows:

extern "C"
{
__declspec(dllexport) int EnumerateDevice(DEVICE **DeviceList);
}

struct DEVICE
{
int DeviceNo;
DEVICE *next;
};

I need to call this function from my C# code. How do I go about doing
this? I went through some interop samples but most of them use basic
data types (int and char) as parameters in their examples.

Any code sample in this regard would be useful.

Thanks in advance.

Regards,
Treadstone
 
C

Christof Nordiek

Treadstone said:
Hi,

Being a newbie to C# and having worked with C all the way, I am stuck
with an interop problem.

I have a DLL exporting certain functions (and written in C).
The signature of 1 such function is as follows:
Do you know this link:
http://www.pinvoke.net

It doesn't contain your method, but has some examples, of Marshalling
structs witch P/Invoke.

HTH
Christof
 
N

Nicholas Paldino [.NET/C# MVP]

Treadstone,

First, you are going to have to define the structure:

[StructLayout(LayoutKind.Sequential)]
public struct Device
{
public int DeviceNo;
public IntPtr Next;
}

You use an IntPtr for the Next parameter because you are going to
marshal it manually when you need it. Then, you define your API like this:

[DllImport("dllname.dll")]
static int EnumerateDevice(ref IntPtr DeviceList);

You would then call the api passing an IntPtr to your method. When you
get it back, you would call the static PtrToStructure method on the Marshal
class to get an instance of the Device structure back. You would also need
to do this for next item in the list, as pointed out by the Next field on
the structure.

Finally, you will probably have to deallocate the memory allocated by
the call to EnumerateDevice, as well as all the items pointed to by the Next
field in each of the structures in the enumeration.
 
T

Treadstone

Nicholas,

I defined the structure in the manner suggested by you, along with the
DllImport declaration.
However, the moment I called either Marshal.AllocCoTaskMem or
Marshal.AllocHGlobal or Marshal.PtrToStructure, I am getting a
System.NotSupported exception.

My code inside my function is as follows:

//BEGINNING OF CODE SNIPPET

IntPtr DeviceList = IntPtr.Zero;
DEVICE device = new DEVICE( ); //The DEVICE structure has been defined
already.

Marshal.AllocHGlobal(Marshal.SizeOf(device); //This gives me a
System.NotSupportedException

//My function call
iReturnValue = EnumerateDevice(ref DeviceList);

Marshal.PtrToStructure(DeviceList, device) //If I do not invoke the
Marshal.AllocHGlobal call, then
the //
System.NotSupportedException occurs here.

//END OF CODE SNIPPET

I tried the Marshal.PtrToStructure with all possible combinations of
the overloaded methods, but ended up with the same result.
Am I making some mistake while calling the methods of Marshal (such as
AllocHGlobal/ AllocCoTaskMem/PtrToStrucuture)? Or does it have
something to do with the version of VS2005?

Thanks in Advance.

Christof: Thanks for the pointer to the website. It has been of great
help to me.

Regards,
Treadstone
 
M

mh

Marshal.StructureToPtr(device, DeviceList, true);
iReturnValue = EnumerateDevice(ref DeviceList);
DEVICE retdevice = (DEVICE)Marshal.PtrToStructure(DeviceList,
typeof(DEVICE))

MH
 
T

Treadstone

MH,

Tried that as well. Still the same result.
I'm getting a System.NotSupportedException in the first statement
itself:

Marshal.StructureToPtr(device, DeviceList, true); //This method
throws an exception.

Tried with various permutations and combinations as well but in vain.

Thanks in advance.

Regards,
Treadstone
 
M

mh

Add a constructor to the the DEVICE structure to initialize the
variables.


[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct DEVICE
{
public int DeviceNo;
public IntPtr Next;
public DEVICE(int _DeviceNo, IntPtr _Next)
{
DeviceNo = _DeviceNo;
Next = _Next;
}
}

//This part works without throwing any exceptions

DEVICE device = new DEVICE(0 , IntPtr.Zero);
IntPtr DeviceList = Marshal.AllocHGlobal(Marshal.SizeOf(device));
Marshal.StructureToPtr(device, DeviceList, true);
//..

MH
 
T

Treadstone

MH,

Thanks a ton. That worked. But the addition of another variable into
the structure saw to it that my woes did not end.
A variable of type string was added into the structure.

My new structure in my C Dll is as follows:

struct DEVICE
{
int DeviceNo;
string DeviceName;
DEVICE *next;
}
From few of the interop articles that I went through, I understood
that a string has to be declared as an IntPtr.
So, in my C# code, here are the modifications I made:

public struct DEVICE
{
public int DeviceNo;
public IntPtr DeviceName;
public IntPtr Next;
}

// In my code.
DEVICE device = new DEVICE( );
IntPtr DeviceList = Marshal.AllocHGlobal(Marshal.SizeOf(device));
device.DeviceName = Marshal.AllocHGlobal(256);

//My function call
iReturnValue = EnumerateDevice(ref DeviceList); // The EnumerateDevice
returns all the device names in range.
DEVICE retdevice = (DEVICE)Marshal.PtrToStructure(DeviceList,
typeof(DEVICE)) ;

//Here I am trying to access the DeviceName that is returned by the
EnumerateDevice Function.
string MyString = Marshal.PtrToStringUni(device.DeviceName); //code
fails here.

The moment I try something like this, the active sync connection to my
mobile (on which the application is running) breaks down.

Am I doing some elementary mistake here?

Thanks in advance.

Regards,
Treadstone.
 
M

mh

If possible, try to redefine DeviceName as char* or wchar* rather than
string in the C dll. In most cases .NET can not handle C/C++ strings
properly or not at all. Also depending on the DeviceName (ANSI/W) data
type, you need to use either PtrToStringUni or PtrToStringAnsi
versions.

struct DEVICE
{
int DeviceNo;
char *DeviceName; //wchar*
DEVICE *next;
}

MH
 
T

Treadstone

MH,

Tried that as well. (Changing the data type to char* / wchar*). :(
Also, tried using the PtrToStringUni and PtrToStringBSTR
(PtrToStringAnsi isn't suppported with Compact Framework, I presume).

Each time I use the PtrToStringUni method, the application hangs and
the connection to my device is broken. It doesn't even throw an
exception.

Thanks.
Treadstone.
 
M

mh

How are you allocating the string in the C dll? Are you using
SysAllocString, GlobalAlloc, malloc, ...?

MH
 
T

Treadstone

In the C Dll, the memory has been allocated for the structure by using
'new'.
The code in the C Dll is as follows:

__declspec(dllexport) int EnumeratDevice(DEVICE **DeviceList,int
*pNum)
{
DEVICE *BTDevicesList = NULL;
DEVICE *BTSpecificDevice = NULL;
DEVICE *BTDevice = NULL;

BTDevice = new DEVICE;

//Enumerate BT devices here.
....
....
BTDevicesList = BTDevice;

*DeviceList = BTDevicesList;
return SUCCESS;
}

But, does the manner in which the memory is allocated make an impact
on the marshalling?

-Treadstone.
 
W

Willy Denoyette [MVP]

Treadstone said:
In the C Dll, the memory has been allocated for the structure by using
'new'.
The code in the C Dll is as follows:

__declspec(dllexport) int EnumeratDevice(DEVICE **DeviceList,int
*pNum)
{
DEVICE *BTDevicesList = NULL;
DEVICE *BTSpecificDevice = NULL;
DEVICE *BTDevice = NULL;

BTDevice = new DEVICE;

//Enumerate BT devices here.
....
....
BTDevicesList = BTDevice;

*DeviceList = BTDevicesList;
return SUCCESS;
}

But, does the manner in which the memory is allocated make an impact
on the marshalling?

-Treadstone.


This doesn't show us how your C string looks like, if this is a non-Unicode
build, the string will be an ANSI string, as you are marshaling the IntPtr
as pointing to a Unicode string, this will fail.

Willy.
 
T

Treadstone

Willy, MH,

It finally worked. I changed the struct definition in my C# code and
it looked something like this:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
struct DEVICE {
public int DeviceNo;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public string
DeviceName;
public IntPtr Next;
}

And in the function, the Marshal.PtrToStructure method succeeded and I
could access DEVICE.DeviceName.
I dint have to do anything else after that.

If someone else has a similar problem (strings within structures),
this link might provide some useful pointers.
http://msdn2.microsoft.com/en-us/library/s9ts558h.aspx#cpcondefaultmarshalingforstringsanchor2

Willy, MH thanks a lot for all the help.

Regards,
Treadstone.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top