[.net] importing a DLL, unmanaged code to C#(part 2)

Started by
7 comments, last by OpenGL_Guru 16 years, 10 months ago
hi all, i had a previous post about importing some C API functions and got most of it to work and i have a problem here now with a certain function of the API and need some help as to what could be causing the problem. the API i am using is here pay special attention to the definition of SHPObject and SHPReadObject and this is what is in question. as you know since C# doesnt have includes i had to recreate the SHPObject structure myself. i did that here:

[StructLayout(LayoutKind.Sequential)]
    struct SHPObject
    {
	 public int	nSHPType;	
	 public int	nShapeId; 	
	 public int	nParts;		
	 public IntPtr	panPartStart;  
	 public IntPtr	panPartType;    
	 public int	nVertices;
         public IntPtr	padfX;		
	 public IntPtr	padfY;
	 public IntPtr	padfZ;		
	 public IntPtr	padfM;	
	 public double	dfXMin;
         public double	dfYMin;
	 public double	dfZMin;
	 public double	dfMMin;
	 public double	dfXMax;
	 public double	dfYMax;
	 public double	dfZMax;
	 public double	dfMMax;
    };



next is my DLL import code/function definition and implementation

[DllImport("shapelib.dll", CharSet = CharSet.Ansi, EntryPoint = "SHPReadObject", CallingConvention=CallingConvention.Cdecl)]
private static extern IntPtr internalSHPReadObject(SHPInfo hSHP, int ishape);
............
............
public static SHPObject SHPReadObject(SHPInfo hSHP, int ishape)
{  
 IntPtr ptr = internalSHPReadObject(hSHP, ishape);
 return (SHPObject)Marshal.PtrToStructure(ptr, typeof(SHPObject));
}
....
....



i have checked the export DEF file to see and make sure SHPReadObject is defined and it is so no problem there. if you notice the SHPReadObject from the link that i gave you you see it is returning a pointer. this may be why i am having the problem. going through the debug process ptr returns a NULL so naturally when i try to return it it crashes with a system NULL reference. There is only one place in the original C API function though where it returns NULL. here is the piece of relevant code from that:

SHPObject SHPAPI_CALL1(*)
SHPReadObject( SHPHandle psSHP, int hEntity )

{
 SHPObject *psShape;

/* -------------------------------------------------------------------- */
/*      Validate the record/entity number.                              */
/* -------------------------------------------------------------------- */
    if( hEntity < 0 || hEntity >= psSHP->nRecords )
        return( NULL );
  .........
  .........
  psShape = (SHPObject *) calloc(1,sizeof(SHPObject));
  psShape->nShapeId = hEntity;
  ........
  ........
 
  return (psShape);


through my debugging hEntity is 0 or greater and the number of records are also greater then hEntity which is just a shape number that i am reading in. SHPAPI_CALL1(*) - i am not sure about this - whether its an old stdcall but it is defined in the C API header file as such:

//#define SHPAPI_CALL __declspec(dllexport) __stdcall       
//#define SHPAPI_CALL1 __declspec(dllexport) * __stdcall

#ifdef SHAPELIB_DLLEXPORT
#  define SHPAPI_CALL __declspec(dllexport)
#  define SHPAPI_CALL1(x)  __declspec(dllexport) x
#endif

#ifndef SHPAPI_CALL
#  define SHPAPI_CALL
#endif

#ifndef SHPAPI_CALL1
#  define SHPAPI_CALL1(x)      x SHPAPI_CALL
#endif



do i need to change my calling convention? is my function defintion appropriate? any help would be appreciated or from anyone that helped me last time. thanks!
heh
Advertisement
What is SHPInfo defined as ? because the API suggests this should be a handle.
Quote:Original post by Niksan2
What is SHPInfo defined as ? because the API suggests this should be a handle.


SHPInfo is a handle defined in the API as:

typedef	struct{    FILE        *fpSHP;    FILE	*fpSHX;    int		nShapeType;				/* SHPT_* */    int		nFileSize;				/* SHP file */    int         nRecords;    int		nMaxRecords;    int		*panRecOffset;    int		*panRecSize;    double	adBoundsMin[4];    double	adBoundsMax[4];    int		bUpdated;    unsigned char *pabyRec;    int         nBufSize;} SHPInfo;typedef SHPInfo * SHPHandle;


in my C# code i have to recreate the struct and i did here:
[StructLayout(LayoutKind.Sequential)]struct SHPInfo{ public IntPtr   fpSHP; public IntPtr   fpSHX; public int	 nShapeType;				/* SHPT_* */ public int	 nFileSize;				    /* SHP file          public int      nRecords; public int	 nMaxRecords; public IntPtr	 panRecOffset; public IntPtr	 panRecSize; [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)] public double[] adBoundsMin; [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)] public double[] adBoundsMax; public int	 bUpdated; string          pabyRec; int             nBufSize;};


i successfully created a handle of this type by overriding the SHPOpen function that is also on the link at the beginning of this. i guess i could give you the whole C# code that i have and you can see for yourself. once i enter the SHPReadObject, in the debugger the handle is passed in successfully and data is in it and its correct so i dont think that this is the problem. here is the whole C# code. please let me know if you have any questions. you can also refer back to my first thread about DLL imports to get some code screenshots. the link to that first thread is here .
heh
So you're passing in a structure, when it requires a pointer to a structure ?
Quote:Original post by Niksan2
So you're passing in a structure, when it requires a pointer to a structure ?


sorry, yesterday the site went down(i think?) and i could add my code that i had intended to add in the first place. basically when you open a file you create a handle to that file, which gives you all the attributes that you can access from the file via the members of the struct. you will see in the code below that i did that and then i pass that handle in there. when debugging the data is all there and nothing seems to be corrupted.
using System;using System.Collections;using System.Runtime.InteropServices;using System.Text;using System.IO;namespace shapeFileReader{    [StructLayout(LayoutKind.Sequential)]    struct SHPInfo    {	 public IntPtr   fpSHP;	 public IntPtr   fpSHX;  	 public int	 nShapeType;					 public int	 nFileSize;				            public int      nRecords;	 public int	 nMaxRecords;	 public IntPtr	 panRecOffset;	 public IntPtr	 panRecSize;	 [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]	 public double[] adBoundsMin;  	 [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]	 public double[] adBoundsMax;	 public int	 bUpdated;	 string          pabyRec;         int             nBufSize;    };    [StructLayout(LayoutKind.Sequential)]    struct SHPObject    {	 public int	nSHPType;		 public int	nShapeId; 		 public int	nParts;			 public IntPtr	panPartStart;  	 public IntPtr	panPartType;    	 public int	nVertices;         public IntPtr	padfX;			 public IntPtr	padfY;	 public IntPtr	padfZ;			 public IntPtr	padfM;		 public double	dfXMin;         public double	dfYMin;	 public double	dfZMin;	 public double	dfMMin;	 public double	dfXMax;	 public double	dfYMax;	 public double	dfZMax;	 public double	dfMMax;    };	[StructLayout(LayoutKind.Sequential)]	struct DBFInfo	{		public IntPtr   fp;		public int      nRecords;		public int	nRecordLength;		public int	nHeaderLength;		public int	nFields;		public IntPtr	panFieldOffset;		public IntPtr	panFieldSize;		public IntPtr	panFieldDecimals;		public string   pachFieldType;		public string   pszHeader;		public int	nCurrentRecord;		public int	bCurrentRecordModified;		public string   pszCurrentRecord;		public int	bNoHeader;		public int      nBufSize;	};    class Class1	{	[DllImport("shapelib.dll", EntryPoint = "SHPOpen")]	private static extern IntPtr internalSHPOpen(string pszLayer, string pszAccess);	[DllImport("shapelib.dll", CharSet = CharSet.Ansi, EntryPoint = "SHPReadObject", CallingConvention=CallingConvention.Cdecl)]	private static extern IntPtr internalSHPReadObject(SHPInfo hSHP, int ishape);	[DllImport("shapelib.dll", EntryPoint = "DBFOpen")]	private static extern IntPtr internalDBFOpen(string pszShapeFile, string pszAccess);        	 public static SHPInfo SHPOpen(string pszLayer, string pszAccess)	 {	  IntPtr ptr = internalSHPOpen(pszLayer, pszAccess);	  return (SHPInfo)Marshal.PtrToStructure(ptr, typeof(SHPInfo));	 }	 public static SHPObject SHPReadObject(SHPInfo hSHP, int ishape)	 {  	  IntPtr ptr = internalSHPReadObject(hSHP, ishape);	  return (SHPObject)Marshal.PtrToStructure(ptr, typeof(SHPObject));	 }	 public static DBFInfo DBFOpen(string pszShapeFile, string pszAccess)	 {	  IntPtr ptr = internalDBFOpen(pszShapeFile, pszAccess);	  return (DBFInfo)Marshal.PtrToStructure(ptr, typeof(DBFInfo));	 }	       static void Main(string[] args)		{                 SHPInfo mySHPHandle;                 DBFInfo myDBFHandle;     	         SHPObject myObject;                             mySHPHandle = SHPOpen("BG2k_clip.shp", "rb");                 myDBFHandle = DBFOpen("BG2k_clip.dbf", "rb");                 myObject = SHPReadObject(mySHPHandle, 0);                } //main	} //class}//namespace


i am able to open the files and get the handles correctly and even probe the members of the structs to get values and they are correct.

the 2 API's(SHP and DBF) are here and here respectively. like i said before the Stdcall stuff seems to be different than the other 2 structs that i am redefining, so i was thinking this was the difference/???
heh
typedef struct
{
////
} SHPInfo;

typedef SHPInfo * SHPHandle;


The API you originally linked to takes SHPHandle as its first parameter, you pass in SHPInfo, so you're passing through an actual structure rather than a pointer to a structure, that's your problem.

yours
public static SHPObject SHPReadObject(SHPInfo hSHP, int ishape)

theirs

SHPObject *SHPReadObject( SHPHandle hSHP, int iShape );
Quote:Original post by Niksan2
typedef struct
{
////
} SHPInfo;

typedef SHPInfo * SHPHandle;


The API you originally linked to takes SHPHandle as its first parameter, you pass in SHPInfo, so you're passing through an actual structure rather than a pointer to a structure, that's your problem.

yours
public static SHPObject SHPReadObject(SHPInfo hSHP, int ishape)

theirs

SHPObject *SHPReadObject( SHPHandle hSHP, int iShape );


yes i understand that but i had to recreate the struct in C# since C# doesnt have pointers. so how do i recreate my current SHPInfo to match the C API version? or better yet how should i have my internalSHPReadObject defined? i thought IntPtr took care of that regardless of how the struct was created?

my previous thread initially when i was having problems is here to see what others were saying. my problem you'll see though from the beginning is that C# couldnt locate the file outside the bin/Debug directory.
heh
Niksan2 is incorrect (sort of). I think he overlooked the internal version of the function that returns a pointer.

You're right, the returned IntPtr contains the pointer to the c struct in memory. Then you convert the data at that location to the c# version. That part should work fine.

But he's also correct, that the first argument has to be passed in as a pointer. This is why it's a good way to store the IntPtr returned by functions before marshaling to a struct.

Might I suggest that SHPReadObject be redefined as follows? I should have described it this way in the original thread, but you were getting inundated with details and didn't want to make things worse. In hind sight that was a mistake. This change should be made to all the functions that return or receive a pointer.

[DllImport("shapelib.dll", CharSet = CharSet.Ansi, EntryPoint = "SHPReadObject", CallingConvention=CallingConvention.Cdecl)]private static extern IntPtr internalSHPReadObject(IntPtr hSHP, int ishape);........................public static IntPtr SHPReadObject(IntPtr hSHP, int ishape, out SHPObject shpObject){   IntPtr ptr = internalSHPReadObject(hSHP, ishape); shpObjcet = (SHPObject)Marshal.PtrToStructure(ptr, typeof(SHPObject)); return ptr;}


This way you have both the pointer (which you can pass to other functions) and a struct representing the data it points to.

Like I said in the original thread, in c# the pointer and the struct are NOT directly connected in any way. So if you have a function that expects a pointer to data created by a c lib, and pass it a c# struct, it won't be able to do anything with it. The c# struct is simply a representation, or copy, of the data stored at the pointer. However it doesn't point to the actual location in memory used by the c lib.

Hope this makes sense. Interoping code is a royal pain at first, but once you get the fundamentals down it's an extremely powerfull tool.
Quote:Original post by gharen2
Niksan2 is incorrect (sort of). I think he overlooked the internal version of the function that returns a pointer.

You're right, the returned IntPtr contains the pointer to the c struct in memory. Then you convert the data at that location to the c# version. That part should work fine.

But he's also correct, that the first argument has to be passed in as a pointer. This is why it's a good way to store the IntPtr returned by functions before marshaling to a struct.

Might I suggest that SHPReadObject be redefined as follows? I should have described it this way in the original thread, but you were getting inundated with details and didn't want to make things worse. In hind sight that was a mistake. This change should be made to all the functions that return or receive a pointer.

*** Source Snippet Removed ***

This way you have both the pointer (which you can pass to other functions) and a struct representing the data it points to.

Like I said in the original thread, in c# the pointer and the struct are NOT directly connected in any way. So if you have a function that expects a pointer to data created by a c lib, and pass it a c# struct, it won't be able to do anything with it. The c# struct is simply a representation, or copy, of the data stored at the pointer. However it doesn't point to the actual location in memory used by the c lib.

Hope this makes sense. Interoping code is a royal pain at first, but once you get the fundamentals down it's an extremely powerfull tool.



thanks Gharen,
so you are saying for instance -- lets back up to when i actually open a SHP file. this is what i have now. the only question is is how to pass in an out parameter in main? this is my DLL imports now and their function w/main.

[DllImport("shapelib.dll", EntryPoint = "SHPOpen")]private static extern IntPtr internalSHPOpen(string pszLayer, string pszAccess);[DllImport("shapelib.dll", CharSet = CharSet.Ansi, EntryPoint = "SHPReadObject", ExactSpelling=true, CallingConvention=CallingConvention.Cdecl)]private static extern IntPtr internalSHPReadObject(IntPtr hSHP, int ishape);[DllImport("shapelib.dll", EntryPoint = "DBFOpen")]private static extern IntPtr internalDBFOpen(string pszShapeFile, string pszAccess);public static IntPtr SHPOpen(string pszLayer, string pszAccess, out SHPInfo mySHPHandle){ IntPtr ptr = internalSHPOpen(pszLayer, pszAccess); mySHPHandle = (SHPInfo)Marshal.PtrToStructure(ptr, typeof(SHPInfo)); return ptr;}public static IntPtr DBFOpen(string pszShapeFile, string pszAccess, out DBFInfo myDBFHandle){ IntPtr ptr = internalDBFOpen(pszShapeFile, pszAccess); myDBFHandle = (DBFInfo)Marshal.PtrToStructure(ptr, typeof(DBFInfo)); return ptr;}public static IntPtr SHPReadObject(IntPtr mySHPHandlePtr, int ishape, out SHPObject myObjectHandle){   IntPtr ptr = internalSHPReadObject(mySHPHandlePtr, ishape); myObjectHandle = (SHPObject)Marshal.PtrToStructure(ptr, typeof(SHPObject)); return ptr;}


//then in main
static void Main(string[] args)   {     SHPInfo mySHPHandle;     DBFInfo myDBFHandle;     SHPObject myObject;          IntPtr mySHPHandlePtr;     IntPtr myDBFHandlePtr;     IntPtr myObjectPtr;               mySHPHandlePtr = SHPOpen("BG2k_clip.shp", "rb", mySHPHandle); //compiler error here, how to send in an out type?      myDBFHandlePtr = DBFOpen("BG2k_clip.dbf", "rb", myDBFHandle);     myObjectPtr = SHPReadObject(mySHPHandlePtr, 4, myObject);   }


is this what you are referring to? does this look right to what you were trying to describe to me? i am getting a compiler error regarding sending in an out parameter....thanks again gharen....

//compiler errors:
Quote:c:\apps\microsoft visual studio .net 2003\vc#\csharpprojects\shapefilereader\class1.cs(142,28): error CS1502: The best overloaded method match for 'shapeFileReader.Class1.SHPOpen(string, string, out shapeFileReader.SHPInfo)' has some invalid arguments
c:\apps\microsoft visual studio .net 2003\vc#\csharpprojects\shapefilereader\class1.cs(142,59): error CS1503: Argument '3': cannot convert from 'shapeFileReader.SHPInfo' to 'out shapeFileReader.SHPInfo'
c:\apps\microsoft visual studio .net 2003\vc#\csharpprojects\shapefilereader\class1.cs(143,28): error CS1502: The best overloaded method match for 'shapeFileReader.Class1.DBFOpen(string, string, out shapeFileReader.DBFInfo)' has some invalid arguments
c:\apps\microsoft visual studio .net 2003\vc#\csharpprojects\shapefilereader\class1.cs(143,59): error CS1503: Argument '3': cannot convert from 'shapeFileReader.DBFInfo' to 'out shapeFileReader.DBFInfo'
c:\apps\microsoft visual studio .net 2003\vc#\csharpprojects\shapefilereader\class1.cs(144,25): error CS1502: The best overloaded method match for 'shapeFileReader.Class1.SHPReadObject(System.IntPtr, int, out shapeFileReader.SHPObject)' has some invalid arguments
c:\apps\microsoft visual studio .net 2003\vc#\csharpprojects\shapefilereader\class1.cs(144,58): error CS1503: Argument '3': cannot convert from 'shapeFileReader.SHPObject' to 'out shapeFileReader.SHPObject'



EDIT - nevermind i wasnt sending in the parameters as out. DOH! it seems to work now -- for now haha. thanks again gharen for your help.

[Edited by - OpenGL_Guru on June 14, 2007 2:42:16 PM]
heh

This topic is closed to new replies.

Advertisement