views:

82

answers:

2

I have a little piece of a library that I would like to write a .NET wrapper for. At the moment, I'm using P/Invoke, but since I don't have the library's source code or a whole lot of C knowledge, I'm having a difficult time with marshaling. It works so far (sort of), but it feels like a hack.

C Signatures

typedef struct
{
    unsigned short  sAddress[MAX_ADDRESS_CHAR_LENGTH + 1];
    unsigned short  sCallback[MAX_CALLBACK_CHAR_LENGTH + 1];
    unsigned short  sMessage[(MAX_MESSAGE_CHAR_LENGTH + 1) ];
    unsigned short  sSmscAddress[MAX_ADDRESS_CHAR_LENGTH+1];
    unsigned short  sSubject[MAX_SUBJECT_CHAR_LENGTH + 1];
    unsigned char   msgLength;        
    unsigned char   pduType;
    unsigned short  msgRef;
    unsigned char   msgSequence;
    unsigned char   msgTotal;
    EMsgPriority    nPriority;
    struct tm       tTime;
    EncodingType    encoding;
    unsigned char   bReceipt;
    unsigned long   dwDataMask;
    struct tm       tValidity;
    unsigned char   nValidityType;
    unsigned char   bRelativeValidityFlag;
    unsigned char   isDeliveryAck;
} SMS_MSG_DATA;

unsigned short SmsEncodeMessage( SMS_MSG_DATA* sms_msg, unsigned char* msg_buf,
    unsigned short* msg_buf_len );

C# P/Invoke

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SMS_MSG_DATA {
    [MarshalAs(UnmanagedType.ByValTStr,
        SizeConst=SmsEncoding.MAX_ADDRESS_CHAR_LENGTH+1)]
    public string sAddress;

    [MarshalAs(UnmanagedType.ByValTStr,
        SizeConst=SmsEncoding.MAX_CALLBACK_CHAR_LENGTH+1)]
    public string sCallback;

    [MarshalAs(UnmanagedType.ByValTStr,
        SizeConst=SmsEncoding.MAX_MESSAGE_CHAR_LENGTH+1)]
    public string sMessage;

    [MarshalAs(UnmanagedType.ByValTStr,
        SizeConst=SmsEncoding.MAX_ADDRESS_CHAR_LENGTH+1)]
    public string sSmscAddress;

    [MarshalAs(UnmanagedType.ByValTStr,
        SizeConst=SmsEncoding.MAX_SUBJECT_CHAR_LENGTH+1)]
    public string sSubject;

    public byte msgLength;
    public byte pduType;
    public ushort msgRef;
    public byte msgSequence;
    public byte msgTotal;
    public EMsgPriority nPriority;
    public tm tTime;
    public EncodingType encoding;
    public byte bReceipt;
    public long dwDataMask;
    public tm tValidity;
    public byte nValidityType;
    public byte bRelativeValidityFlag;
    public byte isDeliveryAck;
}

[DllImport(Constants.LIB_SMSENCODE)]
public static extern ErrorCode SmsEncodeMessage(ref SMS_MSG_DATA sms_msg,
    byte[] msg_buf, ref short msg_buf_len);

Essentially, what this does is takes the SMS_MSG_DATA struct and outputs it to a binary format in the msg_buf byte array. The initial value of msg_buf_len is the size of the byte array, but when the encoding completes, it is set to the number of bytes actually filled.

How can a C++/CLI wrapper make this process any easier and cleaner?

A: 

C++/CLI can make this easier because you don't have to write a PInvoke definition. Instead you can just use the original native SMS_MSG_DATA structure directly. You are free to write a better looking wrapper structure which is internally converted to SMS_MSG_DATA. This wrapper structure need not have any ugly PInvoke declaration and can be much more in lines with managed guidelines.

JaredPar
In that case, I still have to know exactly which managed types can be converted to the unmanaged types in the `SMS_MSG_DATA` struct, right? The issue I'm running into now is that the struct declaration isn't entirely correct, so the data isn't marshaled correctly, causing the Encode method to choke on some of the fields.
David Brown
@David: the difference is that in C++/CLI you would do the conversion yourself (via casts and/or `Encoder` etc).
Pavel Minaev
+1  A: 

This is perfectly reasonable P/Invoke code, though you should make sure that you are not passing Unicode around (check your struct defn.), because all your native declarations seem to take ANSI strings.

C++/CLI doesn't really help you too much here - the place where it makes your life easier is when you want to write some blocks of native code, and make the interface to the C# part simpler. The only thing you could do here, is if on the C# side you really only cared about 1-2 params, you could have the C++/CLI DLL fill out the rest for you and not worry about as much ugly code on the C# side

Paul Betts
I originally didn't have `CharSet.Unicode` on the struct, but the Encode method wasn't encoding the string correctly. It worked fine when I changed it to Unicode, though.
David Brown
How about CharSet.ASCII?
Paul Betts
I don't see how his native declarations take ANSI strings. E.g. `unsigned short sAddress[...]` - note the `short` here - seems very much like an UTF-16 string to me!
Pavel Minaev
(Can't edit this, so adding a new one) - Yeah, your problem is *Definitely* the marshalling of those strings. You're declaring them as Unicode, so it's 2x the space, so you're overwriting the rest of the structure. 1 Char Unicode = 2 Chars ANSI.
Paul Betts
The only options are Ansi, Auto, None, and Unicode. ANSI works fine when encoding, but when I decode it later with another method, the strings come up empty. Unicode is decoded correctly into the original strings.
David Brown
@Pavel Hmmm, I suppose you may be right, I'm used to seeing those as wchar_t's@David: Now's the time where I would bust out WinDbg - set a breakpoint on the native function and check the structs/params passed into it. Of course, if you don't know WinDbg this might be too tricky :( Attach a native VS debugger perhaps? (i.e. just run the app, then "Attach" from the DLL's VS project)
Paul Betts
Yeah, I haven't used WinDbg enough to know how to get anything useful out of it. I also don't have the original library's source code, only the headers, so I can't debug it from VS. I think, at this point, I should just give up. This isn't exactly the right project for someone that doesn't know C/C++ very well.
David Brown
@David If you've got the library's PDBs, that should be good enough
Paul Betts
Unfortunately, the library comes with only DLLs, .LIB files, and headers.
David Brown