PInvoke & Delphi - c#

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 :)

Related

C# string to Inno Setup

I have a C# DLL in which exposes a method which generates a string.
I want to call this Method from Inno Setup and receive the string then.
function GetInformationEx():String;
external 'GetInformationEx#{src}\data\tools\ZipLib.dll stdcall loadwithalteredsearchpath';
procedure ShowProgress(progress:Integer);
var
information : String;
begin
WriteDebugString('ShowProgress called');
if(progress > pbStateZip.position) then
begin
pbStateZip.position := progress;
lblState2.Caption := IntToStr(progress)+' %';
try
information := GetInformationEx();
except
ShowExceptionMessage;
end;
//Do something with the information
end
if(progress >= 100)then
begin
KillTimer(0,m_timer_ID);
//Inform that the extraction is done
end
WriteDebugString('ShowProgress leave');
end;
Here is my simple C# part
[DllExport("GetInformationEx", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
public static String GetInformationEx()
{
return "Some simple text message || or heavy information";
}
My question is:
What kind of type do I have to send back to Inno Setup so that Inno Setup can handle it correctly?
Until now I get this message
PS: I read this post:
Returning a string from a C# DLL with Unmanaged Exports to Inno Setup script
But I want the C# code to be in charge of the string.
The .NET String type definitely won't marshal to Pascal string type. The .NET does not know anything about Pascal types.
The .NET can marshal strings to character arrays (and Pascal can marshal string from character arrays). But when the string is a return type of a function, there's problem with allocation of the memory for the string (who allocates the memory, who releases it).
That's why the solution in question you pointed to suggests you to use by ref/out parameter, because this way the caller can provide a buffer the .NET can marshal the string to. So there are no problems with allocation.

Delphi DLL return string from C# ... .NET 4.5 Heap Corruption but .NET 4.0 works? Explain please?

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.

Calling a Delphi method in a dll from c#

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)

Calling a delphi DLL function from C# Code

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.

Use DLL compiled in Delphi 7 in C#

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.

Categories