I need to call a 'classic' DLL C# (not COM) from Powerbuilder.
The creation of the DLL in C# I based on this example:
RGiesecke dll Export template.
And I managed to call the DLL from within Powerbuilder.
BUT I want to pass the string 'as reference': so I added 'ref' to the function declaration:
[DllExport("ExpTest", CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static string ExpTest([MarshalAs(UnmanagedType.LPWStr)] ref string sText, out int length)
{
MessageBox.Show(sText, "ExpTest");
length = sText.Length;
//sText = "def";
return sText;
}
The code from Powerbuilder calling this function:
String ls_arg, ls_ret
ls_arg = "abc"
long ll_len
ls_ret = ExpTest(ls_arg, ll_len)
messagebox(ls_arg, ls_ret)
When calling the original functio (without 'ref' to the 'string sTest' declaration), it returns "abc".
When I add 'ref' to the 'string sTest' declaration, it returns some 'chinese characters').
Can anyone help?
Even better: how to pass an array of strings (by ref) from and to Powerbuilder?
Thanks for your help!!
Msc.
Tried to define the External functions in Powerbuilder like this:
- FUNCTION String ExpTest(REF String value, REF long len) LIBRARY "Classicdll.dll"
- FUNCTION String ExpTest(String value, REF long len) LIBRARY "Classicdll.dll"
- and both with ALIAS FOR "ExpTest;Ansi"...
Do not use ref. You have to use ref only if you want to change what the object is not what it contains!
Strings in .NET are special objects. They're acting like a value type but they're a reference type.
More information can be found here:
https://www.codeproject.com/articles/6852/strings-in-net
http://csharpindepth.com/Articles/General/Strings.aspx
I think your chinese characters are the reference, as a string, to the String object and not a String which contains chinese characters.
EDIT:
It looks like that using unmanged exports is a ugly hack.
So creating a C# COM *.dll would be the better (supported) way of writing extensions for PowerBuilder.
Related
I hope somebody can explain what exactly the difference is:
In my C# Programm I want to pass an String to an C++ Method.
My Code in C# looks like this:
[DllImport("Test.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern String DoIt(String filename);
The inside of my C++ DLL looks like this:
__declspec(dllexport) CString DoIt(char* szFilename)
{
....
}
If I pass the String (like: C:\temp\my.txt) it becomes malformed =>"Ôœ&°8é-".
Now comes the confusings part I can't literly understand. If I change the return Type from CString to char* everything is fine.
__declspec( dllexport ) char* DoIt(char* filename)
Why is that so? The CharSet in C# is already set to Ansi to Marshal the String into the right Type. I cannot figure out where the connection between the return Type and my passing String is.
If you need more information just let me know.
Both versions of your code are wrong. You certainly can't pass CString as an interop type. You to use simple types for interop, not C++ classes.
The error when you use char* is more subtle. In that scenario there are two problems.
Firstly, with a string return type on the C# side, the pinvoke marshaller assumes that the returned string was allocated with CoTaskMemAlloc and will call CoTaskMemFree to deallocate it once it has been copied.
Secondly, although we can't see it, your C++ code almost certainly returns a pointer to a buffer owned by a local variable in the C++ function. Obviously this local variable goes out of scope when the function returns and so the pointer becomes invalid.
Some options:
Have the caller locate the string buffer and let the callee populate it.
Allocate the returned char* using CoTaskMemAlloc and so meet the expectations of the C# code.
Use the COM BSTR type which the pinvoke marshaller does understand.
I have a COM component which is written in C++ which I want to call its interface in my C# application through interop.
The interface which I want to call is defined like this:
C# interop interface definition:
void ICertainInterface.Func(int cnt, ref string colors)
C++ definition:
ICertainInterface : IUnknown
{
virtual HRESULT Func(long cnt, BSTR * colors) = 0;
}
This is clear to me that, the interface is expected a BSTR array with specific lenth from my application. The 2nd parameter BSTR * colors suppose to stand for the 1st string address in my string array.
Now this is the code I used to call up the interface from my C# application:
ICertainInterface obj = GetInterface();
string[] strArray = new string[4];
strArray[0] = "aaa";
strArray[1] = "bbb";
strArray[2] = "ccc";
strArray[3] = "ddd";
obj.Func(4, ref strArray[0]);
Once I run the application, error given "Attempted to read or write protected memory". This won't happen if the array size is only 1.
It seems to me that, because the string array is managed data in C#. So the memory allocation is not predictable, Once I pass strArray[0] as a reference to C++ interface, the interface will take it as the starting address of an array and assume add by 4 will get the address for next element but which could not be go in the same way in C# memory allocation.
I searched some post from online, seems most interfaces user "ref string[]" as the pointer to an C# array but not like this "ref string" which I can only refer to the 1st element in the array (I even don't know if it is the right approach, because in C++ the 1st element address is equivalent to the array address.)
Further more, I did a same test in excel VBA code which called the same interface:
Dim msColor(4) As String
msColor(0) = "aaa"
msColor(1) = "bbb"
msColor(2) = "ccc"
msColor(3) = "ddd"
Dim obj as ICertainInterface
Set obj = GetInterface();
Call obj.Func(4, msColor(0))
This VBA code works perfect without any error.
Now I totally have no idea how to fix this in my C# application. Can someone point me a way? I would very much appreciate.
Shouldn't the method signature be
void ICertainInterface.Func(int cnt, ref string[] colors)
...and instead of passing the first element in the array, pass the entire array?
Arrays in C# don't behave like arrays in C/C++; you can't just pass a reference to the first element and expect to be able to access the rest of the elements using pointer arithmetic.
Edit: You probably don't need the ref keyword in there, either.
You may need to marshal to appropriate type in order to pass between CLR and non CLR processes
Here you go with the correct signature
so if you are trying to pass a single string then
void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.BStr)] ref string colors)
or if it is kind of array then
void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_BSTR)] ref string[] colors)
I made this solution based on your code. If above solution does not work for you then you may try finding the appropriate array type/subtype suitable for your application here http://msdn.microsoft.com/en-us/library/z6cfh6e6(v=vs.110).aspx and for string types here http://msdn.microsoft.com/en-us/library/s9ts558h(v=vs.110).aspx
using DllImport attribute example
if you are trying to pass a single string then
[DllImport("yourLib.dll", EntryPoint = "Func")]
public static extern void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.BStr)] ref string colors)
if it is kind of array then
[DllImport("yourLib.dll", EntryPoint = "Func")]
public static extern void ICertainInterface.Func(int cnt,[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VT_BSTR)] ref string[] colors)
This may help you achieve the same, Dll import is usually used to call win api
I am trying to call a method in a Delphi DLL with the following signature:
function SMap4Ovr(const OverFileName : ShortString ;
const Aclay : Integer ;
const Acarbon : Double ;
out errstr : ShortString): WordBool;
I am using the following import in C#:
[DllImport("SMap.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern bool SMap4Ovr(
string OverFileName,
int Aclay,
double Acarbon,
out string errstr
);
But I am getting a AccessViolationException.
I seem to be able to call into a couple of simpler methods in the DLL that have string parameters but not ints or doubles.
I have also tried with the CallingConvention = CallingConvention.Cdecl but this gives me the same error.
When writing interop code it is critical that both sides of the interface match in every way. Here are the main issues that you must make agree on both sides:
Calling conventions.
Parameters lists.
Parameter types and semantics.
The first observation is that your calling conventions do not match. You have register on the Delphi side and stdcall on the C# side. The Delphi register convention is private to Delphi and so you should use stdcall.
Secondly, your string parameter types do not match. The Delphi shortstring is a data type that became legacy when Delphi 2 was released and should be considered a relic from the previous century. It was never a valid interop type, and there's nothing in the p/invoke framework that can be used to match it. Whilst you could attempt to do the marshalling by hand, this is a lot of work that is simply not needed when there are simple solutions available. You should try to forget all about shortstring.
You need to use a string type that both sides of the interface can work with. You could use null-terminated C strings, but a better and simpler choice is the COM BSTR which is WideString in Delphi.
So, the final result is as follows.
Delphi
function SMap4Ovr(
OverFileName: WideString;
Aclay: Integer;
Acarbon: Double;
out errstr: WideString
): WordBool; stdcall;
C#
[DllImport("SMap.dll")]
public static extern bool SMap4Ovr(
[MarshalAs(UnmanagedType.BStr)]
string OverFileName,
int Aclay,
double Acarbon,
[MarshalAs(UnmanagedType.BStr)]
out string errstr
);
I did not bother specifying the calling convention on the DllImport since the default is stdcall. If you prefer you can be explicit about this.
Be careful when using WideString that you don't attempt to use it as a return value. Because Delphi uses non-standard semantics for return values, you can only use simple types that fit into a register as return values.
Default calling convention in Delphi is register, not stdcall. It seems calling conventions details show us that Microsoft fastcall is not the same as Borland fastcall (register)
And C# string type differs from Delphi ShortString (it contains internally one byte length + string body)
I have this signature in a delphi 2007 function I'm calling (the SomeOtherFile is another DLL that it in turn is calling):
function MyFunction(Place, Name: PChar):_Recordset; stdcall; far; external 'SomeOtherFile.DLL';
I'm trying to call it from C# code like this:
[DllImport("MyFile.dll", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi, EntryPoint="MyFunction")]
public static extern DataSet MyFunction(string Place, [MarshalAs(UnmanagedType.LPStr)]string Name);
Whenever I run this and store it into a variable, I get a runtime error about type mismatches. I guess I'm reading the signature wrong, but I can't figure out what it should be.
edit
The actual error is: A call to PInvoke function [...] has unbalanced the stack...I've also tried both params using the MarshalAs attribute, and it throws the same thing.
I've done a bit of digging around and I think you need to marshal the return value as a Recordset interface. I'm sure the P/Invoke marshaller won't magically convert your Delphi _Recordset into a .net DataSet class instance.
So I think you can write it something like this:
[DllImport("MyFile.dll")]
[return: MarshalAs(UnmanagedType.Interface)]
public static extern object MyFunction(string Place, string Name);
Call it like this
Recordset rs = (Recordset) MyFunction(Place, Name);
I'm assuming that the Place and Name parameters are input parameters, in which case the default marshalling for string is just fine.
You don't need to specify ANSI character set because that is the default too. You don't need to name the entry point if it has the same name as the C# function. You don't need to specify the calling convention because stdcall is the default.
The Recordset interface resides in the ADODB namespace.
As an aside, the use of far in your Delphi function import is spurious. The far keyword stopped having any effect once we left the 16 bit world behind.
I need to use an unmanaged COM dll in c# program. Dll contains a function, say:
Open(char *name);
But when imported to c# (Project->Add Reference) it is available as:
mydll.Open(ref byte name)
How can I pass a string to this function?
When I do:
byte[] name = new byte[32];
mydll.Open(ref name);
I get compilation error "Cannot convert ref byte[] to ref byte".
If you mean for it to be a string, then in your IDL file, you have to specify that this point represents a string. See this article for information on the [string] attribute:
http://msdn.microsoft.com/en-us/library/d9a4wd1h%28v=VS.80%29.aspx
If you want to be CLS compliant (and interoperate with scripting languages, you might want to look into using BSTR instead of char* for passing strings). This way you'll get unicode support too.
Unless you give COM the hint that this is a string, you will have problems whenever COM has to marshal the parameters (i.e. across apartment or process boundaries).
This article may also give you a good starting point on C++ / C# / COM goodies:
COM Interop Part 1: C# Client Tutorial
Maybe you can do this...
byte[] bytes = Encoding.ASCII.GetBytes(myString);
You might try decorating that "name" variable with:
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPStr)]
That's a single byte, and I think is compatibel with a single char. If not, the answer is likely going to be using MarshalAs to make that variable look like type.
Keep in mind you could lose it if the array is not properly terminated. Anyhow, I would try passing in the pointer to the first element byte[0] try:
mydll.Open(ref name[0]);
I'm not sure how the interop will marshal this but it's worth a try.
The import is not correct. You can import it manually:
[DllImport("<Your COM Dll>")]
private static extern <Return Type> <"Function Name">();
Then, in your main method, or in the method where you initialize your dll object, you need:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr LoadLibrary(string lpFileName);
public MyDll()
{
Environment.CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string dllPath = Environment.CurrentDirectory + #"<Location of Dll you are importing from>";
LoadLibrary(dllPath);
}
For example, check out the following COM Dll:
GOIO_DLL_INTERFACE_DECL gtype_int32 GoIO_GetNthAvailableDeviceName(
char *pBuf,
gtype_int32 bufSize,
gtype_int32 vendorId,
gtype_int32 productId,
gtype_int32 N);
I imported this Dll as the following:
[DllImport("GoIO_DLL.dll")]
private static extern int GoIO_GetNthAvailableDeviceName(
byte[] pBuf,
int bufSize,
int vendorId,
int productId,
int N);
As you can see, the char pointer becomes a byte[], just like you tried. There is no need for the ref keyword.