views:

340

answers:

3

Hi,

I have a Delphi dll defined like this:

TMPData = record
 Lastname, Firstname: array[0..40] of char;
 Birthday: TDateTime;
 Pid: array[0..16] of char;
 Title: array[0..20] of char;
 Female: Boolean;
 Street: array[0..40] of char;
 ZipCode: array[0..10] of char;
 City: array[0..40] of char;
 Phone, Fax, Department, Company: array[0..20] of char;
 Pn: array[0..40] of char;
 In: array[0..16] of char;
 Hi: array[0..8] of char;
 Account: array[0..20] of char;
 Valid, Status: array[0..10] of char;
 Country, NameAffix: array[0..20] of char;
 W, H: single;
 Bp: array[0..10] of char;
 SocialSecurityNumber: array[0..9] of char;
 State: array[0..2] of char;
end;   

function Init(const tmpData: TMPData; var ErrorCode: integer; ResetFatalError: boolean = false): boolean;

procedure GetData(out tmpData: TMPData);

My current c# signatures looks like this:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TMPData
{            
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 40)]
    public string Lastname;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 40)]
    public string Firstname;
    [MarshalAs(UnmanagedType.R8)]
    public double Birthday;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 16)]
    public string Pid;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Title;
    [MarshalAs(UnmanagedType.Bool)]
    public bool Female;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 40)]
    public string Street;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 10)]
    public string ZipCode;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 40)]
    public string City;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Phone;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Fax;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Department;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Company;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 40)]
    public string Pn;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 16)]
    public string In;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 8)]
    public string Hi;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Account;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 10)]
    public string Valid;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 10)]
    public string Status;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string Country;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 20)]
    public string NameAffix;
    [MarshalAs(UnmanagedType.I4)]
    public int W;
    [MarshalAs(UnmanagedType.I4)]
    public int H;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 10)]
    public string Bp;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 9)]
    public string SocialSecurityNumber;
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 2)]
    public string State;
}

[DllImport("MyDll.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Init(TMPData tmpData, int ErrorCode, bool ResetFatalError);

[DllImport("MyDll.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetData(out TMPData tmpData);

I first call Init setting the BirthDay, LastName and FirstName. I then call GetData but the TMPData structure I get back is incorrect. The FirstName, LastName and Birthday fields are populated but the data is incorrect. Is the mapping correct? ( "array[0..40] of char" equal to "[MarshalAs(UnmanagedType.LPStr, SizeConst = 40)]" )?

Update:

I have updated the c# mapping with the feedback to look like this:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
public struct TMPData 
{             
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)] 
    public string Lastname; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)] 
    public string Firstname; 
    [MarshalAs(UnmanagedType.R8)] 
    public double Birthday; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)] 
    public string Pid; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Title; 
    [MarshalAs(UnmanagedType.Bool)] 
    public bool Female; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)] 
    public string Street; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] 
    public string ZipCode; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)] 
    public string City; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Phone; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Fax; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Department; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Company; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)] 
    public string Pn; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)] 
    public string In; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] 
    public string Hi; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Account; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] 
    public string Valid; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] 
    public string Status; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string Country; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)] 
    public string NameAffix; 
    [MarshalAs(UnmanagedType.I4)] 
    public int W; 
    [MarshalAs(UnmanagedType.I4)] 
    public int H; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] 
    public string Bp; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] 
    public string SocialSecurityNumber; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 3)] 
    public string State; 
} 

The Init function:

[DllImport("MyDll.dll")] 
[return: MarshalAs(UnmanagedType.Bool)] 
public static extern bool Init(TMPData tmpData, int ErrorCode, bool ResetFatalError); 

now fails with the following error:

"Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

when I call it as shown below:

int errorCode = 0;
bool resetLastError = true;
TMPData tmpData = new TMPData();

        tmpData.Lastname = "TestLastName";
        tmpData.Firstname = "TestName";
        tmpData.Birthday = 28856.0;
        tmpData.Pid = "12345678";
        tmpData.Title = null;
        tmpData.Female = false;
        tmpData.Street = null; 
        tmpData.ZipCode = null;
        tmpData.City = null;
        tmpData.Phone = null;
        tmpData.Fax = null;
        tmpData.Department = null; 
        tmpData.Company = null;
        tmpData.Pn = null;
        tmpData.In = null;
        tmpData.Hi = null;
        tmpData.Account = null;
        tmpData.Valid = null;
        tmpData.Status = null;
        tmpData.Country = null;
        tmpData.NameAffix = null;
        tmpData.W = 0;
        tmpData.H = 0;
        tmpData.Bp = null;
        tmpData.SocialSecurityNumber = 0;
        tmpData.State = null;

bool success = Init(tmpData, errorCode, resetLastError);    

If I change the ByValTStr to LPStr in the struct definition then the Init function succeeds but the GetData function returns incorrect string values. If I change LPStr back to ByValTStr the Init function fails but the GetData function returns the correct strings. I am not sure if I should marshal array[0..x] of char as LPStr of ByValTStr?

A: 

As dtb mentioned in his comment, 0..40 is 41 characters, not 40. Apparently all your string definitions fail to take into account the 0th element.

Also, if I'm reading this right, (I don't know C# but I do know C,) it looks like you're defining the char arrays as pointers to long (Unicode, 16 bit per char) strings. There are two potential issues with that. First off, a char array declared that way is not a pointer to a string, it's an inline string. Second, it's only an array of WideChars (16 bit per char) if this was built with Delphi version 2009 or later. Otherwise, it an array of Ansi (8 bit per char) chars.

Mason Wheeler
Thank you for the feedback. What definition would you suggest to avoid the potential issues with LPStr? I have tried ByValTStr but it only partially solves the problem as indicated in the updated question
Wouter Roux
A: 

What version of Delphi is the DLL built with? Delphi 2009 introduced Unicode, which would mean you would need to use a Unicode string type in C#, while if it is pre-Delphi 2009 then there is no Unicode. LPStr is 8 Bit, while the character type of ByValTStr is determined by the System.Runtime.InteropServices.CharSet argument of the System.Runtime.InteropServices.StructLayoutAttribute applied to the containing structure.

See: http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype.aspx

You said originally you were getting data back, but it was incorrect. How was in incorrect? Garbage, or just swapped, truncated, etc?

Jim McKeeth
The dll was built with Delphi 6. The data I got back looked like garbage. Not sure if it was swapped but it was not truncated.
Wouter Roux
So if you looked at your data before the call it was different then the data after the call, but it didn't resemble the data you were expecting at all?
Jim McKeeth
Yes if the setting is: UnmanagedType.LPStr the Init call works fine. If I call GetData the TMPData structure that I get back have garbage data for the firstname, lastname fields and default values for all the other fields. If I change to ByValTStr the Init call fails but I can still succesfully call GetData and the return values are correct from the previous successfull call of Init. I think I need to look at the Delphi code and try to figure out what is happening. I don't know delphi but hope that I can create a com delphi dll that is easier to use with interop than a standard dll
Wouter Roux
+1  A: 

okay I finally got it working. thanks for the help.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]  
public struct TMPData  
{              
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)]  
    public string Lastname;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)]  
    public string Firstname;  
    [MarshalAs(UnmanagedType.R8)]  
    public double Birthday;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]  
    public string Pid;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Title;  
    [MarshalAs(UnmanagedType.Bool)]  
    public bool Female;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)]  
    public string Street;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]  
    public string ZipCode;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)]  
    public string City;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Phone;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Fax;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Department;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Company;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)]  
    public string Pn;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]  
    public string In;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]  
    public string Hi;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Account;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]  
    public string Valid;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]  
    public string Status;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string Country;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]  
    public string NameAffix;  
    [MarshalAs(UnmanagedType.R4)]  
    public int W;  
    [MarshalAs(UnmanagedType.R4)]  
    public int H;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]  
    public string Bp;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]  
    public string SocialSecurityNumber;  
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 3)]  
    public string State;  
}  

[DllImport("MyDll.dll")]         
[return: MarshalAs(UnmanagedType.Bool)]         
public static extern bool Init(ref TMPData tmpData,ref int ErrorCode, bool ResetFatalError);         

[DllImport("MyDll.dll")]         
[return: MarshalAs(UnmanagedType.Bool)]         
public static extern bool GetData(ref TMPData tmpData);    
Wouter Roux