StringBuilder codeline = new StringBuilder(100);
protected virtual int OnCodeLine(int code, int docId, ref StringBuilder codeline)
{
////
}
This is what i get with *ref StringBuilder*
ĞĞÑĞÒĞÓĞÔĞÕĞÖĞ×ĞØĞÙĞÚĞÛĞÜĞİĞŞĞßĞàĞáĞâĞãĞäĞåĞæĞçĞèĞéĞêĞëĞìĞíĞîĞïĞğĞñĞòĞóĞôĞõĞöĞ÷ĞøĞùĞúĞûĞüĞıĞşĞÿĞÑÑÑÑÑÑÑ Ñ
ÑÑÑ
ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
and only with StringBuilder i only get 3
This is what its suppose to return
300 078 9059431
By the way this the MICR Code from Cheques
[DllImport("mflib.dll")]
public static extern int mfScanFeeder(int mode, int font, int timeout);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int MFS100_CodeLineDelegate(int code, int docId, ref StringBuilder codeline);
public event MFS100_CodeLineDelegate MFS100_CodeLine;
private static MFS100_CodeLineDelegate cache_CodeLine;
Update : Here is the Original Code that works in vb6
Public Function MFS100_OnCodeline(ByVal code As Long, ByVal docId As Long, ByVal codeline As String) As Long
Dim i As Integer
WriteEvent "OnCodeline:"
WriteEvent " code = " & code
WriteEvent " docId = " & docId
WriteEvent " codeline = " & codeline
MFS100_OnCodeline = -1 ' -1 means: sorting will be done by mfSort()
g_codeline = codeline
CScannerForm.TmrSort.enabled = True
End Function
Update 2
mfSetEvent((int)EventEnum.E_EVENT_CODELINE, cache_CodeLine);
[DllImport("mflib.dll")]
private static extern int mfSetEvent(int eventID, Delegate callsback);
When i use StringBuilder with ref i get a string that have
32361 length. Without ref i get
only the first value of the
string.
OnCodeLine is for the callback
from the scanner device. What is the
problem ?
You should not pass the StringBuilder by ref. When you do that you say you only get one character back. That's the hallmark of expecting ANSI encoding but actually receiving Unicode (UTF-16). Fix it by specifying the character set:
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet=CharSet.Unicode)]
public delegate int MFS100_CodeLineDelegate(int code, int docId, StringBuilder codeline);
I don't understand what are you trying to achieve, but if you want to collect a string generated inside OnCodeLine, you don't need to pass the StringBuilder by reference, as it is a reference type.
Just pass the StringBuilder without ref, populate it, and when you return you'll have the desired string in it.
Regarding what you get after calling the OnCodeLine, can you provide some info regarding the implementation?
Sorry, I didn't notice the PInvoke was involved!! :(
Related
I'm working from VBA into .NET.
I have a working version of an interface using CLI and stdcall
I'm trying to remove the resulting dependency on the C++ 2015 runtime, and it looks like I can do it using UnmanagedExports.
But I have a couple of questions.
Can I just use "ref string" as a parameter and have it work?
If so, can I replace it with "out string"?
In either cases, do I have to do any string/string length management?
I currently pass a couple of callbacks in as "int". From an example I saw elsewhere, it looks like on the C# side, using this, I should be able to replace those parameters with Func for a callback such as Function A(p1 as String, p2 as Long, p3 as String) as Long
Any advice would be much appreciated.
You need a combination of StrPtr, possibly StrConv on the Access side, and IntPtr on the .NET side:
'VBA7
Private Declare PtrSafe Function Command Lib "External.dll" (ByVal CommandName As String, ByVal Result As LongPtr, ByRef ResultLength As Long) As Long
'VBA pre7
Private Declare Function Command Lib "External.dll" (ByVal CommandName As String, ByVal Result As Long, ByRef ResultLength As Long) As Long
'Example to use.
'Result will be up to "i" characters - no new string involved
Dim i As Long, x As Long, strResult As String
i = 100
strResult = Space(i)
x = Command(CommandName, Arguments, StrPtr(strResult), i)
If you use StrConv, the string type is up to you. If you don't, the pointer will be pointing to a Unicode array.
C# side:
[DllExport("Command", CallingConvention.StdCall)]
public static int Command(string commandName, string arguments, IntPtr result, out /*or ref*/ int resultLength)
{
string inputStr = Marshal.PtrToStringUni(result); //Unicode
resultLength = inputStr.Length;
int x = MainFunc.Command(commandName, arguments, ref inputStr);
if(null == inputStr)
{
inputStr = "";
}
if(inputStr.Length > resultLength)
{
inputStr = inputStr.Substring(0, resultLength);
}
byte[] outputBytes = Encoding.Unicode.GetBytes(inputStr);
Marshal.Copy(outputBytes, 0, result, outputBytes.Length);
resultLength = inputStr.Length;
return x;
}
By automatic conversion of code written in VB.NET to C# I have such situation in function declaration of VB.NET:
Private Function dataArchiver(ByVal toPlace As String,
Optional ByVal aExtension As String = ".7z",
Optional ByRef createdName As String = "") As Integer
Tool for automatic conversion does this in C#:
private int dataArchiver(string toPlace,
string aExtension = ".7z",
ref string createdName = "")
And this of course don't work. Keyword "ref" before last argument is underlined with red.
Why is that so? Because string createdName may be (and don't have to be) generated in function and in that case have to be passed out from function.
It is important that this code can work with NET framework 3.5.
Any idea to get this working in C# without much reconcepting of VB.NET program?
You have to create overloaded methods for this (as you would have had to do before C# acquired the optional parameter feature):
private int dataArchiver(string toPlace, string aExtension)
{
string tempVar = "";
return dataArchiver(toPlace, aExtension, ref tempVar);
}
private int dataArchiver(string toPlace)
{
string tempVar = "";
return dataArchiver(toPlace, ".7z", ref tempVar);
}
private int dataArchiver(string toPlace, string aExtension, ref string createdName)
{
return 0;
}
You can use the out keyword instead in this case:
string createdName;
int retVal = dataArchiver("to place", out createdName, extension);
And your new method signature.
private int dataArchiver(string toPlace,
out string createdName,
string aExtension = ".7z")
{
createdName = "some value";
// rest of your code
}
The only change is that out can't have a method signature default value, so you will need to set it inside the method.
I've been trying to invoke a method that have been created in Delphi in the following way:
function _Func1(arrParams: array of TParams): Integer;stdcall;
type
TParams = record
Type: int;
Name: string;
Amount : Real;
end;
My code is:
[DllImport("some.dll", EntryPoint = "_Func1", CallingConvention = CallingConvention.StdCall)]
public static extern int Func(
[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct)] TParams[] arrParams)
And the struct is:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TParams
{
public int Type;
[MarshalAs(UnmanagedType.AnsiBStr)]
public string Name;
public double Amount;
}
When I am calling this method I'm getting the error:
Cannot marshal field 'Name' of type 'TParams': Invalid managed/unmanaged type combination (String fields must be paired with LPStr, LPWStr, BStr or ByValTStr).
However none of those combinations works, as Delphi's strings are prefixed with its length and it is Ansi for sure (I've tried it with other string parameters). Does anyone have a clue how to solve this?
There are two main problems with this, use of open arrays and use of Delphi string.
Open arrays
Delphi open arrays are implemented by passing a pointer to the first element of the array and an extra parameter specifying the index of the last item, high in Delphi terminology. For more information see this answer.
Delphi strings
The C# marshaller cannot interop with a Delphi string. Delphi strings are private types, only to be used internally to a Delphi module. Instead you should use a null-terminated string, PAnsiChar.
Putting it all together you can write it like this:
Delphi
type
TParams = record
_Type: Integer;//Type is a reserved word in Delphi
Name: PAnsiChar;
Amount: Double;
end;
function Func(const arrParams: array of TParams): Integer; stdcall;
C#
[StructLayoutAttribute(LayoutKind.Sequential)]
public struct TParams
{
public int Type;
public string Name;
public double Amount;
}
[DllImport("some.dll")]
public static extern int Func(TParams[] arrParams, int high);
TParams[] params = new TParams[len];
...populate params
int retval = Func(params, params.Length-1);
To compliment David's answer, you can marshal to a Delphi string, but it's ugly. In C#, you have to replace all of your strings in the struct with IntPtr.
private static IntPtr AllocDelphiString(string str)
{
byte[] unicodeData = Encoding.Unicode.GetBytes(str);
int bufferSize = unicodeData.Length + 6;
IntPtr hMem = Marshal.AllocHGlobal(bufferSize);
Marshal.WriteInt32(hMem, 0, unicodeData.Length); // prepended length value
for (int i = 0; i < unicodeData.Length; i++)
Marshal.WriteByte(hMem, i + 4, unicodeData[i]);
Marshal.WriteInt16(hMem, bufferSize - 2, 0); // null-terminate
return new IntPtr(hMem.ToInt64() + 4);
}
This can directly be sent to Delphi, where it'll properly be read as a string.
Remember that you must free this string when you're done with it. However, GlobalFree() can't be called directly on the pointer to the string, because it doesn't point to the start of the allocation. You'll have to cast that pointer to a long, then subtract 4, then cast it back to a pointer. This compensates for the length prefix.
I'm sorry to ask this here since I'm sure it must be answered "out there", but I've been stuck on this for several months now, and none of the solutions I've found have worked for me.
I have the following VB code that works:
Declare Function DeviceSendRead Lib "unmanaged.dll" (ByVal sCommand As String, ByVal sReply As String, ByVal sError As String, ByVal Timeout As Double) As Integer
Dim err As Integer
Dim outstr As String
Dim readstr As String
Dim errstr As String
outstr = txtSend.Text
readstr = Space(4000)
errstr = Space(100)
Timeout = 10
err = DeviceSendRead(outstr, readstr, errstr, Timeout)
and I am trying to implement it in a C# project. The best equivalent I have been able to find is:
[DllImport("unmanaged.dll")] public static extern int DeviceSendRead(String outstr, StringBuilder readstr, StringBuilder errstr, double Timeout);
int err;
StringBuilder readstr = new StringBuilder(4000);
StringBuilder errstr = new StringBuilder(100);
err = DeviceSendRead(txtSend.Text, readstr, errstr, 10);
However, when I run this, the application freezes and I must force quit it. By experimenting with ref and out, I have occasionally managed to make it crash rather than freeze, but the only "progress" I have achieved is to replace the dll function call with:
DeviceSendRead(txtSend.Text, null, null, 10);
This prevents the crash, but of course does nothing (that I can detect). I'm therefore assuming that it's the manner of passing the two return string parameters that is causing the problem. If anyone can suggest what I might be doing wrong, I'd be very happy to hear it. Thanks.
I have reached an answer, which I will record here for completeness, with grateful thanks to all those who pointed me in the right direction.
According to this post elsewhere, the use of .NET Reflector on similar VB code suggests the need to use the string type in place of my StringBuilder, as suggested here by Alex Mendez, JamieSee and Austin Salonen, together with explicit marshaling, as suggested by Nanhydrin, but utilising the unmanaged type VBByRefStr rather than AnsiBStr. The final key to the puzzle is that the string parameter then needs to be passed by reference using the ref keyword.
I can confirm that this works, and that my final working C# code is therefore:
[DllImport("unmanaged.dll", CharSet = CharSet.Ansi)]
public static extern short DeviceSendRead(
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sCommand,
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sReply,
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sError,
double Timeout);
short err;
string outstr = txtSend.Text;
string readstr = new string(' ', 4000);
string errstr = new string(' ', 100);
err = DeviceSendRead(ref outstr, ref readstr, ref errstr, 10);
I hope this is useful to others facing a similar issue.
Try this:
[DllImport("unmanaged.dll")]
public static extern int DeviceSendRead(string outString, string readString, string errorString, double timeout);
int err;
string outstr;
string readstr;
string errstr =
outstr = txtSend.Text;
readstr = new string(' ', 4000);
errstr = new string(' ', 100);
double timeout = 10;
err = DeviceSendRead(outstr, readstr, errstr, timeout);
Try this as an equivalent:
string readstr = new string(' ', 4000);
string errstr = new string(' ', 1000);
Default Marshalling for strings
Default Marshalling behaviour
You may need to be more specific in your dllimport declaration and add in some MarshalAs attributes, if you have more details on what type of strings the called function is expecting (Ansi, Unicode, null terminated, etc.) then that would help.
In fact it expecting null terminated strings could perhaps explain why it's hanging rather than erroring out.
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outString, [MarshalAs(UnmanagedType.AnsiBStr)]string readString, string errorString, double timeout);
You might also need to explicitly state that your parameters are input, output, or both by using the parameter attributes [In, Out].
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outstr, string readstr, string errstr, double Timeout);
You cannot marshal a StringBuilder here. There are some rules to follow for marshalling StringBuilder (see CLR Inside Out: Marshaling between Managed and Unmanaged Code):
StringBuilder and Marshaling
The CLR marshaler has built-in knowledge of the StringBuilder type and
treats it differently from other types. By default, StringBuilder is
passed as [InAttribute, OutAttribute]. StringBuilder is special
because it has a Capacity property that can determine the size of the
required buffer at run time, and it can be changed dynamically.
Therefore, during the marshaling process, the CLR can pin
StringBuilder, directly pass the address of internal buffer used in
StringBuilder, and allow the contents of this buffer to be changed by
native code in place.
To take full advantage of StringBuilder, you'll need to follow all of
these rules:
1.Don't pass StringBuilder by reference (using out or ref). Otherwise, the CLR will expect the signature of this argument to be wchar_t **
instead of wchar_t *, and it won't be able to pin StringBuilder's
internal buffer. Performance will be significantly degraded.
2.Use StringBuilder when the unmanaged code is using Unicode. Otherwise, the CLR will have to make a copy of the string and convert
it between Unicode and ANSI, thus degrading performance. Usually you
should marshal StringBuilder as LPARRAY of Unicode characters or as
LPWSTR.
3.Always specify the capacity of StringBuilder in advance and make sure the capacity is big enough to hold the buffer. The best practice
on the unmanaged code side is to accept the size of the string buffer
as an argument to avoid buffer overruns. In COM, you can also use
size_is in IDL to specify the size.
Rule 3 doesn't seem like it is satisied here.
Hi I have the following sub:
static ustr *construct_packet(int ptype, int stype, int argc, ...)
static ustr *construct_subpacket(int sptype, const char *data, int datalen, int lenbyte)
I tried the "..." in C# and it just gives me an error of "Type Expected."
I also need to send multiple construct_subpacket's into the construct_packet sub.
What would be the equivalent to these in C#?
Question 2:
I want to pass weird ASCII characters like "char(13)" the box through a string without it being replaced by "?." How do I go about doing such a thing? Or is there no way?
Thanks
static string construct_packet(int ptype, int stype, int argc, ...)
static string construct_subpacket(int sptype, string data, int datalen, int lenbyte)
Note:
You don't really have to pass datalen in C# - unlike with pointers, with strings you always know length of the string by checking the Length property on the string
If you want to pass variable number of arguments, you can use "params" keyword. All those arguments have to be the same type