I have written a Banking System DLL in C++ with a C Interface with the following function that is used to rename a customer:
int renameCustomer(const string& firstname_, const string& name_, const unsigned int customerId_);
Now I want to use the function in a .NET Assembly. I tried to import the function from the dll this way
[DllImport("BankManagement.dll", EntryPoint = "renameCustomer", CallingConvention = CallingConvention.Cdecl)]
public static extern int renameCustomer(ref string firstname_, ref string name_, uint customerId_);
and I would like to use it in the following function:
public int changeCustomerName(uint id_, string firstname_, string name_)
{
return renameCustomer(ref firstname_, ref name_, id_);
}
In a test application I call the function this way:
BankManageIntern.BankIntern myBankIntern = new BankManageIntern.BankIntern();
int checkCust = myBankIntern.changeCustomerName(0, "Name", "Firstname");
My Logger shows me that the customer could not be renamed because the input for name is empty. Now I believe that I made a mistake passing the strings to the functions. I have already tried all kinds of ways to pass the string, which I found on Google, but nothing works. Does anybody of you have an idea? The customer exists and the customer id is valid. I'm sure that's not the problem.
One condition is that I may not change the Function of the DLL. I can only make changes in the .NET Assembly and the Application.
try the following:
instead of trying to pass an string, use an pointer to an char. this can be done by using an IntPtr.
private static extern int renameCustomer( IntPtr firstname, IntPtr lastname, uint id);
in your Application you can use the var datatype. It's similar to the auto_ptr in C++.
var firstnamePtr = Marshal.StringToHGlobalAnsi(firstname);
var lastnamePtr = Marshal.StringToHGlobalAnsi(lastname);
and call with that your function from the DLL.
var status = renameCustomer(firstnamePtr, lastnamePtr, id);
Hope that will help you with your problem! :)
Related
I am writing a piece of program that generates Offline Domain Join blob and saves it for future use. This action can be done using command prompt. Below is a sample command that will generate the mentioned file and save it on D drive:
D:\djoin.exe /REUSE /PROVISION /DOMAIN MyDomain.MyCompany.com /MACHINE "user1-pc" /SAVEFILE blob.txt
More information: Offline Domain Join (Djoin.exe) Step-by-Step Guide
Now, I want to add a method to my program (written with C#) to does this functionality for me.
One of the problems here is, the API that Microsoft has provided is a C++ API. I have tried to use the API in managed code using PInvoke. Below is the code I have written.
using System;
using System.Runtime.InteropServices;
namespace TestBlob
{
public class Program
{
static void Main(string[] args)
{
String domain = "MyDomain.MyCompany.com";
String machineName = "user1-pc";
String machineAccoutOU = null;
String dcName = "MyDomain";
uint options = 1;
IntPtr provisionBinData = IntPtr.Zero;
IntPtr provisionBinDataSize = IntPtr.Zero;
string blob = string.Empty;
IntPtr pProvisionTextData = Marshal.StringToHGlobalUni(blob);
uint status = ODJNativeMethods.NetProvisionComputerAccount(domain, machineName, machineAccoutOU, dcName, options, ref provisionBinData, ref provisionBinDataSize, ref pProvisionTextData);
Console.WriteLine(status);
Console.WriteLine(Marshal.PtrToStringUni(pProvisionTextData));
Console.Read();
}
}
public static class NativeMethods
{
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern uint NetProvisionComputerAccount([In] String lpDomain,
[In] String lpMachineName,
[In] String lpMachineAccountOU,
[In] String lpDcName,
[In] uint dwOptions,
[In] [Out] ref IntPtr pProvisionBinData,
[In] [Out] ref IntPtr pdwProvisionBinDataSize,
[In] [Out] ref IntPtr pProvisionTextData);
}
}
When I run the application, it always returns 87 (shows on console), which after a quick search turns out to be an error message: The parameter is invalid.
What am I doing wrong here? Are my PInvoke types not the correct ones corresponding to native language API?
The 3 last parameters are declared out, which means you must not initialize them, but pass correct pointer so the function can allocate things for you.
Also, from what I understand reading the function doc, the binary one and the string one are mutually exclusive, so let's say you want to get back the binary one, then you can define the API like this ([in] are usually implicit):
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern int NetProvisionComputerAccount(
string lpDomain,
string lpMachineName,
string lpMachineAccountOU,
string lpDcName,
int dwOptions,
out IntPtr pProvisionBinData,
out int pdwProvisionBinDataSize,
IntPtr pProvisionTextData);
Note the function does not use SetLastError, so don't declare it in the declaration.
And here is how to call it:
string domain = "MyDomain.MyCompany.com";
string machineName = "user1-pc";
string machineAccoutOU = null;
string dcName = "MyDomain";
// I suggest you don't use hardcoded values to be nice with readers
const int NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 1;
int status = NetProvisionComputerAccount(
domain,
machineName,
machineAccoutOU,
dcName,
NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT,
out IntPtr binData, // let the system allocate the binary thing
out int binDataSize, // this will be the size of the binary thing
IntPtr.Zero); // we don't use this one here, pass null
I can't test further (I get error 1354 which I suppose is normal in my context).
Note the doc doesn't say anything about deallocating what the function allocates (if it allocates something? there are some rare Windows API that use static buffers they own). I think you're supposed to call NetApiBufferFree on binData once all work is done, but it's just a guess.
It could be the encoding of your text file - if you have saved it as ANSI and consuming it as Unicode, it might not work.
Use these statements corresponding to your above code:
IntPtr pProvisionTextData = Marshal.StringToHGlobalAnsi(blob);
and
[DllImport("user32", CharSet=CharSet::Ansi)]
Hope it helps.
A (non-COM) Delphi dll has a function being exported:
function GetQuestions(Digit1, Digit2: string; CountryISO: string):string;
I have added this dll as an existing item in Visual Studio 2012 and have set its Build Action to None, Copy to Output Directory to Copy Always.
The class containing the DllImportAttribute:
public class RefundLibrary
{
[DllImport("RefundLibrary.dll", EntryPoint = "GetQuestions",
CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetQuestions(string digit1, string digit2,
string countryISO);
}
When I call this method in the Page_Load of a WebForm (not sure if relevant), it throws a PInvokeStackImbalance for a possible signature mismatch on the below indicated line:
protected void Page_Load(object sender, EventArgs e)
{
IntPtr str = RefundLibrary.GetQuestions("", "", "BE"); //<- here
string result = Marshal.PtrToStringUni(str);
testp.InnerText = result;
}
I also tried to change the DllImport method's return type to string, the error is identical.
I figure the Marshal.PtrToStringUni(str) is correct as far as the Embarcadero docs go?
In RAD Studio, string is an alias for UnicodeString
Is this really a signature mismatch? What am I missing (except, obviously, a decent understanding of P/Invoke)?
You cannot call that function. It uses the Delphi only register calling convention, and uses Delphi strings. Change it to:
procedure GetQuestions(
Digit1: WideString;
Digit2: WideString;
CountryISO: WideString;
out Questions: WideString
); stdcall;
On the C# side:
[DllImport("RefundLibrary.dll")]
public static extern void GetQuestions(
[MarshalAs(UnmanagedType.BStr)]
string digit1,
[MarshalAs(UnmanagedType.BStr)]
string digit2,
[MarshalAs(UnmanagedType.BStr)]
string countryISO,
[MarshalAs(UnmanagedType.BStr)]
out string questions
);
The use of WideString/BStr is great for the out parameter. Because the content is allocated on the shared COM heap which means that the caller can deallocate it.
You could use PWideChar/LPWStr for the input parameters. That would work fine. I used WideString because I wanted to be consistent. It's up to you.
I have written a DLL in C++. One of the functions writes to a character array.
C++ Function
EXPORT int xmain(int argc, char argv[], char argv2[])
{
char pTypeName[4096];
...
//Other pTypeName ends up populated with "Portable Network Graphics"
//This code verifies that pTypeName is populated with what I think it is:
char szBuff[64];
sprintf(szBuff, pTypeName, 0);
MessageBoxA(NULL, szBuff, szBuff, MB_OK);
//The caption and title are "Portable Network Graphics"
...
//Here, I attempt to copy the value in pTypeName to parameter 3.
sprintf(argv2, szBuff, 0);
return ret;
}
C# Import
//I believe I have to use CharSet.Ansi because by the C++ code uses char[],
[DllImport("FirstDll.dll", CharSet=CharSet.Ansi)]
public static extern int xmain(int argc, string argv, ref string zzz);
C# Function
private void button2_Click(object sender, EventArgs e)
{
string zzz = "";
int xxx = xmain(2, #"C:\hhh.bmp", ref zzz);
MessageBox.Show(zzz);
//The message box displays
//MessageBox.Show displays "IstuÈst¼ÓstÄstlÄstwÄstiÑstõÖstwÍst\
// aÖst[ÖstÃÏst¯ÄstÐstòÄstŽÐstÅstpÅstOleMainThreadWndClass"
}
I have attempted to pass a parameter from C# by reference and have the C++ DLL populate the parameter. Even though I have verified that the value is correct in the DLL, gibberish gets passed to the C# application.
What can I do to write the correct string value to the C# string?
Use a StringBuilder to pass a character array that native code can fill in (see Fixed-Length String Buffers).
Declare the function:
[DllImport("FirstDll.dll", CharSet=CharSet.Ansi)]
public static extern int xmain(int argc, string argv, StringBuilder argv2);
Use it:
// allocate a StringBuilder with enough space; if it is too small,
// the native code will corrupt memory
StringBuilder sb = new StringBuilder(4096);
xmain(2, #"C:\hhh.bmp", sb);
string argv2 = sb.ToString();
Give some other information to the DLLImport call. Look at the following example of my own:
[DllImport("tcpipNexIbnk.dll", EntryPoint = "SendData", CallingConvention = CallingConvention.Cdecl)]
public static extern int Send([MarshalAs(UnmanagedType.LPWStr)]string message);
Notice two things, the CallingConvention parameter:
CallingConvention = CallingConvention.Cdecl)
Use that as it is.
And then just behind the c# string type, you can play with the different Unmanaged types using the MarshalAS instruction, that will cast your C# string parameter to the native string type you have in your c++ program:
public static extern int Send([MarshalAs(UnmanagedType.LPWStr)]string message);
Hope it helps.
Function description here.
I'm struggling to get it right to call this function from c#.
I'm at a stage where I'm calling it but it's returning E_INVALIDARG.
I've set it up as follows...
[DllImport("p2p.dll", CharSet=CharSet.Unicode)]
internal static extern uint PeerGroupCreateInvitation(IntPtr hGroup, string pwzIdentityInfo, IntPtr pftExpiration, int cRoles, IntPtr pRoles, out string ppwzInvitation);
My best guess is the 5th parameter, "pRoles".
I'm supposed to send it a pointer to one or two GUIDs representing the role type.
PEER_GROUP_ROLE_ADMIN
PEER_GROUP_ROLE_MEMBER
I have no clue presently how to do this from c#.
In C this parameter looks like this when calling the function...
..., (PEER_ROLE_ID*) &PEER_GROUP_ROLE_MEMBER, ...
PEER_ROLE_ID looks like a System.Guid type.
PEER_GROUP_ROLE_MEMBER looks like the actual GUID. (Can I get this from the p2p.dll file?)
Any help would be greatly appreciated... especially since there's close to ZERO info on this function on the internet.
Working solution after everyone's comments.
Declaration:
[DllImport("p2p.dll")]
public static extern uint PeerGroupCreateInvitation(IntPtr hGroup, [MarshalAs(UnmanagedType.BStr)] string pwzIdentityInfo, int pftExpiration, int cRoles, ref Guid pRoles, out IntPtr ppwzInvitation);
Calling:
uint hr = PeerGroupCreateInvitation(hGroup, identityInfo, 0, 1, ref PEER_GROUP_ROLE_MEMBER, out pInvitation);
...where PEER_GROUP_ROLE_MEMBER is the System.Guid for this role.
Getting the invitation:
string invitation = Marshal.PtrToStringAuto(pInvitation);
This is the correct declaration:
[DllImport("p2p.dll")]
public static extern uint PeerGroupCreateInvitation(
IntPtr hGroup, /* Updated with #RedDude's suggestion */
[MarshalAs(UnmanagedType.BStr)] string pwzIdentityInfo,
int pftExpiration, // 32 bit, not 64 bit
int cRoles,
ref Guid pRoles,
out IntPtr ppwzInvitation);
As #strenr has said you should use a ref Guid argument to pass the GUID for the pRoles. However, and you might have already decided against this, have you taken a look at the WCF peer-to-peer support? This would give you most of the peer-to-peer capabilities already wrapped up in a .NET interface?
Take a look here
http://msdn.microsoft.com/en-us/library/system.net.peertopeer.aspx
I have two communicating components - one managed, the other unmanaged. The managed needs to retrieve a character string from the unmanaged implementation (the same string or just a copy). I tried the following code.
// Unmanaged code
const char* GetTestName(Test* test)
{
return test->getName();
}
// Managed wrapper
[DllImport(DllName, EntryPoint = "GetTestName")]
public static extern IntPtr GetTestName(IntPtr testObj);
// API Invocation
IntPtr testName = GetTestName(test);
string testStr = Marshal.PtrToStringAuto(testName);
But, the value of testStr is not what is expected. Does anyone know what I'm doing wrong here? Any suggestions would be really helpful.
You're close but you have to use PtrToStringAnsi(). Auto uses the system default which will be Unicode.
I'd suggest this, instead:
[DllImport(DllName, EntryPoint = "EntryPoint")]
[MarshalAs(UnmanagedType.LPStr)]
public static extern StringBuilder GetTestName(IntPtr testObj);
UnmanagedType.LPStr works with strings and System.Text.StringBuilder, and perhaps others (I only ever used those two). I've found StringBuilder to work more consistantly, though.
See this MSDN article for further information on the various string marshalling options.