Click to See Complete Forum and Search --> : Getting errors from PInvoke
jjones7947
October 20th, 2009, 11:19 AM
Have used PInvoke to "wrap" 3 function from a dll. The dll did not have a manifest and there were a lot of #define for datatypes, so I had to work back through them to find all the basic datatypes. I used these when I put the functions in the snippets window of PInvoke assistant and generated signatures to use in C#.
With a little tweaking, I got it to compile and run. However, the output I'm getting is either not there or not what I expect. e.g have a ushort which should return a value found in the sccerr.h file which are in the range 0-100, I'm getting something over 33000.
Is the a way to get errors or warning from the dll or lib?
Jim
darwen
October 21st, 2009, 03:38 AM
It sounds like your PInvoke signatures are incorrect.
Is the a way to get errors or warning from the dll or lib
It depends on how these warnings/errors are exposed by the dll.
If they're thrown as C++ exceptions no - .NET has no concept of a C++ exception. To process C++ exceptions you can either
(1) Write an 'interface' native dll which makes the calls into your dll with native C++ try..catch blocks for the known exceptions. The C# app would then make calls to this dll, not the final dll. You can then pass the error messages back to C# through PInvoke using whichever method you think is appropriate.
(2) Write the .NET interface to the dll in C++/CLI.
If you post
(1) Your PInvoke method signatures
(2) The C++ method signatures (from the .h file)
then I'll be able to tell you where you're going wrong.
Alternatively try using one of the PInvoke tools which are on the net - have a look at the wikipedia page for PInvoke (http://en.wikipedia.org/wiki/Pinvoke#Tools).
Darwen.
jjones7947
October 21st, 2009, 08:51 AM
Sorry for the delay.
Here's the info
Functions defined in header file:
FI_ENTRYSC VTWORD FI_ENTRYMOD FIIdFileEx(VTDWORD, VTVOID *, VTDWORD, VTWORD *,VTLPTSTR, VTWORD);
FI_ENTRYSC VTDWORD FI_ENTRYMOD FIInit(VTVOID);
FI_ENTRYSC VTDWORD FI_ENTRYMOD FIDeInit(VTVOID);
where FI_ENTRYSC is #define __declspec(dllexport) if WIN32 is defined
FI_ENTRYMOD is #define __cdecl if WIN32 is defined
VTWORD is typedef unsigned short
VTDWORD is typedef unsigned long
VTVOID is typedef void
VTLPTSTR is typedef VTTCHAR *
VTTCHAR is typedef VTCHAR
VTCHAR is typedef char
I plug these into PInvoke Assistant I get:
[System.Runtime.InteropServices.DllImportAttribute("sccfi.dll", EntryPoint = "FIInit")]
public static extern uint FIInit();
[System.Runtime.InteropServices.DllImportAttribute("sccfi.dll", EntryPoint = "FIIdFileEx")]
public static extern ushort FIIdFileEx(uint pathType, System.IntPtr path, uint flag, ref ushort fileType, System.IntPtr typeName, ushort maxChars);
[System.Runtime.InteropServices.DllImportAttribute("sccfi.dll", EntryPoint = "FIDeInit")]
public static extern uint FIDeInit();
the return I get from FIInit is 2304
the return I get from FIIdFileEx is always 65535, if the 5th parameter got loaded with a char value, the return should be 0 which is "OK"
Thanks,
Jim
darwen
October 21st, 2009, 09:49 AM
65535 is -1 when held by a short.
This suggests an error code.
if the 5th parameter got loaded with a char value
The 5th parameter is a char * - what code are you using to produce the IntPtr to pass into this parameter ?
Darwen.
jjones7947
October 21st, 2009, 02:03 PM
Ok the value in the short is good news if -1 is not the default value, at least there is a return.
The 5th parameter is an empty one passed in to be loaded, am setting it up like this:
System.IntPtr typeName; (declaration)
typeName = IntPtr.Zero;
typeName = Marshal.AllocateHGlobal(IntPtr.Size);
The 2d parameter I setup like this:
System.IntPtr path = Marshal.StringToGlobalUni(row.Doc_path);
where row.Doc_path contains a file path as a String.
darwen
October 21st, 2009, 02:33 PM
The 2nd parameter should be :
System.IntPtr path = Marshal.StringToHGlobalAnsi(row.Doc_path);
ToHGlobalUni will create a unicode string - not what the function is expecting.
Also you should allocate the size of memory you're specifying as the last parameter e.g.
ushort typeNameSize = 100;
IntPtr typeName = Marshal.AllocateHGlobal((int)typeNameSize);
FIIdFileEx(....., typeName, typeNameSize);
Don't forget to call Marshal.FreeHGlobal on both these pointers after calling the method otherwise you'll end up with a memory leak.
Darwen.
jjones7947
October 22nd, 2009, 11:30 AM
Made the changes you pointed out. I get the same results, so can only assume I'm still doing something wrong. There are a couple other parameters I'm iffy about. Let me insert the C# class involved.
using System;
using System.Runtime.InteropServices;
using System.Configuration;
namespace FIProgram
{
class FileIDApp
{
ushort err;
uint flag;
uint pathType;
ushort maxChars;
ushort fileType;
System.IntPtr typeName;
public FileIDApp()
{
maxChars = 4096;
typeName = Marshal.AllocHGlobal((int)maxChars);
String sFlag = ConfigurationManager.AppSettings["flag"];
String sPathType = ConfigurationManager.AppSettings["pathType"];
if(flag.Equals("normal")){this.flag = 0;}
else if(flag.Equals("extended")){this.flag = 1;}
if(pathType.Equals("ansi")){this.pathType = 2;}
else if(pathType.Equals("unicode")){this.pathType = 4;}
uint rtn = FIInit();
Console.WriteLine("Return from FIInit " + rtn);
}
[System.Runtime.InteropServices.DllImportAttribute("sccfi.dll", EntryPoint = "FIInit")]
public static extern uint FIInit();
[System.Runtime.InteropServices.DllImportAttribute("sccfi.dll", EntryPoint = "FIIdFileEx")]
public static extern ushort FIIdFileEx(uint pathType, System.IntPtr path, uint flag, ref ushort fileType, System.IntPtr typeName, ushort maxChars);
[System.Runtime.InteropServices.DllImportAttribute("sccfi.dll", EntryPoint = "FIDeInit")]
public static extern uint FIDeInit();
public String processDocuments(System.IntPtr path)
{
Console.WriteLine("Path " + Marshal.PtrToStringAnsi(path));
err = FIIdFileEx(pathType, path, flag, ref fileType, typeName, maxChars);
Console.WriteLine("Error " + err);
if (err == 0)
{
Logger.addToLog("OK");
return (Marshal.PtrToStringAnsi(typeName));
}
else
{
return (setError());
}
}
public String setError()
{
String result = "";
if (err == 14)
{
result = "Unsupported Format";
}
else
{
result = "Error Code " + err;
}
return (result);
}
}
}
Ok, the parameters I mean are flag, pathType and err.
First flag and pathType, values for flag are defined in one of the header files as:
#define FIFLAG_NORMAL 0
#define FIFLAG_EXTENDEDFI 1
#define FIFLAG_OVERRIDEOPTIONS 2
There is a whole header file to define values for pathType, so I'm using only two (IOTYPE_ANSIPATH and ISOTYPE_UNICODEPATH)
Since I know that C# may not know about those #defines, you can see in the constructor I'm pulling in a string from the app.config file and using it to assign the appropriate number to flag or pathType.
I just put a Console.WriteLine after the assignment block for these two and got 0 from both. Got rid of the "this" in the assignments and it made no difference.
Now err, if 65535 is -1 in a short then my "if(err == 0)" is total nonsense. How do I get a value like -1 or zero out of and short?
Thanks much for help
Jim
How do you get the code tag to work on here
Arjay
October 22nd, 2009, 01:54 PM
You seem to be doing some extra conversion code that I don't believe is required.
I'm not sure why you use IntPtr for string parameters.
Try just declaring them as strings and set the appropriate CharSet attribute.
For example, let's look at the PInvoke signature for the FindWindow api:
[DllImport( "user32.dll", SetLastError = true, CharSet = CharSet.Auto )]
public static extern IntPtr FindWindow( string lpClassName, string lpWindowName );
Is there any reason you can't declare the strings in your api similarly?
jjones7947
October 22nd, 2009, 05:44 PM
You should carefully read the first post above and note a few things. First the signatures I'm using were generated by PIinvoke Assistant from the signatures declared in the header file. They are not the product of some random determination on my part. the use of the pointers is required. In the case of the path parameter the C++ function is looking for an address to a block of memory which contains the path data. It is not looking for a CRL string for which the address is hidden and therefore not accessible to C++. The second pointer typeName the C++ function is looking for an address to an empty block of memory into which it can write part of it's response.
The marshalling of parameters is not a trivial matter.
Jim
Arjay
October 22nd, 2009, 07:41 PM
You should carefully read the first post above and note a few things. First the signatures I'm using were generated by PIinvoke Assistant from the signatures declared in the header file. They are not the product of some random determination on my part. the use of the pointers is required. In the case of the path parameter the C++ function is looking for an address to a block of memory which contains the path data. It is not looking for a CRL string for which the address is hidden and therefore not accessible to C++. The second pointer typeName the C++ function is looking for an address to an empty block of memory into which it can write part of it's response.
The marshalling of parameters is not a trivial matter.
JimI did carefully read your first post - you made no mention of generating the signatures using "PInvoke Assistant". "PInvoke", was mentioned, but that was it. If you are passing in strings like it looks like param 5 is, then why not prototype at least this param as a string?
At any rate, keep in mind that my comments are an attempt at helping you solve your problem.
darwen
October 23rd, 2009, 05:05 AM
Arjay is right, you really should be using the standard marshalling for strings in the PInvoke. However, let's continue in the vein that we've been running in.
PInvoke interop assistant takes the only option it has based on the information - a const char * parameter could potentially be any of the following depending on context : byte[], string, StringBuilder, IntPtr.
Only IntPtr satisfies all criteria.
Now to deal with the problem :
Can I suggest you just stick with ansi for the file path type - otherwise you'll have to change your marshalling code from StringToHGlobalAnsi to StringToHGlobalUnicode depending on what you've passed in.
You should be specifying the path type as ANSI if you're using StringToHGlobalAnsi for instance.
I think you're problems are starting to lie with the parameters you're passing into the function rather than the marshalling now - you'll have to check the documentation of the function to make sure you're passing the correct values.
Unfortunately what you should be passing in isn't something I can help with - it'll depend on what the function is expecting which you can only get from the documentation.
Darwen.
P.S. Arjay, he did say that he used Pinvoke interop assistant :
I plug these into PInvoke Assistant I get:
Mutant_Fruit
October 23rd, 2009, 05:42 AM
In the case of the path parameter the C++ function is looking for an address to a block of memory which contains the path data.
Which means you can marshal the path as a string in C#. It will automagically be converted to a standard char *. It will convert using the default encoding of the platform unless you specifically request otherwise in the P/Invoke declaration.
The second pointer typeName the C++ function is looking for an address to an empty block of memory into which it can write part of it's response.
StringBuilder can be used for this. Preallocate the required space in your stringbuilder and change the P/Invoke signature to take a StringBuilder instead of an IntPtr.
http://mono-project.com/Interop_with_Native_Libraries#Passing_Caller-Modifiable_Strings
That page explains all you'd ever want to know about the magic that goes on behind the scenes when interop'ing with native libraries using strings.
jjones7947
October 23rd, 2009, 06:29 AM
Well, there may be various ways of marshalling non-primitives across the language barrier, but this one does work. It turns out that the assignment routines for "flag" and "pathType" were not done correctly. I pulled in the strings from the app.cfg file but did not use them in the conditional statements. Instead I used the uint variables, which made the conditions all "false" so nothing got assigned. Fixing this put values in those parameters and now it works fine.
Thanks for the help. This was so informative, I think I'll try it on another dll I have.
Jim
Mutant_Fruit
October 23rd, 2009, 07:05 AM
It's better to use string/stringbuilder when possible because then you won't leak memory. Doing it manually has no benefits other than increasing the possibility of a memory leak, one the GC can't help with.
Glad it's working now anyway.
jjones7947
October 23rd, 2009, 07:47 AM
Can you give an example of how I can do either? Can I just pass in the string I already have?
Course I'd need to change the function signature?
Jim
codeguru.com
Copyright Internet.com Inc., All Rights Reserved.