_bstr_r vs _T("") - c#

I have a .net library which is registered as a COM object, when importing the .tlb file in a C++ project I'm getting such a method declaration
virtual HRESULT __stdcall GetBid (
/*[in]*/ BSTR symbol,
/*[out,retval]*/ double * pRetVal ) = 0;
for the .NET equivalent
double GetBid(string symbol);
now I'm trying to call it like this
double bid;
ptr->GetBid(_T("AAPL"), &bid);
which doesn't work as expected, because on the .NET side the string parameter is actually an empty string.
If I change to such a call
double bid;
ptr->GetBid(_bstr_t("AAPL"), &bid);
everything works as expected.
Why both calls are compiled fine, but the result is different? Shouldn't the first call be converted into a correct marshaling of string?
Thx for any under the hood information about BSTR magic :)

BSTR has a 32-bit length preceeding the string. Thus, BSTR can contain embedded nulls.
_T("AAPL") creates a wchar_t * with a terminating null, but with no length prefix.
Under the hood though, both are wchar_t * so the call compiles and there is no conversion necessary. You were somewhat lucky because worse things could happen than just getting no string on the other side. The marshaler might look at the _T("AAPL") count back 32-bits, and happen to get a reeeeaally long length value by luck, which would be bad. :-)
Youl would get an automatic conversion if the parameter was defined as _bstr_t, since that would invoke the _bstr_t(wchar_t *) constructor.

Because BSTR is a pointer to a wide character string, but it's not mean that you can just assign simple const wchar_t* string. For working with BSTR you need to use a few system functions, line SysAllocString() for creating BSTR. _bstr_t class incapsulate all this stuff

Related

Returning string from delphi dll to c#

I am trying to separate an encryption function from our legacy code to a dll which I can call from C#, but I am having issues getting it to work and I keep getting access violations when calling the dll.
I am not sure where the AV happens because delphi has a hard time hitting my breakpoints when the dll is attached to another process.
I got it to work yesterday using David Heffernan's answer here: Returning a string from delphi dll to C# caller in 64 bit
But my success was short-lived as I changed the string parameters to regular string's (delphi) saw it didn't work and changed them back to to AnsiString (our encryption routine expects Ansi). Since I changed these param types. I have not been able to get it to work again.
Here is my Delphi Code:
procedure Encrypt(const Source: AnsiString; const Key: AnsiString; var OutPut:PAnsiChar; const OutputLength: Integer);
var
EncryptedString, EncodedString: AnsiString;
begin
EncryptedString := Crypt(Source, Key);
EncodedString := Encode(EncryptedString);
if Length(EncodedString) <= OutputLength then
System.AnsiStrings.StrPCopy(Output, EncodedString);
end;
exports
Encrypt;
My C# caller:
[DllImport("AsmEncrypt.dll", CharSet = CharSet.Ansi)]
public static extern void Encrypt(string password, string key, StringBuilder output, int outputlength);
// using like this:
Encrypt(credentials.Password, myKey, str, str.Capacity);
My best bet right now is that I've goofed some of the arguments to the dll since it seems to crash before it reaches an OutputDebugStr() I had put on first line of Encrypt()
All help will be greatly appreciated
Change the Delphi function to
procedure Encrypt(Source, Key, OutPut: PAnsiChar; OutputLength: Integer); stdcall;
in order to make this code work.
You should probably also make the length argument IN/OUT so that the caller can resize the string builder object once the call returns. That would also allow the callee to signal any errors to the caller, another flaw in your current design.
I must also say that using AnsiString as a byte array is a recipe for failure. It's high time you started doing encryption right. If you have text, then encode it as a byte array with a specific encoding, usually this means UTF-8. Then encrypt that byte array to another byte array.
From this docs page:
The AnsiString structure contains a 32-bit length indicator, a 32-bit reference count, a 16-bit data length indicating the number of bytes per character, and a 16-bit code page.
So an AnsiString isn't simply a pointer to an array of characters -- it's a pointer to a special structure which encodes a bunch of information.
However, .NET's P/Invoke machinery is going to pass a pointer to an array of characters. Delphi is going to try and interpret that as a pointer to its special AnsiString structure, and things aren't going to go well.
I think you're going to have a hard time using AnsiString in interop. You're better off choosing a string type which both .NET and Delphi know about. If you then need to convert that to AnsiString, do that in Delphi.

Difficulty to figure out what (uint8_t const * const *) means in .Net terms

I'm reading a C++ code, and I'm pulling my hair trying to understand this cast:
(uint8_t const * const *) someVideoFrame->someData
I see something like a pointer to an array of byte, but I have difficulties understanding the double constant pointer usage.
From what I've been able to understand, I've simply ported it as (byte **).
In terms of steps, what is this cast trying to achieve? What is the const effect in C++ ?
Edit
I've found this meanwhile in C++ documentation:
int const * const Constant4
... declares that Constant4 is constant pointer to a constant integer. Basically ‘const’ applies to whatever is on its immediate left (other than if there is nothing there in which case it applies to whatever is its immediate right).
But I'm still wondering what is the objective of declaring constants on the fly.
Start from right and move to the left: pointer to const pointer to const uint8_t
uint8_t const * const *
// ^^^^^^^^^^^^^ ^^^^^^^ ^
// to to
// const uint8_t <- const pointer <- pointer
If you want to learn more about reading pointer declarations and be as good as it gets, I strongly recommend learning the "spiral rule". Although old, I found it super useful, and the mnemonic is really easy to grasp.
The cast is probably trying to enforce that you're not modifying by mistake the inner pointer (which is const), nor the data it points to (which is also const).

c++ wchar array to c#

I have no knowledge of C++ and I have to cenvert some code to C#. I've managed to do some bits but I don't really understand how to convert a few lines so I'm here asking for help.
This is the C++ code:
WCHAR wsSerial[MAX_PATH]={'\0'};
WCHAR wsS2[MAX_PATH]={'\0'};
wcscpy_s(wsSerial, MAX_PATH, m_strSerial);
wcscpy_s(wsS2,MAX_PATH,wsSerial+8);
wsS2[8]=NULL;
ULONG ulCode2 = wcstoul(wsS2, NULL,10);
This is what I have in C#:
string wsSerial;
string wsS2;
wsSerial = mSerial; //an external input
wsS2 = wsSerial + 8;
wsS2= wsSerial.Substring(0, 8);
long ulCode2 = long.Parse(wsS2);
So I have two questions:
wsSerial is an array in C++ but I don't need an array for this in C#, do I? I mean, all it does is store a large number which is later converted into a numeric value, right?
What exactly does this do? wcscpy_s(wsS2,MAX_PATH,wsSerial+8). The + 8 throws me off.
In C a string is simply a contiguous area containing a "string" of characters, terminated by a special character. In other words, an array of char. (Or wchar_t for wide-character strings.)
In C# (and C++) this is not needed as it has its own special string type, which handles the array-stuff behind the scenes.
Regarding the +8 thing, it simply skips the first eight characters of wsSerial when copying. To understand this, you should read about "pointer arithmetic".
It's like this:
string wsSerial = mSerial;
string wsS2 = wsSerial.Substring(8, 8);
long ulCode2 = long.Parse(wsS2);
It looks like you're almost all the way there, but the first argument in the call to
wsSerial.Substring()
should be 8, the second should be MAX_PATH minus 8.

Communication between c++ and c# application through network

I have a simple server written in c# listening on some port. I have an application in c++ and I need the application to send some information to the server. This information is a struct containing 5 integers. I was thinking that I can send it also as a string: something like: "ID=3, anotherInt=5...". Is it a good idea? If not, how should I do that?
How to make it work? What is your advice?
Thank you.
I think you have a mistake in your code.
char *ln = "String to send";
connect(client_socket, (struct sockaddr *)&clientService, sizeof(clientService));
send(client_socket,(const char*)&ln, sizeof(ln), 0);
The prototype for send function is:
ssize_t send(int socket, const void *message, size_t length, int flags);
ln is already a pointer to your char buffer. You are passing in &ln, which is the address
of the pointer. Shouldn't it be just "ln"?
You should fix the send() method in client code. sizeof() is wrong way to find the length of string, casts applied on "ln" aren't quite right for what you need there. Check <<this link>> for an example and see how it works for you. BTW, C# code in the server needs some serious re-writing if it were to work predictably. You are using 4096 byte buffer and calls to Read() aren't guaranteed to fetch the entire transmission in one go. You will need a loop just for Read to make sure you are reading everything you need - ofcourse, this needs a clear definition of communication semantics. Happy networking!
First of all, (const char*)&ln is not correct. ln is a char *, so when you take the address of it using & you are getting a char **, which you are then casting to a char *. This is undefined behavior. You'll want to get rid of the & operator. Also you'll probably want to read up on what pointers are and how to use them.
As a rule of thumb, don't cast willy-nilly to make the compiler shut up. The errors are trying to tell you something. However, the sockaddr and sockaddr_in thing is correct; the api is designed that way. If you turn on -Wall in your compiler options, it should give you a warning in the correct place.
ALSO: you want strlen(ln) and not sizeof.
Quick n Dirty Rundown on Pointers
When a type includes * before the variable name, the variable holds a pointer to a value of that type. A pointer is much like a reference in C#, it is a value holding the location of some data. These are commonly passed into functions when a function wants to look at a piece of data that the caller owns, sometimes because it wants to modify it. A string is represented as a char *, which is a pointer to the first character in the string. The two operators that are related to pointers are & and *. & takes an lvalue and returns a pointer to that value. * takes a pointer and returns the value it points to. In this case, you have a string, as a char *, and the function you're calling wants a char *, so you can pass it in directly without casting or using * or &. However, for the other function you have a struct sockaddr_in and it wants a struct sockaddr *, so you (correctly) use & to get a struct sockaddr_in *, then cast it to a struct sockaddr *. This is called "type punning," and is an unpleasant reality of the API. Google will give you a better description of type punning than I can.
connect(client_socket, (struct sockaddr *)&clientService, sizeof(clientService));
this is ok, but this line should read:
send(client_socket,(const char*)ln, strlen(ln), 0);
where the conversion (const char*) can be omitted.
In your code the value of pointer ln is sent (correctly) but you most likely want to send the string it's pointing to in it's entire length.
Concerning the messages to be send: Converting integers to ascii is not a bad idea. You also may have a look at JSON or Googles protobuf format. Formatters or Parsers can easily be written from scratch.

Unmanaged Exports / Delphi / .NET 4 / Robert Giesecke

I've successfully used Roberts UnmanagedExportLibrary.zip to call a .NET 2/3.5 assembly from Delphi 2007.
However when I recompile the C# assembly to target .NET 4 using VS2010 the call crashes with a stack overflow exception in ntdll.dll. (ntdll calling ntdll) after loading mscorlib/mscoreei.
Has anybody else got this to work when targeting .NET 4? - Robert's documentation seems to imply that this should work.
Great work by Robert by the way - very useful.
Thanks
Myles.
Arrays are more tricky because you need to take more care over where the array is allocated and destroyed. The cleanest approach is always to allocate at the caller, pass the array to the callee to let it fill out the array. That approach would look like this in your context:
public struct Sample
{
[MarshalAs(UnmanagedType.BStr)]
public string Name;
}
[DllExport]
public static int func(
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]
Sample[] samples,
ref int len
)
{
// len holds the length of the array on input
// len is assigned the number of items that have been assigned values
// use the return value to indicate success or failure
for (int i = 0; i < len; i++)
samples[i].Name = "foo: " + i.ToString();
return 0;
}
You need to specify that the array needs to be marshalled in the out direction. If you wanted values marshalled both ways then you would use In, Out instead of Out. You also need to use MarshalAs with UnmanagedType.LPArray to indicate how to marshal the array. And you do need to specify the size param so that the marshaller knows how many items to marshal back to the unmanaged code.
And then on the Delphi side you declare the function like this:
type
TSample = record
Name: WideString;
end;
PSample = ^TSample;
function func(samples: PSample; var len: Integer): Integer; stdcall;
external dllname;
Call it like this:
var
samples: array of TSample;
i, len: Integer;
....
len := 10;
SetLength(samples, len);
if func(PSample(samples), len)=0 then
for i := 0 to len-1 do
Writeln(samples[i].Name);
Update
As AlexS [discovered][1] (see comments below), passing the size param index by reference is only supported on .net 4. On earlier versions you need to pass the size param index by value.
The reason I chose to pass it by reference here is to allow for the following protocol:
The caller passes in a value indicating how large the array is.
The callee passes out a value indicating how many elements have been populated.
This works well on .net 4, but on earlier versions you would need to use an extra parameter for step 2.
Reference
https://stackoverflow.com/a/22507948/4339857

Categories