I need to use a DLL (Hardware ID Extractor) made in Delphi 7 in my C# application.
The functions exported by this DLL are:
Exported functions:
// CPU
function GetCPUSpeed: Double;
function CPUFamily: ShortString; { Get cpu identifier from the windows registry }
function GetCpuTheoreticSpeed: Integer; { Get cpu speed (in MHz) }
function IsCPUIDAvailable: Boolean; Register;
function GetCPUID (CpuCore: byte): ShortString;
Function GetCPUVendor: ShortString;
// RAM
function MemoryStatus (MemType: Integer): cardinal; { in Bytes }
function MemoryStatus_MB (MemType: Integer): ShortString; { in MB }
// HDD
function GetPartitionID (Partition : PChar): ShortString; { Get the ID of the specified patition. Example of parameter: 'C:' }
function GetIDESerialNumber(DriveNumber: Byte ): PChar; { DriveNr is from 0 to 4 }
I know (obviously) that string in Delphi are not null terminated and are byte (ASCII). But I have no clue on how to map these Delphi string to C#.
Thanks.
Here's an example of how you could declare the GetCPUSpeed function in C#:
class Program
{
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern double GetCPUSpeed();
static void Main(string[] args)
{
double cpuSpeed = GetCPUSpeed();
Console.WriteLine("CPU speed: {0}", cpuSpeed);
}
}
And there are the other declarations you could try:
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string CPUFamily();
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern int GetCpuTheoreticSpeed();
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern bool IsCPUIDAvailable();
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetCPUID(byte cpuCore);
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetCPUVendor();
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern uint MemoryStatus(int memType);
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string MemoryStatus_MB(int memType);
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetPartitionID(char partition);
[DllImport("the_name_of_the_delphi_library.dll")]
public static extern string GetIDESerialNumber(byte driveNumber);
The problem comes from the way you designed your exported functions. DLLs are (among other things) a mechanism to provide code in a way that it can be used from applications (or other DLLs) written in different programming languages.
Have a look at the Windows API, there are lots of functions returning text. Unfortunately this is done in a lot of different ways, the Windows API has no real standard for it. However, I'm quite sure that you will not find a single function that returns a "string" (either a Pascal or a C (null-terminated) string) in the way you did. The reason is simple: This would make it very difficult or even impossible to use different programming languages for different modules. Your DLL would allocate the memory for the string, and once the caller is done with the string it would need to free the memory. Thus both modules need to share the manager for this slice of memory. How could the C# program use your DLL, seeing that they use completely different memory managers?
The customary way to get a string value from a DLL is to call the function with a preallocated buffer and the buffer length in parameters, let the function fill that buffer with the string contents, and let the function result denote success or failure. Passing a buffer of insufficient size for example would be one cause of failure. Different Windows API functions deal with this in different ways, the easiest one for you would probably to define your maximum string length with a constant, similar to MAX_PATH for example.
To solve your problem you should take one Windows API function that you know how to call from C#, and which returns a string result in a buffer, as the example. Model your exported functions after this example, and it should be easy to call them from the C# program.
Edit:
I remembered that I'd seen a similar question (How to import a function from a DLL made in Delphi?) some time ago, and now that I looked I found it to be yours as well.
You wrote then that you don't have the source code for that DLL, so changing the exported functions is out of the question. What you could do is create a wrapper DLL (or COM object) in Delphi, call the original HardwareIDExtractor.dll and provide a sane API on top of it. That would allow you to provide both AnsiChar and WideChar versions of the exported functions at the same time.
If you want to reduce the clutter (two DLLs necessary instead of one) you could also put the original HardwareIDExtractor.dll as a resource into your wrapper DLL, as described in this blog posting: Using DLLs stored as Resources in Delphi programs
If you have the source of the D7 dll, you can change the exported function to make the
ShortString function to return PChar. You can create new functions that call the original ones and do the typecast - that's the easiest path. If you follow this path do the same to the Integer and Cardinal types (cast them to LongInt and LongWord, respectively).
Some code below (I imagine that mages like mr Hausladen have a more elegant approach, but this works :-) )
var
SInput: ShortString;
POutput: PAnsiChar;
Tam: Integer;
pt: Pointer;
begin
SInput := 'Alphabet'; //ShortString
pt := #SInput[1]; // Points to the first char of the string;
Tam := (Length(SInput))+1; // Size the string
POutput := StrAlloc(tam); // Allocate;
CopyMemory(POutput,pt,tam); // Do the copy
POutput[tam-1] := #0; // Put the null to finish
MessageBox(0, POutput, 'Algo', MB_ICONWARNING or MB_OK);
StrDispose(POutput);
end;
This works because internally ShortString is a array [0..255] of Char. I don't have an D2009 at hand to see if the SizeOf(char) must be changed to SizeOf(AnsiChar). BUT the principle is the same: allocate the PAnsiChar, get the Pointer to the first the char of the ShortString and do Copy (in this case I've done with CopyMemory) to the PAnsiChar. And put the null in its' place.
Or you can go the mghie's way and create a wrapper and do the casts there.
Related
I am trying to call a DLL written in Rust from a C# program. The DLL has two simple functions that take stings (in different manner) and prints to the console.
Rust DLL code
#![crate_type = "lib"]
extern crate libc;
use libc::{c_char};
use std::ffi::CStr;
#[no_mangle]
pub extern fn printc(s: *const c_char){
let c_str : &CStr = unsafe {
assert!(!s.is_null());
CStr::from_ptr(s)
};
println!("{:?}", c_str.to_bytes().len()); //prints "1" if unicode
let r_str = std::str::from_utf8(c_str.to_bytes()).unwrap();
println!("{:?}", r_str);
}
#[no_mangle]
pub extern fn print2(string: String) {
println!("{:?}", string)
}
C# console program code
[DllImport("lib.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
static extern void print2(ref string str);
[DllImport("lib.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void printc(string str);
static void Main(string[] args)
{
try
{
var graw = "yeyeye";
printc(graw);
print2(ref graw);
}
catch (Exception ex)
{
Console.WriteLine("calamity!, {0}", ex.Message);
}
Console.ReadLine();
}
For the print2 function it keep printing garbage on screen until it causes AccessViolationException
The 2nd printc function does print the string, but only if CharSet.Unicode is not set. If it is set, it will only print the first char, hence the println!("{:?}", c_str.to_bytes().len()); will print 1.
I believe that Cstr::from_ptr function does not support Unicode, that is why it returns only the first char of the string.
Any idea how to pass Unicode string as parameters to Rust DLLs? Is it possible to make things simpler like in print2 function?
If you check the documentation on CharSet, you'll see that CharSet.Unicode tells .NET to marshal strings as UTF-16 (i.e. two bytes per code point). Thus, .NET is trying to pass printc what should be a *const u16, not a *const libc::c_char. When CStr goes to compute the length of the string, what it sees is the following:
b"y\0e\0y\0e\0y\0e\0"
That is, it sees one code unit, then a null byte, so it stops; hence why it says the length is "1".
Rust has no standard support for UTF-16 strings, but if you're working on Windows, there are some conversion methods: search the docs for OsStrExt and OsStringExt. Note that you must use the docs that installed with the compiler; the ones online won't include it.
Sadly, there's nothing for dealing directly with null-terminated UTF-16 strings. You'll need to write some unsafe code to turn a *const u16 into a &[u16] that you can pass to OsStringExt::from_wide.
Now, Rust does use Unicode, but it uses UTF-8. Sadly, there is no direct way to get .NET to marshal a string as UTF-8. Using any other encoding would appear to lose information, so you either have to explicitly deal with UTF-16 on the Rust side, or explicitly deal with UTF-8 on the C# side.
It's much simpler to re-encode the string as UTF-8 in C#. You can exploit the fact that .NET will marshal an array as a raw pointer to the first element (just like C) and pass a null-terminated UTF-8 string.
First, a static method for taking a .NET string and producing a UTF-8 string stored in a byte array:
byte[] NullTerminatedUTF8bytes(string str)
{
return Encoding.GetBytes(str + "\0");
}
Then declare the signature of the Rust function like this:
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
static extern void printc([In] byte[] str);
Finally, call it like this:
printc(NullTerminatedUTF8bytes(str));
For bonus points, you can rework printc to instead take a *const u8 and a u32, passing the re-encoded string plus it's length; then you don't need the null terminator and can reconstruct the string using the std::slice::from_raw_parts function (but that's starting to go beyond the original question).
As for print2, that one is just unworkable. .NET knows nothing about Rust's String type, and it is in no way compatible with .NET strings. More than that, String doesn't even have a guaranteed layout, so binding to it safely is more or less not possible.
All that is a very long-winded way of saying: don't use String, or any other non-FFI-safe type, in cross-language functions, ever. If your intention here was to pass an "owned" string into Rust... I don't know if it's even possible to do in concert with .NET.
Aside: "FFI-safe" in Rust essentially boils down to: is either a built-in fixed-size type (i.e. not usize/isize), or is a user-defined type with #[repr(C)] attached to it. Sadly, the "FFI-safe"-ness of a type isn't included in the documentation.
I've been learning about marshaling Unmanaged DLL imports into C# ... And I've come across something I don't quite understand.
In Delphi, there is a function that is returning Result := NewStr(PChar(somestring)) from a Procedure SomeFunc() : PChar; Stdcall;
From my understanding, NewStr just allocates a buffer on the local heap ... and SomeFunc is returning a pointer to it.
In .NET 4.0 (Client Profile), via C# I can use :
[DllImport("SomeDelphi.dll", EntryPoint = "SomeFunc", CallingConvention = CallingConvention.StdCall)]
public static extern String SomeFunc(uint ObjID);
This works (or as David says, "appears to work") fine in Windows 7 .NET 4.0 Client Profile. In Windows 8, it has unpredictable behavior, which brought me down this path.
So I decided to try the same code in .NET 4.5 and got Heap corruption errors. Okay, so now I know this is not the correct way to do things. So I dig further :
Still in .NET 4.5
[DllImport("SomeDelphi.dll", EntryPoint = "SomeFunc", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr _SomeFunc();
public static String SomeFunc()
{
IntPtr pstr = _SomeFunc();
return Marshal.PtrToStringAnsi(pstr);
}
This works without a hitch. My (novice) concern is that NewStr() has allocated this memory and it's just sitting there forever. Is my concern not valid?
In .NET 4.0, I can even do this and it never throws an exception :
[DllImport("SomeDelphi.dll", EntryPoint = "SomeFunc", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr _SomeFunc();
public static String SomeFunc()
{
String str;
IntPtr pstr = _SomeFunc();
str = Marshal.PtrToStringAnsi(pstr);
Marshal.FreeCoTaskMem(pstr);
return str;
}
This code throws the same heap exception in 4.5, however. This leads me to believe the problem lies in the fact that in .Net 4.5, the marshaler is trying to FreeCoTaskMem() and that is what is throwing the exceptions.
So questions :
Why does this work in .Net 4.0 and not 4.5?
Should I be concerned about NewStr()'s allocation in the native DLL?
If answer "No" to #2, then the second code example is valid?
The key information, which is very hard to find in the docs, concerns what the marshaller does with a p/invoke function with a return value of type string. The return value is mapped to a null-terminated character array, i.e. LPCTSTR in Win32 terms. So far so good.
But the marshaller also knows that the string must have been allocated on a heap somewhere. And it cannot expect the native code to deallocate it since the native function has finished. So the marshaller deallocates it. And it also assumes that the shared heap that was used was the COM heap. So the marshaller calls CoTaskMemFree on the pointer returned by the native code. And that's what leads to your error.
The conclusion is that if you wish to use string return value on the C# p/invoke end, you need to match that on the native end. To do so return PAnsiChar or PWideChar and allocate the character arrays with a call to CoTaskMemAlloc.
You absolutely cannot use NewStr here. In fact you should never call that function. Your existing code is comprehensively broken and every call you make to NewStr leads to a memory leak.
Some simple example code that will work:
Delphi
function SomeFunc: PAnsiChar; stdcall;
var
SomeString: AnsiString;
ByteCount: Integer;
begin
SomeString := ...
ByteCount := (Length(SomeString)+1)*SizeOf(SomeString[1]);
Result := CoTaskMemAlloc(ByteCount);
Move(PAnsiChar(SomeString)^, Result^, ByteCount);
end;
C#
[DllImport("SomeDelphi.dll")]
public static extern string SomeFunc();
You would probably want to wrap the native code up in a helper for convenience.
function COMHeapAllocatedString(const s: AnsiString): PAnsiChar; stdcall;
var
ByteCount: Integer;
begin
ByteCount := (Length(s)+1)*SizeOf(s[1]);
Result := CoTaskMemAlloc(ByteCount);
Move(PAnsiChar(s)^, Result^, ByteCount);
end;
Yet another option is to return a BSTR and use MarshalAs(UnmanagedType.BStr) on the C# side. However, before you do so, read this: Why can a WideString not be used as a function return value for interop?
Why do you see different behaviour in different .net versions? Hard to say for sure. Your code is just as broken in both. Perhaps the newer versions are better at detecting such errors. Perhaps there's some other difference. Are you running both 4.0 and 4.5 on the same machine, same OS. Perhaps your 4.0 test is running on an older OS which doesn't throw errors for COM heap corruptions.
My opinion is that there's little point understanding why broken code appears to work. The code is broken. Fix it, and move on.
My few points:
First, Marshal.FreeCoTaskMem is for freeing COM allocated memory blocks! It's not guaranteed to work for other memory blocks allocated by Delphi.
NewStr is deprecated (I get this after googling):
NewStr(const S: string): PString; deprecated;
My suggestion is that you also export a DLL function that does string deallocation instead of using FreeCoTaskMem.
How can i use this dll function in c#? I tried the following but i get error.
"External component has thrown an exception."
First time i am doing this PInvoke stuff with C# and Delphi.
function HTTPGET(location:string):string; stdcall;
var
HTTP:TIdHttp;
begin
HTTP := TidHttp.Create(nil);
try
result := HTTP.Get(location);
finally
FreeAndNil(HTTP);
end;
end;
exports
HTTPGET;
begin
end.
namespace Test
{
class Program
{
[DllImport("project1.dll")]
public static extern string HTTPGET(string location);
static void Main(string[] args)
{
Console.WriteLine(HTTPGET("http://www.reuters.com/"));
}
}
}
You cannot call that function from C#. That's because you cannot use Delphi string for interop. You can use PAnsiChar for strings passed from managed to unmanaged, but in the other direction it's more complex. You'd need to allocate the memory at the caller, or use a shared heap. I prefer the latter approach which is easiest done with the COM BSTR. This is WideString in Delphi.
As has been discussed before, you cannot use WideString as a return value for interop, since Delphi uses a different ABI from MS tools for return values.
The Delphi code needs to look like this:
procedure HTTPGET(URL: PAnsiChar; out result: WideString); stdcall;
On the C# side you write it like this:
[DllImport("project1.dll")]
public static extern void HTTPGET(
string URL,
[MarshalAs(UnmanagedType.BStr)]
out string result
);
If you want Unicode for the URL then use PWideChar and CharSet.Unicode.
procedure HTTPGET(URL: PWideChar; out result: WideString); stdcall;
....
[DllImport("project1.dll", CharSet=CharSet.Unicode)]
public static extern void HTTPGET(
string URL,
[MarshalAs(UnmanagedType.BStr)]
out string result
);
Do not use string type: strings require memory management, and C# and Delphi modules obviously use different memory managers (leave alone that C# passes char* and Delphi expects String). Try changing location type to PChar in your DLL, and also change your result type so it's either PChar (the buffer should be allocated explicitly) or something else, but not string.
As i remember, you can't marshall delphi strings with C#... you have to use a workaround with PChar and manage the memory yourself, or use something like the workaround provided in the last answer here:
Using Delphi's stuct arrays and strings in C#
Try Unmanaged Exports for c# by Robert Giesecke
https://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports
we are using encrypting engine from c# in Delphi App due compatibility with php and works well (so, the mentioned strings problem do not exists).
our earlier solution (worse) : register c# dll as com component and use it. with above solution output library have to be placed in exe directory and without registration works well :)
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 a DLL compiled in Delphi 2007 and an example using it in other Delphi project. Here is a part of code:
TErrorCallback = function(Msg:PChar):byte of object;
TSaveEventCallback = function (Line:PChar; HiCode:PChar; LoCode:PChar; MobileNo:PChar):byte of object;
function InitModule(ErrorCallback:TErrorCallback; SaveEventCallback :TSaveEventCallback; MainWindowHandle:THandle; Sock_Event:integer):byte; stdcall; external 'My.dll' name 'InitModule';
function DLLSocketEvent(var msg: TMessage): byte; stdcall; external 'My.dll' name 'DLLSocketEvent';
function InitObjList(Objs: array of PChar; NumObjs: byte; Name: PChar):byte; stdcall; external 'My.dll' name 'InitObjList';
And here is my C# analog:
class Message
{
unsigned int msg;
int wParam;
int lParam;
int result;
};
delegate byte ErrorCallbackDelegate(string msg);
delegate byte SaveEventCallbackDelegate(string line, string hiCode, string loCode, string mobileNo);
[DllImport("My.dll")]
static extern byte InitModule(ErrorCallbackDelegate errorCallback, SaveEventCallbackDelegate saveEventCallback, IntPtr mainWindowsHandle, Int32 sockEvent);
[DllImport("My.dll")]
static extern byte DllSocketEvent(Message msg);
[DllImport("My.dll")]
static extern byte InitObjList(string[] objs, byte numObjs, string name);
The point is I've tried only InitModule method and it throwed an exception:
A call to PInvoke function 'ProjTest!ProjTest.MyClass::InitModule' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
Please, help me with this. How should I describe these DLL functions in C#?
You can't call that DLL from C#. The main problem are the two of object callbacks. There's no way in C# to match that. You will need to modify the existing DLL or add an intermediate adapter DLL. As it stands your DLL is only accessible from Delphi or C++ Builder.
If you can modify the DLL then the modification you need to make is to remove the of object. If you need the callback to act on an instance then you will need to pass the instance as a parameter. However, C# delegates can wrap all that up transparently so you would only need to pass the instance as a parameter if you needed the DLL to be accessible from other languages, e.g. Delphi.
The other issue is the open array parameter. That is also not easily accessed from other languages. Although there are tricks, I would recommend passing a reference to the first element rather than an open array. Open arrays are unique to Delphi.
I also do not understand why you are using the byte type to hold array length. You should use Integer for this. There's nothing to gain from using byte and you simply invite overflow. Also, MainWindowHandle should not be THandle in Delphi. It should be HWND.
My recommendation to you would be to modify the DLL to have a C compatible interface and thus be accessible from all languages that support that. In practice this would make it accessible from all mainstream programming languages.