tags:

views:

306

answers:

1

Hi.

I hope someone can assist me with the problem I'm currently experiencing. We have a lot of Delphi legacy code, and need to convert some of our Delphi applications to C#.

The legacy code I'm currently struggling with is that of calling a function from a 3rd party application's non-COM DLL.

Here is the C-style header and struct used for the specific function:

/*** C Function AwdApiLookup ***/  
extern BOOL APIENTRY AwdApiLookup( HWND hwndNotify, ULONG ulMsg,  
                                   BOOL fContainer, CHAR cObjectType,  
                                   SEARCH_CRITERIA* searchCriteria,  
                                   USHORT usCount, USHORT usSearchType,  
                                   VOID pReserved );  

/*** C Struct SEARCH_CRITERIA ***/  
typedef struct _search_criteria  
{  
    UCHAR dataname[4];  
    UCHAR wildcard;  
    UCHAR comparator[2];  
    UCHAR datavalue[75];  
} SEARCH_CRITERIA;

In our Delphi code, we have converted the above function and structure as:

(*** Delphi implementation of C Function AwdApiLookup ***)
function AwdApiLookup(hwndNotify: HWND; ulMsg: ULONG; fContainer: Boolean;
    cObjectType: Char; pSearchCriteria: Pointer; usCount: USHORT;
    usSearchType: USHORT; pReserved: Pointer): Boolean; stdcall;
    external 'AWDAPI.dll';

(*** Delphi implementation of C Struct SEARCH_CRITERIA ***)
TSearch_Criteria = record
    dataname:   array [0..3] of char;
    wildcard:   char;
    comparator: array [0..1] of char;
    datavalue:  array [0..74] of char;
end;
PSearch_Criteria = ^TSearch_Criteria;

and the way we call the above mentioned code in Delphi is:

AwdApiLookup(0, 0, true, searchType, @criteriaList_[0], 
             criteriaCount, AWD_USE_SQL, nil);

where criteriaList is defined as

criteriaList_: array of TSearch_Criteria;

After all that is said and done we can now look at the C# code, which I cannot get to work. I'm sure I'm doing something wrong here, or my C header is not translated correctly. My project does compile correctly, but when the function is called, I get a "FALSE" value back, which indicates that the function did not execute correctly in the DLL.

My C# code thus far:

/*** C# implementation of C Function AwdApiLookup ***/
DllImport("awdapi.dll", CharSet = CharSet.Auto)]
public static extern bool AwdApiLookup(IntPtr handle, ulong ulMsg, 
                                       bool fContainer, char cObjectType, 
                                       ref SearchCriteria pSearchCriteria, 
                                       ushort usCount, ushort usSearchType, 
                                       Pointer pReserverd);

/*** C# implementation of C Struct SEARCH_CRITERIA ***/
[StructLayout(LayoutKind.Sequential)]
public struct SearchCriteria
{
    private readonly byte[] m_DataName;
    private readonly byte[] m_Wildcard;
    private readonly byte[] m_Comparator;
    private readonly byte[] m_DataValue;

    public SearchCriteria(string dataName, string comparator, string dataValue)
    {
        m_DataName = Encoding.Unicode.GetBytes(
                                dataName.PadRight(4, ' ').Substring(0, 4));
        m_Wildcard = Encoding.Unicode.GetBytes("0");
        m_Comparator = Encoding.Unicode.GetBytes(
                                    comparator.PadRight(2, ' ').Substring(0, 2));
        m_DataValue = Encoding.Unicode.GetBytes(
                                    dataValue.PadRight(75, ' ').Substring(0, 75));
    }

    public byte[] dataname { get { return m_DataName; } }
    public byte[] wildcard { get { return m_Wildcard; } }
    public byte[] comparator { get { return m_Comparator; } }
    public byte[] datavalue { get { return m_DataValue; } }
}

My C# call to the C# function looks like this

var callResult = UnsafeAwdApi.CallAwdApiLookup(IntPtr.Zero, 0, true, 'W', 
                                               ref searchCriteria[0], criteriaCount,
                                               66, null);

where searchCriteria and criteriaCount is defined as

List<SearchCriteria> criteriaList = new List<SearchCriteria>();
var searchCriteria = criteriaList.ToArray();
var criteriaCount = (ushort)searchCriteria.Length;

and adding data to searchCriteria:

public void AddSearchCriteria(string dataName, string comparator, string dataValue)
{
    var criteria = new SearchCriteria();
    criteria.DataName = dataName;
    criteria.Wildcard = "0";
    criteria.Comparator = comparator;
    criteria.DataValue = dataValue;
    criteriaList.Add(criteria);
}

Like I said, my code compiles correctly, but when the function executes, it returns "FALSE", which should not be the case as the Delphi function does return data with the exact same input.

I know I'm definitely doing something wrong here, and I've tried a couple of things, but nothing seems to be working.

Any assistance or nudge in the right direction would be greatly appreciated.

Thanks, Riaan

+2  A: 

Several things here.

First of all C++ ULONG is a 32-bit integer, and becomes uint in C# - ulong is 64-bit.

For the struct, you don't need to mess with byte arrays. Use strings, and ByValTStr. Also, it's not really worth bothering with readonly and properties for interop structs. Yes, mutable value types are generally bad in a pure .NET API, but in this case it's the existing API, there's no point in masking it. So:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SearchCriteria
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4]
    public string m_DataName;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1]
    public string m_Wildcard;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2]
    public string m_Comparator;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 75]
    public string m_DataValue;
}

If you really want to do all the string conversions yourself, it may be easier to just use unsafe and fixed-size arrays:

public unsafe struct SearchCriteria
{
    public fixed byte m_DataName[4];
    public byte m_Wildcard;
    public fixed byte m_Comparator[2];
    public fixed byte m_DataValue[75];
}

[EDIT] Two more things.

CHAR cObjectType should become byte cObjectType, and not char cObjectType that you currently use.

Also, yes, there is a problem with array marshaling in your example. Since your P/Invoke declaration is ref SearchCriteria pSearchCriteria - i.e. a single value passed by reference - that's precisely what P/Invoke mashaler will do. Keep in mind that, unless your struct only has fields of unmanaged types in it (the one with fixed arrays above is that, the one with string is not), the marshaler will have to copy the structs. It won't just pass address to the first element of the array directly. And in this case, since you didn't tell it it's an array there, it will only copy the single element you reference.

So, if you use the version of the struct with string, fields, you need to change the P/Invoke declaration. If you only need to pass SEARCH_CRITERIA objects into the function, but won't need to read data from them after it returns, just use an array:

public static extern bool AwdApiLookup(IntPtr handle, uint ulMsg, 
                                       bool fContainer, byte cObjectType, 
                                       SearchCriteria[] pSearchCriteria, 
                                       ushort usCount, ushort usSearchType, 
                                       Pointer pReserverd);

And call it like this:

var callResult = UnsafeAwdApi.CallAwdApiLookup(
    IntPtr.Zero, 0, true, (byte)'W', 
    searchCriteria, criteriaCount,
    66, null);

If function writes data into that array, and you need to read it, use [In, Out]:

[In, Out] SearchCriteria[] pSearchCriteria,

If you use the version with fixed byte[] arrays, you can also change the P/Invoke declaration to read SearchCriteria* pSearchCriteria, and then use:

fixed (SearchCriteria* p = &searchCriteria[0])
{
    AwdApiLookup(..., p, ...);
}

This will require unsafe as well, though.

Pavel Minaev
Hi Pavel,Thank you for the revised struct. I've had it that way as well, but changed it back to your recommendation. thank you as well for spotting the ULONG error. I've updated my code accordingly as well, but unfortunately I'm still getting a "FALSE" back, which means the function did not execute correclty.Just another question, is it maybe that the unmanaged DLL cannot access the memory of the managed array's 1st element?
Riaan
Yes, there is a problem with array there that I didn't spot. Updated answer.
Pavel Minaev
Hi Pavel,I've added the way searchCriteria is currenlty being added. With the struct changing as per your recommendation my code is currently not compiling. What would be the correct way in updating the AddSearchCriteria function to accept the new struct datamembers. I've tried Convert.ToByte, but it also gives compile errors. Thanks for all the assist so far.
Riaan
I assume you use the version with `ByValTStr`? If so, then your `AddSearchCriteria` should be fine; if it doesn't, please provide the exact error message you're getting. I've also edited the answer and added a sample function call - you need to cast `'W'` to byte, and drop `ref ...[0]` for `searchCriteria`. Also, what is type `Pointer`? I've left it as is, but it's not a stock C# type.
Pavel Minaev
Hi Pavel,Thank you for your responses thus far. I've updated my code accordingly, but thus far with no luck. It executes now, but the external fnction does not return any values. Type Pointer is defined as VOID in the C++ header, but in Delphi it is defined as a Pointer (pointe reference) but a null value is being passed through to the function in the Delhpi code. Will keep you posted though. (I'm not at the office for the rest of the week, but I'll see what I can come up with in the meantime)
Riaan
I mean, what did you defined `Pointer` as in your C# translation? I see that you pass `null` to it - if that is actual code, then `Pointer` is a reference type in your C# code, which is likely not what you want. It should be `IntPtr`.
Pavel Minaev
Also, what do you mean by "function does not return any values"? As defined, your function only returns a `bool`, and it cannot _not_ return one. Do you mean that it returns a wrong `bool`? Or is it actually supposed to use the `SearchCriteria[]` array to return data? If so, try applying `[In, Out]` attribute to the array argument of the function.
Pavel Minaev