views:

1166

answers:

6

I am attempting to call into a native .dll from c# using p/invoke. I'm able to make the call (no crash, the function returns a value), but the return code indicates "A pointer parameter does not point to accessible memory." I've resorted to trial and error in order to solve this, but I haven't made any progress so far.

Here's the signature of the native function I'm calling:

LONG extern WINAPI MyFunction ( LPSTR lpszLogicalName, //input
                                   HANDLE hApp,           //input  
                                   LPSTR lpszAppID,       //input 
                                   DWORD dwTraceLevel,    //input
                                   DWORD dwTimeOut,       //input
                                   DWORD dwSrvcVersionsRequired, //input
                                   LPWFSVERSION lpSrvcVersion, //WFSVERSION*, output
                                   LPWFSVERSION lpSPIVersion,  //WFSVERSION*, output
                                   LPHSERVICE lphService       //unsigned short*, output
                                 );

Here's the imported signature in C#:

 [DllImport("my.dll")]
 public static extern int MyFunction( [MarshalAs(UnmanagedType.LPStr)] 
                                      string logicalName, 
                                      IntPtr hApp, 
                                      [MarshalAs(UnmanagedType.LPStr)] 
                                      string appID, 
                                      int traceLevel, 
                                      int timeout, 
                                      int srvcVersionsRequired, 
                                      [Out] WFSVersion srvcVersion, 
                                      [Out] WFSVersion spiVersion,
                                      [Out] UInt16 hService
                                    );

Here's the C definition of WFSVERSION:

typedef struct _wfsversion
{
    WORD            wVersion;
    WORD            wLowVersion;
    WORD            wHighVersion;
    CHAR            szDescription[257];
    CHAR            szSystemStatus[257];
} WFSVERSION, * LPWFSVERSION;

Here's the C# definition of WFSVersion:

[StructLayout(LayoutKind.Sequential)]
public class WFSVersion
{
    public Int16 wVersion;
    public Int16 wLowVersion;
    public Int16 wHighVersion;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 257)]
    public char[] szDescription;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 257)]
    public char[] szSystemStatus;
}

Here's the call to MyFunction from C#:

WFSVersion srvcVersionInfo = new WFSVersion();
WFSVersion spiVersionInfo = new WFSVersion();


 UInt16 hService = 0;
 IntPtr hApp = IntPtr.Zero;
 string logicalServiceName = tbServiceName.Text;
 int openResult = MyFunction(logicalServiceName, hApp, null, 0,  
                              XFSConstants.WFS_INDEFINITE_WAIT,
                              0x00000004, srvcVersionInfo, spiVersionInfo, 
                              hService);

As I said, this call returns, but the return value is an error code indicating "A pointer parameter does not point to accessible memory." I must be doing something wrong with parameters 1,3,7,8, or 9. However, I've made successful calls to other functions in this .dll which required WFSVERSION* as parameters, so I don't think parameters 7 or 8 are the issue here.

I would appreciate any thoughts you might have about the cause of this issue, or any constructive criticisms of my code. This is my first experience with P/Invoke, so I'm not sure where to begin. Is there any way to narrow down the issue, or is trial an error my only option?

A: 

I'm not sure exactly what the issue with this one is but hopefully this will be a starting point.

Try looking into the options on the DllImport attribute, this could be to do with the marshalling of the strings.

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]

The CharSet option is the one I think you may need.

Peter
I tried CharSet.Auto, CharSet.Ansi, and Charset.Unicode, but the problem remains
Odrade
Is there any further documentation on the PInvoke website for the dll you're using or is it a custom dll?http://www.pinvoke.net/
Peter
A: 

I have found AppVerifier useful for tracking down P/Invoke marshal issues before. It's free and from Microsoft.

Mo Flanagan
A: 

My guess would be that one of your pointers (lpSrvcVersion, lpSPIVersion or lphService) isn't accessible from your .NET application. Can you try modifying the DLL (if it's yours) and see if you can get it working without the pointers? (I know you'll have to add them back later, but at least you can narrow down the location of the problem.)

Jon Tackabury
Unfortunately, I don't have the ability to modify the dll.
Odrade
A: 

Are you sure that it is not hApp? That looks like an input parameter, the handle to the requesting application. Quick google...yes, there is a function to create an app handle, and a default parameter you can use.

R Ubben
+1  A: 

Some ideas:

  • The C# WFSVersion class should probably be a struct. I don't know if the P/Invoke marshaller cares, but I've always seen structs used.

  • Character size might be an issue.

    C's CHAR is 8 bits wide (ANSI), and .Net's System.Char is 16 bits wide (Unicode). To give the marshaller as much info as possible so it does the correct conversion, try adding "CharSet = CharSet.Ansi" to the DllImport and StructLayout attributes, and changing the string declarations in WFSVersion:

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string szDescription;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string szSystemStatus;
    
  • Another issue could be data alignment in the structs. If no alignment was specified when the C struct was compiled, the data elements in the struct were probably aligned on a one or two byte boundary. Try using Pack in WFSVersion's StructLayout attribute:

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    // Try pack values of 2, 4 and 8 if 1 doesn't work.
    

And some questions:

  • Was MyFunction intended to be called from non-C code? The original author may have written code that assumes the data passed in is allocated with the C runtime memory manager.

  • Does code in the C DLL use the pointers passed to it for later processing after MyFunction has returned? If so - and assuming it's possible/wise/sane to go forward with such a situation - it may be necessary to "pin" the structs passed to MyFunction using the fixed keyword. Plus there are probably security issues to deal with.

Chris R. Timmons
Thanks for the info regarding the handling of C strings. Although this was not the source of the problem I reported, I did have some issues with this that I hadn't noticed yet.
Odrade
+2  A: 

You have two obvious errors here. In your struct definition you should be using byte[] instead of char[] for szDescription and szSystemStatus.

Also the last parameter in your pInvoke call is not a pointer. When you make your call to MyFunction hService is zero and therefore an invalid pointer as far as the function is concerned. [Out] is a Marshaling directive telling the runtime when and where to copy data not an indicator that the parameter is a pointer. What you need is to change [Out] to out or ref this tells the runtime that hService is a pointer:

[DllImport("my.dll")]
public static extern int MyFunction( [MarshalAs(UnmanagedType.LPStr)] 
                                  string logicalName, 
                                  IntPtr hApp, 
                                  [MarshalAs(UnmanagedType.LPStr)] 
                                  string appID, 
                                  int traceLevel, 
                                  int timeout, 
                                  int srvcVersionsRequired, 
                                  [Out] WFSVersion srvcVersion, 
                                  [Out] WFSVersion spiVersion,
                                  out UInt16 hService);
Stephen Martin
Thank you, that was the problem. Having learned that, though, I'm wondering if the [Out] WFSVersion parameters are also marshaled incorrectly.
Odrade
I get it now: WFSVersion is a reference type, not a value type. The [Out] attribute isn't even necessary for those params; it just increases the efficiency of the calls a bit.
Odrade