I am trying to use PInvoke to send a structure containing strings to a DLL written in Microfocus COBOL using net7.0 (x86) and some strings appear to be get truncated while marshalling. The structure are read from and written to by the DLL. I don't have access to the source.
The struct looks like
using System.Runtime.InteropServices;
namespace DllImport.Test;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TransactionHeader
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string Filler1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string TransactionCode; //4
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string FunctionCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
public string JuryPopSystemId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string SystemId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
public string tchSystemUserId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string Filler2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public string SystemPassword;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
public string DbConnectFlag;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string Jid;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
public string Pin;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string AltReturnCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string AltInfoCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string Filler3;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string Filler4;
[MarshalAs(UnmanagedType.I4)] public int tchRtnCdNum;
[MarshalAs(UnmanagedType.I4)] public int tchInfCdNum;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 35)]
public string ErrorMsg;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Reserved;
}
The call is
[DllImport("XXXX.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern void THECALL([In,Out] ref TransactionHeader hdr, [In, Out] ref udtString128 request,
[In, Out] ref udtCommArea commArea);
After the call, the header has the strings truncated by 1 character. For example, I set the SystemId to "DEVL", but the value is "DEV" after the call. I feel as though I am running to a null terminated string problem, but I wanted to check with people more experienced with PInvoke.
Declaring the struct members as UnmanagedType.LPStr makes no difference.
Thanks.
i have a byte array that is sent through sockets. The byte array is a ascii string.
var bytes = Encoding.ASCII.GetBytes("00000250Q+0000SPS_22MFR ");
How can i convert/marshal this byte array into a struct?
internal struct Header
{
public ulong Size;
public string Type;
public ushort Sequence;
public ushort ErrorCode;
public string Sender;
public string Receiver;
}
I already tried it with StructLayout and MarshalAs, but i get either an exception or a very different input/output.
[StructLayout(LayoutKind.Sequential, Size = 26, CharSet = CharSet.Ansi)]
internal struct SitTelegramHeader
{
public ulong Size;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] public string Type;
public ushort Sequence;
public ushort ErrorCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)] public string Sender;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)] public string Receiver;
}
Is there any way to do this with Marshal/BinaryFormatter/etc. or do i have to convert it by hand?
Thx for help.
The struct that i have built in c# when run against the function does not give me one of the error message that tell me something is wrong with the struct. It says that a source file could not be found. Again, I don't know much about this DLL b/c of lack of documentation. But I am still thinking it is because I set the struct up wrong. Most likely i'm thinking the 3rd struct where it is referencing a struct within the struct.
i was hoping for some feedback on the work I did.
Thanks for the assistance.
This is what was provided in C:
int BatchTotal_Transactions(int transType, pTGiftCardReqBatchTotal req, pTGiftCardRespBatchTotal resp, int (*Com)(char *));
typedef struct _tagGiftCardReqBatchTotal
{
char Password[9];
char OperatorID[9];
char BatchNum[14];
char StartDate[11];
char EndDate[11];
unsigned char Type;
} TGiftCardReqBatchTotal, *pTGiftCardReqBatchTotal;
typedef struct _tagGiftCardRespBatchTotal
{
char Result;
char TerminalId[17];
unsigned char DispMsgControl;
char DispMsg[256];
char Display[41];
char Date[11];
char Time[9];
char RespCode[4];
char BatchNum[14];
char ErrorFlag;
char CustLang;
char UserLang;
char OpenDate[17];
char ClosedDate[17];
char StartDate[11];
char EndDate[11];
char BatchStatus;
int CardTypeNum;
TGiftCardTotals GctHost[MAX_CARD_CODES];
TGiftCardTotals GctTRS[MAX_CARD_CODES];
} TGiftCardRespBatchTotal, *pTGiftCardRespBatchTotal;
typedef struct _tagGiftCardTotals
{
unsigned short CardCode;
unsigned short PurchaseNum;
long PurchaseTotal;
unsigned short RefundNum;
long RefundTotal;
unsigned short RedemptionNum;
long RedemptionTotal;
unsigned short CorrectionNum;
long CorrectionTotal;
long PurchaseBenefitTotal;
long RefundBenefitTotal;
long RedemptionBenefitTotal;
} TGiftCardTotals, *pTGiftCardTotals;
And this is how I did it in C#:
[DllImport("batch.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern int BatchTotal_Transactions(int transType, ref giftCardReqBatchTotal req, ref giftCardRespBatchTotal resp, IntPtr com);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct _tagGiftCardReqBatchTotal
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string Password;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string OperatorID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string BatchNum;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
public string StartDate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
public string EndDate;
public byte Type;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct _tagGiftCardRespBatchTotal
{
public byte Result;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
public string TerminalId;
public byte DispMsgControl;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DispMsg;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 41)]
public string Display;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
public string Date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string Time;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string RespCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string BatchNum;
public byte ErrorFlag;
public byte CustLang;
public byte UserLang;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
public string OpenDate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
public string ClosedDate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
public string StartDate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
public string EndDate;
public byte BatchStatus;
int CardTypeNum;
[MarshalAs(UnmanagedType.Struct, SizeConst = 1024)]
public _tagGiftCardTotals GctHost;
[MarshalAs(UnmanagedType.Struct, SizeConst = 1024)]
public _tagGiftCardTotals GctTRS;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct _tagGiftCardTotals
{
public UInt16 CardCode;
public UInt16 PurchaseNum;
public int PurchaseTotal;
public UInt16 RefundNum;
public int RefundTotal;
public UInt16 RedemptionNum;
public int RedemptionTotal;
public UInt16 CorrectionNum;
public int CorrectionTotal;
public int PurchaseBenefitTotal;
public int RefundBenefitTotal;
public int RedemptionBenefitTotal;
}
Are you sure the error isn't the following:
Specified module could not be found.
If this is the error then it is because the dll could not be found. Make sure the dll is in your path:
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dllimportattribute.value.aspx
I've spent a lot of time to look for the solution but still don't find it out.
I have 2 classes:
[StructLayout(LayoutKind.Sequential)]
public class Result
{
public int Number;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string Name;
public int Size;
}
[StructLayout(LayoutKind.Sequential)]
public class CoverObject
{
public int NumOfResults;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 4)]
public Result[] Results;
}
My expectation that the command Marshal.SizeOf(typeof(CoverObject)) will return 52, but not, it's just 20. Thus, all of marshall and unmarshall that I use later are not working.
Seeming it only counts the first member (Number) in Result class. Did I do anything wrong?
Change your classes to structs
[StructLayout(LayoutKind.Sequential)]
public struct Result
{
public int Number;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string Name;
public int Size;
}
[StructLayout(LayoutKind.Sequential)]
public struct CoverObject
{
public int NumOfResults;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 4)]
public Result[] Results;
}
some where else:
Marshal.SizeOf(typeof(CoverObject)) // it will return 52
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?
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);
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?
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.