I'm having trouble figuring out the best way to have a delphi function operate on a byte array from .net.
The delphi signature looks like this:
procedure Encrypt(
var Bytes: array of byte;
const BytesLength: Integer;
const Password: PAnsiChar); stdcall; export;
The C# code looks like this:
[DllImport("Encrypt.dll",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi)]
public static extern void Encrypt(
ref byte[] bytes,
int bytesLength,
string password);
Omitting var and ref before the byte array declaration seemed to fail, but is it required since I'll be changing only the contents of the array and not the array itself?
Also, for some reason I can't seem to get the length of the array in delphi, if I remove the BytesLength parameter than Length(Bytes) will not work, if I add the BytesLength parameter, Length(Bytes) starts to work but BytesLength has a wrong value.
Make the first parameter of the Delphi Encrypt be Bytes: PByte and you should be good to go.
An open array, as you have it, expects to be passed both the pointer to the first element and the length which explains what you describe in your question.
Related
I just noticed std::byte in the C++ 17.
I am asking this question because I use the code below to send byte array to C++ to play audio sound.
C#:
[DllImport ("AudioStreamer")]
public static extern void playSound (byte[] audioBytes);
C++:
#define EXPORT_API __declspec(dllexport)
extern "C" void EXPORT_API playSound(unsigned char* audioBytes)
With the new byte type in C++ 17, it looks like I might be able to do this now:
C#:
[DllImport ("AudioStreamer")]
public static extern void playSound (byte[] audioBytes);
C++:
#define EXPORT_API __declspec(dllexport)
extern "C" void EXPORT_API playSound(byte[] audioBytes)
I am not sure if this will even work because the compiler I use does not support byte in C++ 17 yet.
So, is std::byte in C++ 17 equivalent to byte in C#? Is there a reason to not use std::byte over unsigned char* ?
According to C++ reference,
Like the character types (char, unsigned char, signed char) std::byte can be used to access raw memory occupied by other objects.
This tells me that you can freely replace
unsigned char audioBytes[]
with
std::byte audioBytes[]
in a function header, and everything is going to work, provided that you plan to treat bytes as bytes, not as numeric objects.
std::byte is equivalent to both unsigned char and char in C++ in a sense that it is a type to represent 1 byte of raw memory.
If you used unsigned char* in your interface, you can easily replace it with std::byte.
In your C# code this will result in no changes at all, on the C++ side this will make your type system more strict (which is a good thing) due to the fact that you will not be able to treat your std::bytes as text characters or as small integers.
Of course this is C++17 feature which may or may not be properly supported by your compiler.
I am having trouble marshalling data between a C# application and an existing C++ DLL. The caveats that are making this difficult are that it is an unsigned char pointer to an array, and I need access to the data (from all fields) after the call in C#. I would like to avoid using unsafe code, if at all possible.
Here is the C++ signature:
BYTE GetData(unsigned char *Data_Type, unsigned char *Data_Content, unsigned int *Data_Length);
I've tried a bunch of things in C#, but here's what I have now:
[DllImport("somecpp.dll")]
public static extern byte GetData([In][Out] ref byte Data_Type, [In][Out] ref byte[] Data_Content, [In][Out] ref int Data_Length);
Then calling it, I am attempting this:
byte retrievedData = GetData(ref data_type, ref data_content, ref data_length);
This is definitely not working, and I'm not sure what to try next. Any ideas? Thanks!
Your ref byte[] parameter matches unsigned char**. That's one level of indirection too many.
The p/invoke should be
[DllImport("somecpp.dll")]
public static extern byte GetData(
ref byte Data_Type,
[In,Out] byte[] Data_Content,
ref uint Data_Length
);
It is plausible that the function uses cdecl. We can't tell that from here.
I also suspect that the Data_Type argument should be out rather than ref.
I have pretty annoying issue which I'm unable to solve for 2 days. I have an encrypt() method which makes use of Crypto++ library written in C++. The method is implemented as follows:
string CRijndaelHelper::Encrypt(string text)
{
CFB_Mode< AES >::Encryption e;
e.SetKeyWithIV(_key, sizeof(_key), _iv);
string cipher, encoded;
// CFB mode must not use padding. Specifying
// a scheme will result in an exception
StringSource ss(text, true,
new StreamTransformationFilter(e,
new StringSink(cipher)
));
return cipher;
};
When I call this method within the native environment it works perfectly, encrypting the whole 26Kb xml string without any problem.
Eventually I've had to implement the encryption in C# code, for that purpose I wrote the following wrapper code in native dll for later use with PInvoke:
extern "C"
API_EXPORT char* _cdecl encrypt(char* contents)
{
string cont(contents);
CRijndaelHelper rij;
string transformedText = rij.Encrypt(cont);
return marshalReturn(transformedText);
}
While the PInvoke part looks as follows:
[DllImport("EncryptDecrypt.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string encrypt([MarshalAs(UnmanagedType.LPStr)] string contents);
And everything looks working perfect except that I get in transformedText variable only first 540 bytes encrypted and that's all.
Please advise.
Your fundamental problem is that you are working on the wrong types. Encryption works with byte arrays and not with text. You need to stop using string, char* and so on. You need to use unsigned char* in the unmanaged code, and byte[] in the managed code.
It might be hard for you to accept this advice, but I proffer it all the same. On top of the problems with zero bytes being treated as null terminators (the reason for your truncation), your current approach simply neglects the issue of text encoding. You cannot do that.
You presumably have good reasons why you wish to transform a string to another string. That's fine, but the encryption part of the transformation needs to operate on byte arrays. The way to handle encryption of text to text is to use a chain of transformations. The transformation when encrypting runs like this:
Convert string to byte array using well-defined encoding. For instance, UTF-8.
Encrypt byte array, which results in another byte array being output.
Encode this encrypted byte array as text using, for example, base64.
This respects the fact that encryption operates on byte arrays, as well as being explicit about the text encoding.
In the opposite direction you reverse the steps:
Decode the base64 to an encrypted byte array.
Decrypt this byte array.
Decode the decrypted byte array using UTF-8 to obtain the original string.
... returns a string which is correctly encrypted when invoked from native code
The problem is not you C++ encrypt, which uses a std::string. The problem is with marshalling it back to managed code as a char*.
Change CRijndaelHelper::Encrypt to the following to remove the embedded NULL that will be sprinkled liberally with a probability of 1/255:
#include <cryptopp/base64.h>
using CryptoPP::Base64Encoder;
...
string CRijndaelHelper::Encrypt(string text)
{
CFB_Mode< AES >::Encryption e;
e.SetKeyWithIV(_key, sizeof(_key), _iv);
string cipher, encoded;
// CFB mode must not use padding. Specifying
// a scheme will result in an exception
StringSource ss(text, true,
new StreamTransformationFilter(e,
new Base64Encoder(
new StringSink(cipher)
)));
return cipher;
};
With the Base64 encoding, the marshaling as a char* will not truncate the encryption results on the first NULL byte encountered.
Then, for decryption:
StringSource ss(cipher, true,
new Base64Decoder(
new StreamTransformationFilter(d,
new StringSink(text)
)));
consider following setup: c# application with c++ library. c# elements are filled from c++ via callback. on c# side callback is defined like this:
void callbackTester(IntPtr pData, UInt32 length)
{
int[] data = new int[length];
Marshal.Copy(pData, data, (int)0, (int)length);
//using data.. on c#
}
now, on c++ side callback is defined like this:
typedef void (__stdcall *func)(uint8_t* pdata, uint32_t length);
and c++ is using callback like this:
void onData()
{
std::vector<uint8_t> dataToCallback;
// fill in dataToCallback
_callback(&(dataToCallback[0]), dataToCallback.size());
// where _callback is a pointer to that c# function
}
my task: get array from c++ side on c# side using callback.
so, when c++ object is calling onData() function, it calls my callback from c#. so far so good. i have made a c++ tester program, which uses this, and i am receiving array correctly on callback side. if i am using it on c# tester, i am receiving crap.
for example: if i am sending uint8_t array of {1, 1}, i am getting {1, 1} for c++ tester, and i am getting {0xfeeeabab, 0xfeeefeee} on c# side... obviously, the conversion between uint8_t* c++ pointer and IntPtr c# is working not as i expect.
any suggestions? thanks a lot.
The issue appears to be that C++ uint8_t is an unsigned byte, and C# int is a signed 4 byte integer. So you have a simple mismatch of types. The C# type that matches uint8_t is byte.
Your callback should be:
void callbackTester(IntPtr pData, uint length)
{
byte[] data = new byte[length];
Marshal.Copy(pData, data, 0, (int)length);
}
one thing to check is that on C# side you are expecting int-4 bytes per element ("int[] data") but on C++ you only allocating uint8-1 byte per element.
adjust the allocation or length usage, you could be getting access violation, that why you see magic bytes [1].
[1] http://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_debug_values
Is this list correct?
unsigned int(c) -> uint(c#)
const char*(c) -> String(c#)
unsigned int*(c) -> uint[](c#)
unsigned char*(c) -> byte[](c#)
I think there's a mistake here because with these 4 parameters for native function I have PInvokeStackImbalance.
C function is:
bool something
(unsigned char *a,
unsigned int a_length,
unsigned char *b,
unsigned int *b_length);
PInvoke is:
[DllImport(#"lib.dll", EntryPoint = "something")]<br>
public static extern bool something(
byte[] a,
uint a_length,
byte[] b,
uint[] b_length);
First, PInvoke.net is your friend.
Second, You conversions are correct except that you should use a StringBuilder for functions that take a char* as a buffer to fill ([in out]).
Your stack imbalance may be due to the use of different calling conventions. The default calling convention for C# is __stdcall, but your C function is probably __cdecl. If that is the case you will need to add the CallingConvention to your DLLImport attribute.
EDIT: Also, as Groo pointed out, if the pointer arguments in your C function are actually just pointers to unsigned int (for example, as opposed to expecting an array of int) then you should use ref uint instead of an int[].