NullReferenceException when running DLLImported function

I have a vb.net chunk of code that works fine when I call the wrapper function that looks like this:

<DllImport(".\analysis.dll", EntryPoint:="levels_analysis", CharSet:=CharSet.Ansi)> _
    Private Shared Function levels_analysis( _
         <MarshalAs(UnmanagedType.VBByRefStr)> ByRef in_string As String, _
        <MarshalAs(UnmanagedType.I1)> ByVal in_string_indicator As Char, _
        <MarshalAs(UnmanagedType.I4)> ByVal in_string_format As Integer, _
        <MarshalAs(UnmanagedType.VBByRefStr)> ByRef transaction_id As String, _
        <MarshalAs(UnmanagedType.VBByRefStr)> ByRef criteria_parm As String, _
        <MarshalAs(UnmanagedType.LPStr)> ByVal in_parm As String, _
        <MarshalAs(UnmanagedType.I4)> ByVal qc_flag As Integer, _
        <MarshalAs(UnmanagedType.I4)> ByVal loan_flag As Integer, _
        <MarshalAs(UnmanagedType.I4)> ByVal pool_flag As Integer, _
        <MarshalAs(UnmanagedType.LPStr)> ByVal function_type As String, _
        <MarshalAs(UnmanagedType.VBByRefStr)> ByRef client_ini_file_path As String, _
        <MarshalAs(UnmanagedType.I4)> ByRef loan_assumption_flag As Integer, _
        <MarshalAs(UnmanagedType.R8)> ByRef foreclosure_frequency As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef loss_severity As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef loss_coverage As Double, _
        <MarshalAs(UnmanagedType.VBByRefStr)> ByRef risk_grade As String, _
        <MarshalAs(UnmanagedType.R8)> ByRef expected_loss_lower As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef expected_loss_upper As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef spei_adjustment As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef adjusted_ltv As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef pool_size_factor As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef ss_factors As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef ff_pool As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef ls_pool As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef lc_pool As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef exp_loss_low_pool As Double, _
        <MarshalAs(UnmanagedType.R8)> ByRef exp_loss_upp_pool As Double, _
        <MarshalAs(UnmanagedType.I4)> ByRef pool_type As Integer, _
        <MarshalAs(UnmanagedType.R8)> ByRef ss_pct As Double, _
        <MarshalAs(UnmanagedType.I4)> ByRef qc_err As Integer) As Integer
    End Function

 


I'm trying to port it to c# and have the following so far:

[DllImport(@".\analysis.dll", EntryPoint="levels_analysis", CharSet=CharSet.Ansi)]
  static extern int levels_analysis( [MarshalAs(UnmanagedType.LPStr)] string in_string,
   [MarshalAs(UnmanagedType.I1)] char in_string_indicator,
   [MarshalAs(UnmanagedType.I4)] int in_string_format,
   [MarshalAs(UnmanagedType.LPStr)] string transaction_id,
   [MarshalAs(UnmanagedType.LPStr)] string criteria_parm,
   [MarshalAs(UnmanagedType.LPStr)] string in_parm,
   [MarshalAs(UnmanagedType.I4)] int qc_flag,
   [MarshalAs(UnmanagedType.I4)] int loan_flag,
   [MarshalAs(UnmanagedType.I4)] int pool_flag,
   [MarshalAs(UnmanagedType.LPStr)] string function_type,
   [MarshalAs(UnmanagedType.LPStr)] string client_ini_file_path,
   [MarshalAs(UnmanagedType.I4)] ref int no_assumption_flag,
   [MarshalAs(UnmanagedType.R8)] ref double ff_loan,
   [MarshalAs(UnmanagedType.R8)] ref double ls_loan,
   [MarshalAs(UnmanagedType.R8)] ref double lc_loan,
   [MarshalAs(UnmanagedType.LPStr)] ref string risk_grade,
   [MarshalAs(UnmanagedType.R8)] ref double expected_loss_lower_loan,
   [MarshalAs(UnmanagedType.R8)] ref double expected_loss_upper_loan,
   [MarshalAs(UnmanagedType.R8)] ref double sp_index_adjustment,
   [MarshalAs(UnmanagedType.R8)] ref double adjusted_ltv,
   [MarshalAs(UnmanagedType.R8)] ref double pool_size_factor,
   [MarshalAs(UnmanagedType.R8)] ref double ss_factors,
   [MarshalAs(UnmanagedType.R8)] ref double ff_pool,
   [MarshalAs(UnmanagedType.R8)] ref double ls_pool,
   [MarshalAs(UnmanagedType.R8)] ref double lc_pool,
   [MarshalAs(UnmanagedType.R8)] ref double expected_loss_lower_pool,
   [MarshalAs(UnmanagedType.R8)] ref double expected_loss_upper_pool,
   [MarshalAs(UnmanagedType.I4)] ref int pool_type,
   [MarshalAs(UnmanagedType.R8)] ref double ss_percentage,
   [MarshalAs(UnmanagedType.I4)] ref int qc_error_codes);

 


I call this function inside a managed method.  When I call this function with the exact same data that I used with the vb.net function, I get a NullReferenceException.  However, this exception doesn't get thrown until the very end of the managed method which called the unmanaged function.  The unmanaged function is returning an int, but it's definitely not doing what it should be doing inside that unmanaged function (it outputs text files).  Again, it works fine in VB.net.  I'm new to dllimporting so I don't even know where to start.  Am I using the proper MarshalAs conversions


Answer this question

NullReferenceException when running DLLImported function

  • He3117

    I finally solved the problem today.  For the majority of the ref parameters the silly C++ programmers were really after the starting memory location for an array of that type.  IMO it seems like bad programming practice.  Shouldn't they have been asking for arrays or pointers to arrays   Anyway, once we actually created arrays and then just handed in a reference to the first element in the array it worked.

    One funny thing I noticed was that I literally couldn't catch the exception from the calling method, nor could I catch it in the method that called that method.  I could only catch it from the main application block where the program initially starts.


  • billy_bob123456

    James,

    Thanks for your thorough answer.  Unfortunately, I'm not sitting at my project right now, but I'm fairly certain I've tried it using the ref keyword as well.  I can say for sure the string being passed in definitely isn't null.  In fact, I've made sure through debugging that NONE of the parameters being passed in are null.  The odd behavior is that the "imported" dll function is returning an int as expected.  The exception isn't being thrown until I return from the method that called the imported function.  However, the dll function should be writing to an output file and it's not doing that.  If an imported function blows up, shouldn't the exception be thrown before it returns

  • sellison14

    Keyser, below is how I view the problem and the resolution.

    Problem Issue: You are using Platform Invocation Services to communicate with an unmanaged C-DLL from managed code.

    Findings:

    There appears to be a difference in your two calls:

    [VB .NET]

    <MarshalAs(UnmanagedType.VBByRefStr)> ByRef client_ini_file_path As String,

    [/VB .NET]

    [VC# .NET]

    [MarshalAs(UnmanagedType.LPStr)] string client_ini_file_path,

    [/VC# .NET]

    The VB string is passed as a reference to a string.  The VC# string is passed by value with no allowable expectation of a return value.

    Resolution:

    Pass a reference to a string.  You can accomplish this by using the ref keyword or the out keyword.  The ref keyword usually expects an update to existing inbound string.  The out keyword acts in similar fashion to the ref keyword; however, it is intended that the method called "is going to assign a value to the parameter, as opposed to modifying its existing value".

    Ref: "Fast Track C#" by K. Scott Allen (and others), (c) 2002 Wrox Publishing, Chapter 3, Defining and Calling Methods, Pg. 70

    Comments:

    You are attempting to assign a string to an inbound-only string that was passed into the function but cannot be returned as an updated string.  Once the string has been assigned in the function that was called, the new temporary string is stored on the heap.  I suppose this string is expecting to be resolved by the compiler when the called function goes out of scope and clean up it stack.  When the C-function called goes out of scope, this new temporary string in memory triggers an exception.  My assessment is that this is somehow like getting dressed to out on the town but having no where to go.

    To the point, it is most likely the case that the in-bound string is null when the function is called, and you are illegally attempting to update that string from a null to something else triggering the exception.

    Sincerely,

    James Sigler
    Dallas, TX
    jmsigler2@hotmail.com

    P.S. Last year, I was a member of the Distributed Services Group at Microsoft on the OLE/COM/DCOM/.NET Remoting Team including Platform Invocation Services.


  • Hakkk

    One other note:

    You have restricted the character set to marshalling ANSI character strings.  So, you do not need TCHAR types on the C-DLL side as I used.  You can use LPCSTR instead of LPCTSTR and LPSTR instead of LPTSTR.

    James

  • beachmalle

    I am interested in replicating your error.  Do you expect only the last string below, "risk_grade", to return string   I am assuming all of the other strings are for input only and are not updated.  I am also assuming the last string is sent as empty but not null.  If the last string is passed as null, I believe the ref keyword should be replaced with the out keyword.

       [MarshalAs(UnmanagedType.LPStr)] string in_string,
       [MarshalAs(UnmanagedType.LPStr)] string transaction_id,
       [MarshalAs(UnmanagedType.LPStr)] string criteria_parm,
       [MarshalAs(UnmanagedType.LPStr)] string in_parm,
       [MarshalAs(UnmanagedType.LPStr)] string function_type,
       [MarshalAs(UnmanagedType.LPStr)] string client_ini_file_path,
       [MarshalAs(UnmanagedType.LPStr)] ref string risk_grade,

    I may be the case that I need to know what you are doing file wise on the unmanaged side.

    James Sigler
    Dallas, TX
    jmsigler2@hotmail.com


  • Bill Zack

    Okay,

    your comments helped me to narrow my focus to the passing of strings.

    I looked at "Fast Track C#" by Wrox Publishing, Chapter 11 - COM and COM+ Interoperability, "Platform Invocation Services", Pages 347-352.

    This section actually talks about the passing of strings which, if am correct, is the heart of our issue.

    Now, there are two cases:

    1.) passing an input string to the unmanaged DLL
    2.) passing an input/output string to the unmanged DLL

    In the first case, you can use the C# System.String (or string), a class implementing an immutable string.  The C-DLL stack argument can be typed as an LPCTSTR.  If you are simple passing strings by value, this is the way to go.

    In the second case, since a string class object is immutable, we need to use a System.Text.StringBuilder class, from the System.Text namespace of course.  This object allows you to create a variable or parameter where the C-DLL Win32 function expects a writeable fixed-length string buffer.  The type for this can be defined as LPTSTR.

    The reference I quoted above also discusses the passing of structures.

    So, if you want to pass a string back to managed code from unmanaged code you might do something like this:

    // ManagedCode.cs

    using System;
    using System.Runtime.InteropServices;

    namespace ManagedCode
    {
     /// <summary>
     /// Summary description for Class1.
     /// </summary>
     class ManagedClass
     {
       [DLLImport("analysis.dll")]
      static extern int levels_analysis(
         ...
         string client_ini_file_path,
         ...
         StringBuilder risk_grade,
         uint risk_grade_size, 
         ...);

      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main(string[] args)
      {
         //
         // TODO: Add code to start application here
         //
            StringBuilder risk_grade = new StringBuilder(255);
         int retval = levels_analysis(
            ...
            client_ini_file_path,
            ...
            risk_grade,
            (uint)risk_grade.Capacity, 
            ...);
      }
    }

    ==================================================

    // UnmanagedCode.cpp : Defines the entry point for the C-DLL app.

    #include <winnt.h>

    #ifdef UNMANAGEDCODE_EXPORTS
    #define UNMANAGEDCODE_API __declspec(dllexport)
    #else
    #define UNMANAGEDCODE_API __declspec(dllimport)
    #endif

    UNMANAGEDCODE_API int levels_analysis(
         ...
         LPCTSTR client_ini_file_path,
         ...
         LPTSTR risk_grade,
         DWORD risk_grade_size, 
         ...);

    BOOL APIENTRY DllMain( HANDLE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved )
    {
       switch (ul_reason_for_call)
       {
          case DLL_PROCESS_ATTACH:
          case DLL_THREAD_ATTACH:
          case DLL_THREAD_DETACH:
          case DLL_PROCESS_DETACH:
          break;
       }
       return TRUE;
    }

    UNMANAGEDCODE_API int levels_analysis(
       ...
       LPCTSTR client_ini_file_path,
       ...
       LPTSTR risk_grade,
       DWORD risk_grade_size, 
       ...)
    {
       ...
       return 1;
    }

    This approach allows you to create a buffer in which to fill an output string to be return to managed code.

    This solution works as long as the buffer is always big enough to return the necessary string.  Now, if you want a more fault tolerant approach, you should pass a zero as the buffer size on the first call and mark the buffer size as a ref parameter.  The first time the C function is called, the buffer size necessary for required memory allocation is returned to the user.  Then, a second call is made to fullfill the request where the buffer has the necessary buffer size to obtain the information needed from the unmanaged C-DLL.  I learned this technique back in my C coding days in writing low-level platform SDK code.  This approach avoids using unnecessarily large memory allocated buffers.

    I believe this gives you a nicer more straight forward solution than the solution you already put together.  Let me know what you think.

    Sincerely,

    James Sigler
    Dallas, TX
    jmsigler2@hotmail.com


  • NullReferenceException when running DLLImported function