I'm trying to create a wrapper for a C dll to use it in C#. I don't have the source code of the dll.
I'm having problem using a method that gives me "System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt'" when executing. I tried to search online for the same problem but I didn't find what the problem with my code is.
The method that gives the problem is the following:
C:
L3B6_API L3B6_ERR_t STDCALL L3B6_ActivateBoot(int nodeid, unsigned cpunum, unsigned ms_timeout, const char * devicenames, A * dev_param);
the wrapper I created in C# is this:
[DllImport("L3B6.dll", EntryPoint = "L3B6_ActivateBoot")]
return: MarshalAs(UnmanagedType.I4)]
static extern L3B6ErrorCode ActivateBoot(
int nodeid,
uint cpunum,
uint msTimeout,
[MarshalAs(UnmanagedType.LPStr)]
string deviceNames,
ref A devParams
);
Where L3B6ErrorCode is an enum I created but it doesn't give any problem (I used it for other methods that work).
I think the problem is in the structure A. The original structure is like this:
struct A
{
unsigned char blv;
unsigned char cpunum;
unsigned char nodeid;
unsigned char hwverA;
unsigned char hwverB;
unsigned char hwverC;
unsigned char hwverD;
unsigned char cputype;
unsigned char hwcode;
B memini_devrec;
};
struct B
{
unsigned hwCode;
unsigned cpuCode;
unsigned dualCPU;
unsigned cpuNumber;
unsigned internalFlashStart;
unsigned internalFlashEnd;
unsigned externalFlashAccess;
unsigned externalFlashStart;
unsigned externalFlashEnd;
unsigned isResetVectorSpecified;
unsigned resetVectors;
char bootName[32];
unsigned isS19toPhyConversionAllowed;
unsigned noFlashPaging;
char deviceName[32];
char cpuName[32];
};
The corresponding structures I created are the following:
[StructLayout(LayoutKind.Sequential)]
public struct A
{
public byte bootloaderversion;
public byte cpuNum;
public byte nodeId;
public byte hwVerA;
public byte hwVerB;
public byte hwVerC;
public byte hwVerD;
public byte cpuType;
public byte hwCode;
public B meminiDeviceRecord;
};
[StructLayout(LayoutKind.Sequential)]
public struct B
{
public uint hwCode;
public uint cpuCode;
[MarshalAs(UnmanagedType.Bool)]
public bool dualCPU;
public uint cpuNumber;
public uint internalFlashStart;
public uint internalFlashEnd;
public uint externalFlashAccess;
public uint externalFlashStart;
public uint externalFlashEnd;
[MarshalAs(UnmanagedType.Bool)]
public bool isResetVectorSpecified;
public uint resetVectors;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string bootName;
[MarshalAs(UnmanagedType.Bool)]
public bool isS19toPhyConversionAllowed;
[MarshalAs(UnmanagedType.Bool)]
public bool noFlashPaging;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string deviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string cpuName;
};
I've used the structure B in another method that needed to fill it and didn't have any problem, so I suppose the problem is in the A structure, but I can't understand what I'm doing wrong. I think the problem is that structure because also another method that takes that structure as parameter is giving me the same exception. I checked the size of the structs in C and C# and it is the same.
Related
Could anyone please help me with the following?
I have a dll written in C and want to call a certain function in it from C#.
The function returns a pointer to a structure. Here is the structure:
typedef struct
{
char crv_name[40];
char crv_name2[12];
char units[40];
char creator[24];
char index_units[8];
double first_dep_tim;
double last_dep_tim;
double level_spacing;
EmptyValU empty_val;
long num_ele;
}
Now, I know (from calling this from a C client) that the last member (num_ele) will be set to 1.
Now, that penultimate member is of type EmptyValU which is defined as:
typedef union
{
double d;
float f;
long l;
ulong ul;
short s;
ushort us;
char c;
}EmptyValU;
Now, I can call this ok fom C# and read everything up until empty_val. My value for num_ele is nonsense as I am clearly misaligned in memory after empty_val.
Here is my C# code:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CurveInfo
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
public string crv_name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]
public string crv_name2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
public string units;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
public string creator;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string index_units;
public double first_dep_tim;
public double last_dep_tim;
public double level_spacing;
[MarshalAs(UnmanagedType.Struct, SizeConst = 8)]
public EmptyValU empty_val;
public long num_ele;
}
and I have defined EmptyValU as:
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct EmptyValU
{
[FieldOffset(0)]
public double d;
[FieldOffset(0)]
public float f;
[FieldOffset(0)]
long l;
[FieldOffset(0)]
ulong ul;
[FieldOffset(0)]
short s;
[FieldOffset(0)]
ushort us;
[FieldOffset(0)]
char c;
}
Like I say, when I call the function which returns a pointer to such a structure all the values are populated correctly up until the EmptyValU member, which, along with num_ele is not correctly populated. Something about the way I have to define the union in C# is what I am missing in order to keep the alignment correct.
Thanks for any help,
Mitch.
I've solved this.
I C, a long (and ulong) is 4 bytes wide, but in C# they are 8 bytes wide.
So instead of:
[FieldOffset(0)]
long l;
[FieldOffset(0)]
ulong ul;
I should have had:
[FieldOffset(0)]
int l;
[FieldOffset(0)]
uint ul;
becasue in C# int and uint are 4 bytes wide.
For the same reason, on the C# side instead of:
public long num_ele;
I need:
public int num_ele;
Now it all works.
I'm write a plugin with c# . the program with c# will use a dll which writes by c ,so I have to call c function in my c# program ,but unfortunately the c function's parameter is a struct and it is so complex that I never find any
help information about how to convert it to c# parameter.
the struct has an embedded function and void* parameter ,I didn't find anyway to convert them to c#.
the struct is mostly like this
struct first_struct{
char* parameter1;
int parameter2;
unsigned long parameter3;
unsigned short parameter4;
void* parameter5;
int (*parameter6)(int,void *);
second_struct parameter7;
};
struct second_struct{
char parameter8[64] ;
char parameter9[256];
};
I want change this c struct to c# struct but I have no idea how to do it
thanks #Biesi Grr and #Ian Abbott ‘s help it sames that I can change the c struct to c# like that.
public delegate int parameter6(int volcnt, System.IntPtr vod);
[StructLayout(LayoutKind.Sequential)]
public struct csharp_firstSturct
{
[MarshalAs(UnmanagedType.LPStr)]
public string parameter1;
public int parameter2;
public ulong parameter3;
public ushort parameter4;
public System.IntPtr parameter5;
public parameter6 m_parameter6;
}
but I still has no idea about how to convert parameter7 from C to C#,what should I do to convert parameter7 form c to c#.
Assuming your char arrays are null terminated strings you can do it the following way
public delegate int parameter6(int volcnt, System.IntPtr vod);
[StructLayout(LayoutKind.Sequential)]
public struct csharp_firstSturct
{
[MarshalAs(UnmanagedType.LPStr)]
public string parameter1;
public int parameter2;
public ulong parameter3;
public ushort parameter4;
public System.IntPtr parameter5;
public parameter6 m_parameter6;
[MarshalAs(UnmanagedType.Struct)]
public second_struct parameter7;
}
[StructLayout(LayoutKind.Sequential)]
public struct second_struct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string parameter8;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string parameter9;
}
I'm trying to call a C DLL in my C# project using marshaling and have some functions working but I have trouble with others. Like the one below.
My first problem is getting the structs correct and next problem is to return PROFILE_INFO as an array with the list of program files or maybe it won't return a list and proNum is an index.
function in C
extern "C" __declspec(dllexport) int WINAPI GetProgramFileList (unsigned long proNum, PROFILE_INFO *proFile);
typedef struct{
PROINFO proInfo;
__int64 proSize;
PROGRAM_DATE createDate;
PROGRAM_DATE writeDate;
}PROFILE_INFO;
typedef struct{
char wno[33];
char dummy[7];
char comment[49];
char dummy2[7];
char type;
char dummy3[7];
}PROINFO;
typedef struct{
short year;
char month;
char day;
char hour;
char min;
char dummy[2];
}PROGRAM_DATE;
My function
[DllImport(#".\IFDLL.dll", EntryPoint = "GetProgramFileList", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern int GetProgramFileListTest(ulong proNum, ref PROFILE_INFO pro);
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROFILE_INFO
{
[MarshalAs(UnmanagedType.Struct)]
public PROINFO proInfo; // WNo/name/type
public long proSize; // Program size
[MarshalAs(UnmanagedType.Struct)]
public PROGRAM_DATE createDate; // Program creating date
[MarshalAs(UnmanagedType.Struct)]
public PROGRAM_DATE writeDate; // Program updating date
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROINFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno; // WNo.
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy; // dummy
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment; // program name
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2; // dummy
public char type; // program type
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3; // dummy
}
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROGRAM_DATE
{
public short year; // Date (Year) 4-digit
public char month; // Date (Month)
public char day; // Date (Day)
public char hour; // Date (Time)
public char min; // Date (Minutes)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private char dummy; // Dummy
}
The PROGRAM_DATE createDate in C# struct PROFILE_INFO will throw:
Cannot marshal field 'createDate' of type 'CClient.Models.PROFILE_INFO': The type definition of this field has layout information but has an invalid managed/unmanaged type combination or is unmarshalable.
Changing PROGRAM_DATE fields to string makes it accept it but the function returns an argument(-60) error instead. Though not sure if I'm closer to success.
Other attempts, including attempts to get PROFILE_INFO to return as an array (ref PROFILE_INFO[]), landed in:
A call to PInvoke function has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature.
I have got these descriptions following the C dll:
Explanation
Obtain the program list in the standard area
Argument
proNum [in]
Specify the number of the data to be obtained.
proFile [out]
Store the program information in the standard area, in PROFILE_INFO type.
Before executing this function, make sure to ensure the data area for the number of the data to be obtained.
Return value
If it succeeded, “0” is returned. If there is an error, a value other than “0” is returned.
Other functions I have working is GetProgramDirInfo, SendProgram, ReceiveProgram, SearchProgram, etc. but they don't return any arrays, so I assume marshaling arrays is my problem here. Also I'm trying to avoid using unsafe pointers and I'm unsure if I need to do the copying myself.
Any help is appreciated.
Few points when working with p/invoke:
don't add what's obvious for .NET (structs are structs, ...)
don't add pack if you're unsure, by default .NET should behave like C/C++ on that matter
don't add Ansi information if there's no string in the definition (only when there are strings or TStr, etc.). It doesn't cause problems but is useless
in general, don't add attributes if you don't know what they are used for
int and long in C/C++ are (in general) 32-bit. long is not 64-bit in C/C++
Here is a code that should be better:
[DllImport(#".\IFDLL.dll", EntryPoint = "GetProgramFileList")]
public static extern int GetProgramFileListTest(uint proNum, ref PROFILE_INFO pro);
[StructLayout(LayoutKind.Sequential)]
public struct PROFILE_INFO
{
public PROINFO proInfo; // WNo/name/type
public long proSize; // Program size
public PROGRAM_DATE createDate; // Program creating date
public PROGRAM_DATE writeDate; // Program updating date
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct PROINFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno; // WNo.
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy; // dummy
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment; // program name
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2; // dummy
public char type; // program type
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3; // dummy
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct PROGRAM_DATE
{
public short year; // Date (Year) 4-digit
public char month; // Date (Month)
public char day; // Date (Day)
public char hour; // Date (Time)
public char min; // Date (Minutes)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private string dummy; // Dummy
}
I am trying to create some vhd/vhdx files using the VHD API in C#.
There's a C++ union that looks like this:
typedef struct _CREATE_VIRTUAL_DISK_PARAMETERS
{
CREATE_VIRTUAL_DISK_VERSION Version;
union
{
struct
{
GUID UniqueId;
ULONGLONG MaximumSize;
ULONG BlockSizeInBytes;
ULONG SectorSizeInBytes;
PCWSTR ParentPath;
PCWSTR SourcePath;
} Version1;
struct
{
GUID UniqueId;
ULONGLONG MaximumSize;
ULONG BlockSizeInBytes;
ULONG SectorSizeInBytes;
ULONG PhysicalSectorSizeInBytes;
PCWSTR ParentPath;
PCWSTR SourcePath;
OPEN_VIRTUAL_DISK_FLAG OpenFlags;
VIRTUAL_STORAGE_TYPE ParentVirtualStorageType;
VIRTUAL_STORAGE_TYPE SourceVirtualStorageType;
GUID ResiliencyGuid;
} Version2;
struct
{
GUID UniqueId;
ULONGLONG MaximumSize;
ULONG BlockSizeInBytes;
ULONG SectorSizeInBytes;
ULONG PhysicalSectorSizeInBytes;
PCWSTR ParentPath;
PCWSTR SourcePath;
OPEN_VIRTUAL_DISK_FLAG OpenFlags;
VIRTUAL_STORAGE_TYPE ParentVirtualStorageType;
VIRTUAL_STORAGE_TYPE SourceVirtualStorageType;
GUID ResiliencyGuid;
PCWSTR SourceLimitPath;
VIRTUAL_STORAGE_TYPE BackingStorageType;
} Version3;
};
} CREATE_VIRTUAL_DISK_PARAMETERS, *PCREATE_VIRTUAL_DISK_PARAMETERS;
I'm trying to convert that to C#, but not having much luck. I'm not interested in Version3 at all, so am leaving that out.
I've tried a number of things and the best I could get to was having Version2 working (by doing something really bizarre), but I've never managed to get Version1 and Version2 working at the same time.
The solution that has wielded the best results so far has been this, but there has to be something wrong there because Version1 simply doesn't work, and SectorSizeInBytes in Version1 is a ulong rather than uint (if I change it to uint like it should be, I break Version2 and Version1 still doesn't work!)
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParameters
{
[FieldOffset(0)] public CreateVirtualDiskParametersVersion1 Version1;
[FieldOffset(0)] public CreateVirtualDiskParametersVersion2 Version2;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion1
{
public CreateVirtualDiskVersion Version;
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public ulong SectorSizeInBytes;
public string ParentPath;
public string SourcePath;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion2
{
public CreateVirtualDiskVersion Version;
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public uint SectorSizeInBytes;
public uint PhysicalSectorSizeInBytes;
public string ParentPath;
public string SourcePath;
public OpenVirtualDiskFlags OpenFlags;
public VirtualStorageType ParentVirtualStorageType;
public VirtualStorageType SourceVirtualStorageType;
public Guid ResiliencyGuid;
}
I know theoretically the Version field should be set outside the Version structs and I have tried that as well, but it just breaks things even more funnily enough...
So, can someone advise how to properly translate the above to C#, leaving out the Version3 struct as that's not needed?
Using Pack = 1 to StructLayout attributes eliminates any padding between struct members.
In TCP connections structs are usually passed around without padding so that all programs using the struct can agree on its layout in memory.
However as #David Heffernan pointed out, that may not be the case when passing structs to Windows DLL's. I didn't test the actual call to CreateVirtualDisk because it seemed a bit risky, given that I haven't used this call before and didn't want to clobber my disk if I made a mistake. It looks as if the default packing of 8 bytes (Pack = 0 for default or Pack = 8) may be the correct setting, based on the following quote.
See 64-bit Windows API struct alignment caused Access Denied error on named pipe
The Windows SDK expects packing to be 8 bytes. From Using the Windows Headers
Projects should be compiled to use the default structure packing, which is currently 8 bytes because the largest integral type is 8 bytes. Doing so ensures that all structure types within the header files are compiled into the application with the same alignment the Windows API expects. It also ensures that structures with 8-byte values are properly aligned and will not cause alignment faults on processors that enforce data alignment.
Version is moved to the top of CreateVirtualDiskParameters.
The two unions then follow. Both have the same offset sizeof(CREATE_VIRTUAL_DISK_VERSION).
Also SectorSizeInBytes is uint rather than ulong.
You can let the marshaller do the work of filling string members using the attribute, eg
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
Or, you can represent it as it appears in memory, which is a pointer to a Unicode string:
public IntPtr ParentPath;
and then extract the string yourself with
Marshal.PtrToStringAuto(vdp.Version1.ParentPath)
If you're passing the C# struct to an external DLL, populate it with an unmanaged string
vdp.Version1.ParentPath = (IntPtr)Marshal.StringToHGlobalAuto("I am a managed string");
then free the unmanaged string when you're finished with it
Marshal.FreeHGlobal(vdp.Version1.ParentPath);
Try this.
public enum CREATE_VIRTUAL_DISK_VERSION
{
CREATE_VIRTUAL_DISK_VERSION_UNSPECIFIED = 0,
CREATE_VIRTUAL_DISK_VERSION_1 = 1,
CREATE_VIRTUAL_DISK_VERSION_2 = 2
};
public enum OPEN_VIRTUAL_DISK_FLAG
{
OPEN_VIRTUAL_DISK_FLAG_NONE = 0x00000000,
OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS = 0x00000001,
OPEN_VIRTUAL_DISK_FLAG_BLANK_FILE = 0x00000002,
OPEN_VIRTUAL_DISK_FLAG_BOOT_DRIVE = 0x00000004,
OPEN_VIRTUAL_DISK_FLAG_CACHED_IO = 0x00000008,
OPEN_VIRTUAL_DISK_FLAG_CUSTOM_DIFF_CHAIN = 0x00000010
};
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)]
public struct VIRTUAL_STORAGE_TYPE
{
uint DeviceId;
Guid VendorId;
};
[StructLayout(LayoutKind.Explicit, Pack = 8, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParameters
{
[FieldOffset(0)]
public CREATE_VIRTUAL_DISK_VERSION Version;
[FieldOffset(8))]
public CreateVirtualDiskParametersVersion1 Version1;
[FieldOffset(8))]
public CreateVirtualDiskParametersVersion2 Version2;
}
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion1
{
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public uint SectorSizeInBytes;
//public IntPtr ParentPath; // PCWSTR in C++ which is a pointer to a Unicode string
//public IntPtr SourcePath; //string
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
[MarshalAs(UnmanagedType.LPWStr)] public string SourcePath;
}
[StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Unicode)]
public struct CreateVirtualDiskParametersVersion2
{
public Guid UniqueId;
public ulong MaximumSize;
public uint BlockSizeInBytes;
public uint SectorSizeInBytes;
public uint PhysicalSectorSizeInBytes;
//public IntPtr ParentPath; //string
//public IntPtr SourcePath; //string
[MarshalAs(UnmanagedType.LPWStr)] public string ParentPath;
[MarshalAs(UnmanagedType.LPWStr)] public string SourcePath;
public OPEN_VIRTUAL_DISK_FLAG OpenFlags;
public VIRTUAL_STORAGE_TYPE ParentVirtualStorageType;
public VIRTUAL_STORAGE_TYPE SourceVirtualStorageType;
public Guid ResiliencyGuid;
}
I have the following C function:
int w_ei_connect_init(ei_cnode* ec, const char* this_node_name,
const char *cookie, short creation);
ei_cnode looks like this:
typedef struct ei_cnode_s {
char thishostname[EI_MAXHOSTNAMELEN+1];
char thisnodename[MAXNODELEN+1];
char thisalivename[EI_MAXALIVELEN+1];
char ei_connect_cookie[EI_MAX_COOKIE_SIZE+1];
short creation;
erlang_pid self;
} ei_cnode;
Which I have converted to C#:
[StructLayout(LayoutKind.Sequential)]
public struct cnode {
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_HOSTNAME_LEN + 1)]
public string thishostname;
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_NODE_LEN + 1)]
public string thisnodename;
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_ALIVE_LEN + 1)]
public string thisalivename;
[MarshalAsAttribute(UnmanagedType.ByValTStr,
SizeConst = Ei.MAX_COOKIE_SIZE + 1)]
public string ei_connect_cookie;
public short creation;
public erlang_pid self;
}
I'm not good with pointers or C in general, so I'm not sure how I'm supposed to supply a cnode to ei_connect_init.
What would the equivalent C# signature be for the C function above?
Whenever you want to pass a C# struct to a parameter value containing the equivalent native struct but with a pointer, the typical method is to label the parameter as "ref". This causes the PInvoke layer to essentially pass the address in.
[DllImport("somedll")]
public static extern w_ei_connect_init(
ref cnode v,
[In] string this_node_name,
[In] string cookie,
int16 creation);
Something like this should work:
int w_ei_connect_init(ref cnode ec,
[MarshalAs(UnmanagedType.LPStr)] string this_node_name,
[MarshalAs(UnmanagedType.LPStr)] string cookie, short creation);
You should also consider marking your struct with
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
attribute, so those TStr will be ansi-strings, not unicode.