Marshalling char* C function to C# - c#

I am trying to understand how to marshall the char* type by passing and modifying strings back and forth between managed & unmanaged code. Managed to unmanaged code seems to work fine, but the opposite does not work. Is IntPtr suited for this situation?
C
EXPORT char* CharTest(char* ptchar, unsigned char* ptuchar)
{
ptchar[0] = 'x';
ptchar[1] = 'y';
printf("%s %s\n", ptchar, ptuchar);
return(ptchar);
}
C#
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest(string ptchar, string ptuchar);
static void Main()
{
string ptchar = "ptchar";
string ptuchar = "ptuchar";
Console.WriteLine(Marshal.PtrToStringAnsi(CharTest(ptchar, ptuchar)));
}
Output
xychar ptuchar
x?J
Thank you!

You could declare the return type of the imported function as string
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static string CharTest(string ptchar, string ptuchar);
But because of the fact that you are actually returning one of the parameters, you would have to rely on the marshaller not freeing the parameter buffer before copying the return buffer.
You have two further options:
Marshal it yourself. Make sure to place it in a try/finally in case of exceptions
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest(IntPtr ptchar, string ptuchar);
static void Main()
{
string ptchar = "ptchar";
string ptuchar = "ptuchar";
IntPtr ptcharPtr = IntPtr.Zero;
try
{
ptcharPtr = Marshal.StringToHGlobalAnsi(ptchar);
Console.WriteLine(Marshal.PtrToStringAnsi(CharTest(ptcharPtr, ptuchar)));
}
finally
{
Marshal.FreeHGlobal(ptcharPtr);
}
}
Declare the parameter as StringBuilder which means it will be copied both ways. In this case you do not need to look at the return value as it will be the same as the parameter.
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest([In, Out] StringBuilder ptchar, string ptuchar);
static void Main()
{
StringBuilder ptchar = new StringBuilder("ptchar");
string ptuchar = "ptuchar";
CharTest(ptchar, ptuchar);
Console.WriteLine(ptchar);
}

Related

Pass C# Byte[] to C++ API

I have to pass an Byte array containing an MAC-Address to a C++ Method. Since I don't have much experience with working with c
C++ APIsI don't know how to do this. I've tried to pass the array itself, but got an invalid parameter code as response from the API. I've also tried to create an IntPtr but to no avail.
I know that the problem is that C++ can't handle managed datatypes such as arrays, so I've to create a unmanaged array somehow, I think.
Here is the definition of the C++ Method:
ll_status_t LL_Connect(
ll_intf_t intf,
uint8_t address[6]);
The array in C# is defined the following way:
Byte[] addr = new Byte[6];
Of course, the array is not empty.
For example:
C++
extern "C"
{
__declspec(dllexport) void GetData(uint8_t* data, uint32_t length)
{
for (size_t i = 0; i < length; ++i)
data[i] = i;
}
}
C#
[DllImport("LibName.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetData([In, Out] [MarshalAs(UnmanagedType.LPArray)] byte[] data, uint length);
And use in C#
byte[] data = new byte[4];
GetData(data, (unit)data.Lenght);
If you have an array fixed length, for example:
C++
extern "C"
{
__declspec(dllexport) void GetData(uint8_t data[6])
{
for (size_t i = 0; i < 6; ++i)
data[i] = i;
}
}
C#
[DllImport("LibName.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetData([In, Out] [MarshalAs(UnmanagedType.LPArray, SizeConst = 6)] byte[] data);
And use in C#
byte[] data = new byte[6];
GetData(data);
For your case:
[DllImport("LibName.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int LL_Connect(byte intf, [In, Out] [MarshalAs(UnmanagedType.LPArray, SizeConst = 6)] byte[] address);

C# Sending String Array to C++ DLL as SafeArray. Gets only first character

I have C# project and I have to use a C++ dll using DllImport. (I have source codes of c++ dll)
I'm importing a function from c++ dll like this :
[DllImport("Example.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SendRequest([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] ref string[] fields);
I'm using this function in C# like this :
List<String> fields = new List<String>();
fields.Add("Test1");
fields.Add("Test2");
string[] fieldsArr = fields.ToArray();
int resultOfSendRequest = SendRequest(ref fieldsArr);
Problem is in c++ dll, it casts string to char* in somewhere and it reads the only first character, not entire string.
How can I solve this without touching c++ dll. (If I call this function from VB6 it works without any problem.)
C++ code is like this :
VARIANT vVar;
__declspec( dllexport ) int _stdcall SendRequest (SAFEARRAY**);
int _stdcall SendRequest ( SAFEARRAY** arrayFlds,
short NFlds)
{
// *********** prepare O.i.d, fields name, values
for (long iElem=0; iElem < NFlds; iElem++)
if (LoadElement (&vVar, iElem, &flds[iElem], &pFlds[iElem],*arrayFlds)==-1)
return -1;
//...
}
int LoadElement( VARIANT* vVar,
long iElem,
S_FLDS* flds,
char** pFld,
SAFEARRAY* arrayFlds)
{
hRes = SafeArrayGetElement(arrayFlds, &iElem, pFld);
strcpy(flds->FieldName, *pFld);
flds->bValLen = 0;
char *Name = flds->FieldName;
//....
}
The fieldName and char *Name at the end of the core only consists of first character of string. Not the full string.
In C Langues a string is a byte[] with each string terminated with a '\0'. An array of strings the last item has two '\0' at the end. So try following :
[DllImport("Example.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SendRequest(IntPtr fields);
static void Main(string[] args)
{
List<String> fields = new List<String>();
fields.Add("Test1");
fields.Add("Test2");
string fieldsArr = string.Join("\0", fields);
IntPtr fieldsPtr = Marshal.StringToBSTR(fieldsArr);
int results = SendRequest(fieldsPtr);
}
If it is an array of pointers then use this
public struct Pointers
{
public IntPtr[] pointers;
}
[DllImport("Example.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SendRequest(IntPtr fields);
static void Main(string[] args)
{
List<String> fields = new List<String>();
fields.Add("Test1");
fields.Add("Test2");
List<IntPtr> pointers = new List<IntPtr>();
foreach (string field in fields)
{
IntPtr intPtr = Marshal.StringToBSTR(field);
pointers.Add(intPtr);
}
Pointers sPointers = new Pointers();
sPointers.pointers = pointers.ToArray();
IntPtr fieldsPtr = IntPtr.Zero;
Marshal.StructureToPtr(sPointers, fieldsPtr, true);
int results = SendRequest(fieldsPtr);
Marshal.FreeHGlobal(fieldsPtr);
}

Get certificate of an installed Windows Installer package with product code

I have a couple of product codes filtered from MsiEnumProducts and need to get their assigned certificates.
It should be possible to get the cert by using MsiOpenDatabase, but I don't know how to reliably get paths to the .msi files.
I would prefer to avoid external assemblies (like wix), only pinvoke.
Given a product code, a call to MsiGetProductInfo passing the property name INSTALLPROPERTY_LOCALPACKAGE returns the location of the cached installer package in C:\Windows\Installer.
Once you have the cached package, a call to MsiGetFileSignatureInformation can retrieve the certificate.
Example using pInvoke:
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport("msi.dll", CharSet = CharSet.Ansi, SetLastError = false)]
static extern int MsiEnumProducts(int iProductIndex, StringBuilder lpProductBuf);
[DllImport("msi.dll", CharSet = CharSet.Ansi, SetLastError = false)]
static extern int MsiGetProductInfo(string product, string property, [Out] StringBuilder valueBuf, ref Int32 len);
[DllImport("msi.dll", CharSet = CharSet.Ansi, SetLastError = false)]
static extern int MsiGetFileSignatureInformation(string fileName, int flags, out IntPtr certContext, IntPtr hashData, ref int hashDataLength);
[DllImport("Crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
static extern int CertFreeCertificateContext( IntPtr certContext );
[DllImport("Crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
static extern int CertGetNameString(IntPtr certContext, UInt32 type, UInt32 flags, IntPtr typeParameter, StringBuilder stringValue, UInt32 stringLength );
static void Main(string[] args)
{
int index = 0;
StringBuilder productCode = new StringBuilder();
int result = MsiEnumProducts(index, productCode);
while (result == 0)
{
Console.WriteLine("{0}", productCode);
Int32 length = 1024;
StringBuilder fileName = new StringBuilder();
result = MsiGetProductInfo(
productCode.ToString(),
"LocalPackage",
fileName,
ref length );
if (result == 0)
{
Console.WriteLine("{0}", fileName);
IntPtr certContext = IntPtr.Zero;
IntPtr hashData = IntPtr.Zero;
int hashDataLength = 0;
result = MsiGetFileSignatureInformation(
fileName.ToString(),
0,
out certContext,
hashData,
ref hashDataLength);
if ( result == 0 )
{
Console.WriteLine("Got Cert");
StringBuilder simpleDisplayType = new StringBuilder();
int ok = CertGetNameString(
certContext,
4, // == CERT_NAME_SIMPLE_DISPLAY_TYPE
0,
IntPtr.Zero,
simpleDisplayType,
1024 );
if (ok != 0)
{
Console.WriteLine("{0}", simpleDisplayType);
}
CertFreeCertificateContext(certContext);
}
}
++index;
result = MsiEnumProducts(index, productCode);
}
}
}

How to free the memory allocated from C++ in C#?

I called a C++ function named "GenerateCode" in C# to get a pointer to a memory block, (return from a "new" operator ). And after use in C#, the memory block should be released.
But I tried Marshal.FreeHGlobal(), it always throws exception.
And also, I wrote a C++ function to free the memory, it always throws exception too.
I wonder how to do.
C# code
[DllImport("Generate.dll", EntryPoint = "GenerateCode", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GenerateCode([MarshalAs(UnmanagedType.LPStr)]string ComputerInfo, string ProductKey);
[DllImport("Generate.dll", EntryPoint = "FreeMemory", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern void FreeMemory(IntPtr ptr);
public string GetRegisterCode(string ComputerInfo, string ProductKey)
{
string s = null;
IntPtr ptr = new IntPtr();
ptr = GenerateCode(ComputerInfo, ProductKey);
s = Marshal.PtrToStringAnsi(ptr, CODE_LENGTH);
FreeMemory(ptr);
}
C++ Code:
const int LENGTH = 24;
extern "C" __declspec(dllexport) char* GenerateCode(char* ComputerInfo, char* ProductKey)
{
char* strAsciiName = new char[LENGTH];
return strAsciiName;
}
extern "C" __declspec(dllexport) void FreeMemory(char* ptr)
{
if(ptr != NULL)
{
delete[] ptr;
ptr = NULL;
}
}

Why this Explicit P/Invoke does not work?

The following .net to native C code does not work, any ideas
extern "C" {
TRADITIONALDLL_API int TestStrRef( __inout char* c) {
int rc = strlen(c);
std::cout << "the input to TestStrRef is: >>" << c << "<<" ;
c = "This is from the C code ";
return rc;
}
}
[DllImport("MyDll.dll", SetLastError = true)]
static extern int TestStrRef([MarshalAs(UnmanagedType.LPStr)] ref string s);
String abc = "InOut string";
TestStrRef(ref abc);
At this point Console.WriteLine(abc) should print "This is from the C code " but doesn't, Any ideas on what's wrong ?
FYI - i have another test function not using ref type string, it works just fine
Your code wrong at C side also. __inout annotation just tell compiler you can change buffer to which "c" argument pointed. But pointer itself located in stack and does not return to caller if you modified "c" argument.
Your declaration may look like:
extern "C" {
TRADITIONALDLL_API int TestStrRef( __inout char** c) {
int rc = strlen(*c);
std::cout << "the input to TestStrRef is: >>" << *c << "<<" ;
*c = "This is from the C code ";
return rc;
}
}
And C# side:
[DllImport("MyDll.dll", SetLastError = true)]
static extern int TestStrRef(ref IntPtr c);
{
String abc = "InOut string";
IntPtr ptrOrig = Marshal.StringToHGlobalAnsi(abc)
IntPtr ptr = ptrOrig; // Because IntPtr is structure, ptr contains copy of ptrOrig
int len = TestStrRef(ref ptr);
Marshal.FreeHGlobal(ptrOrig); // You need to free memory located to abc' native copy
string newAbc = Marshal.PtrToStringAnsi(ptr);
// You cannot free memory pointed by ptr, because it pointed to literal string located in dll code.
}
Does this work for you? Basically just add CallingConvention = CallingConvention.Cdecl to the DllImport statement. You might also want to specify the CharSet (for example: CharSet:=CharSet.Unicode)
[DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
static extern int TestStrRef([MarshalAs(UnmanagedType.LPStr)] ref string s);

Categories