views:

646

answers:

3

I am referencing a DLL in my C# project as follows:

[DllImport("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
           CharSet = CharSet.Ansi)]

        public static extern void FeeCalculation(string cin, string cout, string flimit,
            string frate, string fwindow, string fincrement, string fbird, 
            string fparameter, string fvalidation, string fcoupon);

The FeeCalculation function is exported as follows in the DLL:

extern "C" __declspec(dllexport) void __stdcall FeeCalculation(char *cin, 
char *cout, char *flimit, char *frate,
char *fwindow, char *fincrement, char *fbird,
char *fparameter, char *fvalidation, char *fcoupon);

The DLL function returns a reference to it's internal structures in the form of char * so if you were to reference this DLL in C++, you would do the following to do the calculation and get the returned structures:

FeeCalculation(buff, (char *)&fans, (char *)fl, (char *)ft, (char *)fw, (char *)fi, (char *)fe, (char *)&fm, (char *)val, (char *)cpn);

Now, how do I retrieve those values that are returned by reference using C#? Meaning, how do I do the same thing in C# to get the returned structures to get my returned calculation? I know I need to create an unsafe method, but I am unclear on how to deal with the memory addresses in C# like you would in C++.

Edit: Below states to use IntPtr but how do you place into identical structure so the fields of the structure can be referenced?

Edit: Here is the returned structure that I am interested in (cout):

struct feeAnswer {


    unsigned int    fee;

    unsigned int    tax1;

    unsigned int    tax2;

    unsigned int    tax3;

    unsigned int    tax4;

    unsigned int    surcharge1;

    unsigned int    surcharge2;

    unsigned int    validationFee;

    unsigned int    couponFee1;

    unsigned int    couponFee2;

    unsigned int    couponFee3;

    unsigned int    couponFee4;

    unsigned short int dstay;       //Day Stay

    unsigned short int mstay;       //Minute Stay

};

Here is the (cin) that I would pass along with other structures (they are zero byte at the moment, I want to get this to work first then I will implement the rest):

struct feeRequest {

    unsigned char   day;

    unsigned char   month;

    unsigned int    year;   //2000 ~ 2099



    unsigned char   hour;

    unsigned char   minute;

    unsigned char   rate;

    unsigned char   validation;



    unsigned char   coupon1;

    unsigned char   coupon2;

    unsigned char   coupon3;

    unsigned char   coupon4;

};
+3  A: 

The char* parameters in this case are not strings but pointers to chunks of raw bytes representing the data. You should marshal your parameters as instances of the IntPtr type, in conjunction with Marshal.AllocHGlobal to create a chunk of memory and then Marshal.PtrToStructure to convert that block of memory into a usable .NET type.

As an example:

[StructLayout(LayoutKind.Sequential)]
struct MyUnmanagedType
{
    public int Foo;
    public char C;
}

IntPtr memory = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyUnmanagedType)));

try
{
    FeeCalculation(memory);

    MyUnmanagedType result = (MyUnmanagedType)Marshal.PtrToStructure(
        memory, typeof(MyUnmanagedType));
}
finally
{
    Marshal.FreeHGlobal(memory);
}
MikeP
Can you call the function as FeeCalculation(memory) even though it expects many more variables?
0A0D
It's just a simplification for the sake of example. You'll need to re-declare each unmanaged struct in your C# code, and then marshal each one individually.
MikeP
Ah ok, so if I want cout and fm result, I just pass that to the PtrToStructure and I'll be good to go.. essentially two statements?
0A0D
John's method is easier now that we have explicit structures to work with. My method is more robust in the general case, but for simple structures John's method is cleaner and easier.
MikeP
+1  A: 

To answer your Edit, you need to create a struct, and then use the StructLayoutAttribute on the fields in order to make the byte order and padding the same as the original dll did.

Nick
+2  A: 

Edit: now that we have structures to work with, a better solution is possible. Just declare structs in C# that match your C++ structs, and use them in the extern declaration

[StructLayout(LayoutKind.Sequential)]
public struct feeAnswer {
   public uint    fee;
   public uint    tax1;
   public uint    tax2;
   public uint    tax3;
   public uint    tax4;
   public uint    surcharge1;
   public uint    surcharge2;
   public uint    validationFee;
   public uint    couponFee1;
   public uint    couponFee2;
   public uint    couponFee3;
   public uint    couponFee4;
   public ushort  dstay;       //Day Stay
   public ushort  mstay;       //Minute Stay
   };

  [StructLayout(LayoutKind.Sequential, Pack=1)]
  public struct feeRequest {
   public byte   day;
   public byte   month;
   public uint   year;   //2000 ~ 2099
   public byte   hour;
   public byte   minute;
   public byte   rate;
   public byte   validation;
   public byte   coupon1;
   public byte   coupon2;
   public byte   coupon3;
   public byte   coupon4;
   };

  [DllImport ("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
             CharSet = CharSet.Ansi)]
  public static extern void FeeCalculation (
          feeRequest cin,
          out feeAnswer cout,
           ...



        ....

original answer (before we had structs) below


It appears to me that these are not references to internal strings, but rather pointers to string buffers that will be filled in by the call. If you were returning string pointers, then these would be declared char** rather than char*.

So I think these are just standard out parameters. There's just a lot of them. So your C# interop would look like this

[DllImport("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
           CharSet = CharSet.Ansi)]
public static extern void FeeCalculation(string cin, 
        [MarshalAs(UnmanagedType.LPStr, SizeConst=100)]
        out string cout, 
        [MarshalAs(UnmanagedType.LPStr, SizeConst=100)]
        out string flimit,

or this if your "strings" aren't really strings

[DllImport("FeeCalculation.dll", CallingConvention = CallingConvention.StdCall,
           CharSet = CharSet.Ansi)]
public static extern void FeeCalculation(string cin, 
        [MarshalAs(UnmanagedType.LPArray, SizeConst=100)]
        out byte[] cout, 
        [MarshalAs(UnmanagedType.LPArray, SizeConst=100)]
        out byte[] flimit,
        ....
John Knoeller
He said right in the problem statement that they aren't really strings.
MikeP
@MikeP: He also said they were references, which they clearly aren't.
John Knoeller
So excuse my ignorance but if they are out as byte arrays, then I can then convert this to my defined structure and then I would also have to pass as a byte array back to get the response I want?
0A0D
My impression was that he was referring to the concept of pass-by-reference, as opposed to pass-by-value. Not C++ or C# references in particular. Meaning the parameters are used to reference data, attaching no API-specific meaning to that term.
MikeP
@MikeP: You got it.. that is exactly what I am talking about hence out parameter
0A0D
Passing as a byte array isn't a great idea since you're hardcoding the size to pass, and you'll have to perform not only a managed allocation initially that will add to the GC, but also have to marshal the data back after the call completes anyway.
MikeP
@MikeP: passing by byte array is what the function as originally declared did, but now that we have structs to work with, I have revised my answer.
John Knoeller
No, the original passed byte pointers, which isn't the same thing since the byte array needs an explicit size.Anyway, yes, this is the better answer now. Letting the C# marshaller do as much of the work as possible is the best idea.
MikeP
I get inconsistent accessibility.. parameter type 'out feeAnswer' is less accessible than method FeeCalculation(....)
0A0D
@Changeling: sorry, you have declare the fields as public <sigh>
John Knoeller
@John: oops ! forgot about that.. it's so easy since you would think public/private would be default!
0A0D