I've started upgrading a .NET 2.0 WinForms application to .NET 4.0. Well, OK, the upgrade process was just a matter of switching platform target, but making it actually work. I assumed that's all there would be to it.
But it seems that something drastically changed in .NET 4.0 regarding interop. Using DllImport(), the application embeds a couple Delphi dlls. When the application targets .NET 2.0, everything works normally. But when I changed it to target .NET 4.0, stuff starts going haywire, like something is corrupting memory.
For example, it replaces single digits with "0" in strange places. Data passed in an IStream gets 8 characters replaced with (Hex) 00 00 00 00 00 00 00 80, but only about 70% of the time. Two consecutive calls to retrieve the same value return different results (retrieving a value from a cache in memory, succeeds the first time, fails the second time). Strings being sent to a log are showing up truncated.
I've tried lots of stuff trying to make calling conventions more explicit, none of it has any effect. All strings are handled as [MarshalAs(UnmanagedType.LPWStr)] on the .NET side and PWChar on the Delphi side.
What changed in .NET 4.0 that would break P/Invoke like this?
----------------------------Edit-------------------------------------
Here's the simplest example. It generates a PDF which sometimes works correctly, but more frequently ends up corrupt (and works correctly in .NET 2.0):
[DllImport(DLLName)]
public static extern void SetDBParameters(
[MarshalAs(UnmanagedType.LPWStr)] string Server,
[MarshalAs(UnmanagedType.LPWStr)] string Database,
[MarshalAs(UnmanagedType.LPWStr)] string User,
[MarshalAs(UnmanagedType.LPWStr)] string Password,
short IntegratedSecurity);
procedure SetDBParameters(Server, Database, User, Password: PWChar;
IntegratedSecurity: WordBool); stdcall;
[DllImport(DLLName)]
public static extern short GeneratePDF(
[MarshalAs(UnmanagedType.LPWStr)] string Param1,
[MarshalAs(UnmanagedType.LPWStr)] string Param2,
[MarshalAs(UnmanagedType.LPWStr)] string Param3,
[MarshalAs(UnmanagedType.LPWStr)] string Param4,
out IStream PDFData);
function GeneratePDF(Param1, Param2, Param3, Param4: PWChar;
out PDFData: IStream): WordBool; stdcall;
private byte[] ReadIStream(IStream Stream)
{
if (Stream == null)
return null;
System.Runtime.InteropServices.ComTypes.STATSTG streamstats;
Stream.Stat(out streamstats, 0);
Stream.Seek(0, 0, IntPtr.Zero);
if (streamstats.cbSize <= 0)
return null;
byte[] result = new byte[streamstats.cbSize];
Stream.Read(result, (int)streamstats.cbSize, IntPtr.Zero);
return result;
}
WordBool and short were originally boolean (Delphi) and bool (C#), I changed them to be more explicit, just in case.
----------------------------Edit-------------------------------------
The stuff I wrote earlier about WinForms appears to have turned out to be not completely relevant, I've recreated one of the issues without any UI. The following program generates 0,1,2,3,4,5,6,7,8,9 under 2.0/3.5, but 0,-1,-1,-1,-1,-1,-1,-1,-1 under 4.0.
using System;
using System.Runtime.InteropServices;
namespace TestNet4interop
{
static class Program
{
[DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
public static extern void AddToList(long value);
[DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
public static extern int GetFromList(long value);
static void Main()
{
for (long i = 0; i < 10; i++)
{
AddToList(i);
Console.WriteLine(GetFromList(i));
}
}
}
}
And the Delphi side (compiled with Delphi 2007):
library TestSimpleLibrary;
uses
SysUtils,
Classes;
{$R *.res}
var
List: TStringList;
procedure AddToList(value: int64); stdcall;
begin
List.Add(IntToStr(value));
end;
function GetFromList(value: int64): integer; stdcall;
begin
result := List.IndexOf(IntToStr(value));
end;
exports
AddToList,
GetFromList;
begin
List := TStringList.Create;
end.
It appears to be a bug in the Visual Studio 2010 debugger. It seems to be clobbering memory that doesn't belong to it. All of the problems I've observed (all of which can be reproduced reliably) disappear completely if I run the application directly, instead of through Visual Studio 2010.
The bug is actually in the Managed Debug Assistant. If you turn it off completely (set HKLM\Software\Microsoft.NETFramework\MDA = "0"), the problem goes away. But of course you lose some debugging capability by doing so.
Appears that this is a problem with the Calling Convention property in the DllImport attribute. Should be Cdecl not the default StdCall. I had this problem when migrating from 2.0 to 4.0 and running in VS2010. See article here. http://codenition.blogspot.com/2010/05/pinvokestackimbalance-in-net-40i-beg.html
Boolean is a one byte type on Delphi. So changing them must be with a one byte type
I see a similar problem with a Delphi dll:
social_msdn I have noticed that my library compiled with FreePascal (instead of Delphi) works even within VS2010 without any problems. Therefore I don't know if Delphi, the .NET4 debugger or the combination is the reason for the trouble.
There is some evidence that memory allocated during dll start-up (e.g. in the initialization section) is affected by the memory corruption.
Related
How can I use this function in C#?
function CheckCard (pPortID:LongInt;pReaderID:LongInt;pTimeout:LongInt): PChar;
This function included the dll.
I can try this way:
[DllImport("..\\RFID_107_485.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.ThisCall)]
public static extern char CheckCard(int pccPortID, int pccdReaderID, int pccTimeout);
char pccCheckCard = CheckCard(3, 129, 1000);
Console.WriteLine(pccCheckCard);
but i don't get a true answer...
please help me ? :)
There are many problems here. This is what I can see:
The Delphi code as written uses the Delphi register calling convention. That is only accessible from Delphi code and cannot be called by a p/invoke method. However, it is possible that you have omitted the calling convention from the code and it is in fact stdcall.
Your p/invoke uses CallingConvention.ThisCall which certainly does not match any Delphi function. That calling convention is not supported by Delphi.
You mistranslate PChar, a pointer to null-terminated array of characters as char, a single UTF-16 character.
The Delphi code looks suspicious. The function returns PChar. Well, who is responsible for deallocating the string that is returned. I would not be surprised if the Delphi code was returning a pointer to a string variable that is destroyed when the function returns, a very common error.
You refer to the DLL using a relative path. That is very risky because you cannot easily control whether or not the DLL will be found. Place the DLL in the same directory as the executable, and specify just the DLL's file name.
There is no error checking to be seen.
A variant that might work could look like this:
Delphi
function CheckCard(pPortID: LongInt; pReaderID: LongInt; pTimeout: LongInt): PChar;
stdcall;
C#
[DllImport("RFID_107_485.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr CheckCard(int pccPortID, int pccdReaderID, int pccTimeout);
....
IntPtr pccCheckCard = CheckCard(3, 129, 1000);
// check pccCheckCard for errors, presumably IntPtr.Zero indicates an error
// assuming ANSI text
string strCheckCard = Marshal.PtrToStringAnsi(pccCheckCard);
// or if the Delphi code returns UTF-16 text
string strCheckCard = Marshal.PtrToStringUni(pccCheckCard);
This leaves unresolved how to deallocate the pointer returned. You'll have to consult your documentation for the function to find that out. The question contains insufficient information.
I am trying to access ntextcat dll from Delphi 2007.
Registering the dll´s failed (I have tried 32 and 64Bit regsvr32).
Importing the typelibrary in Delphi failed as well (as .dll and .Net).
Is there anybody out there who got access to ntextcat within Delphi?
This is a .net library. As such it is not readily accessible from unmanaged code such as Delphi. If you do want to consume it from Delphi you'll need to create a wrapper. For instance by using one of the following:
A COM interface.
A mixed mode C++/CLI assembly that wraps the managed library and exposes an unmanaged interface.
A wrapper based on Robert Giesecke's UnmanagedExports.
The latter two allow you to consume a .net assembly as a native DLL.
If the library has a rich and broad interface then the task of wrapping it if not going to be terribly easy or convenient. You may be better finding a different library that is easier to consume.
If the library has a small interface, or if you only need a small part of its capabilities, then making a wrapper may be more tractable.
Based on David´s answer (thank you for pointing me in the right direction) this the solution using Robert Giesecke's UnmanagedExports:
C# (be aware to set the platform target to the same as your Delphi application targets to! Do not use "AnyTarget"!)
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
namespace ClassLibrary1
{
public class Class1
{
[DllExport("testfunc", CallingConvention = CallingConvention.StdCall)]
public static int anytest(int iInt) {
return iInt;
}
}
}
The Delphi source:
Type
Ttest = function(const iInt: Integer): Integer; stdcall;
procedure TForm1.btTestClick(Sender: TObject);
var
Handle: Integer;
test : Ttest;
begin
Handle := LoadLibrary(<PathToDLL>ClassLibrary1.dll');
if Handle <> 0 then begin
#test := GetProcAddress(Handle, 'testfunc');
if #test <> NIL then ShowMessage(IntToStr(test(1234)));
end;
end;
Based on this it is possible to access the ntextcat .net library.
David's answer to another question shows a Delphi DLL function returning a WideString. I never thought that was possible without the use of ShareMem.
My test DLL:
function SomeFunction1: Widestring; stdcall;
begin
Result := 'Hello';
end;
function SomeFunction2(var OutVar: Widestring): BOOL; stdcall;
begin
OutVar := 'Hello';
Result := True;
end;
My caller program:
function SomeFunction1: WideString; stdcall; external 'Test.dll';
function SomeFunction2(var OutVar: Widestring): BOOL; stdcall; external 'Test.dll';
procedure TForm1.Button1Click(Sender: TObject);
var
W: WideString;
begin
ShowMessage(SomeFunction1);
SomeFunction2(W);
ShowMessage(W);
end;
It works, and I don't understand how. The convention I know of is the one used by the Windows API, for example Windows GetClassNameW:
function GetClassNameW(hWnd: HWND; lpClassName: PWideChar; nMaxCount: Integer): Integer; stdcall;
Meaning the caller provides the buffer, and the maximum length. The Windows DLL writes to that buffer with the length limitation. The caller is allocates and deallocates the memory.
Another option is that the DLL allocate the memory for example by using LocalAlloc, and the Caller deallocates the memory by calling LocalFree.
How does the memory allocation and deallocation work with my DLL example? Does the "magic" happen because the result is WideString(BSTR)? And why aren't Windows APIs declared with such convenient convention? (Are there any known Win32 APIs that uses such convention?)
EDIT:
I Tested the DLL with C#.
Calling SomeFunction1 causes an AV (Attempted to read or write protected memory).
SomeFunction2 works fine.
[DllImport(#"Test.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string SomeFunction1();
[DllImport(#"Test.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SomeFunction2([MarshalAs(UnmanagedType.BStr)] out string res);
...
string s;
SomeFunction2(out s);
MessageBox.Show(s); // works ok
MessageBox.Show(SomeFunction1()); // fails with AV!
Here is a followup.
A WideString is the same as a BSTR, it's just the Delphi name for it. The memory allocation is handled by the shared COM allocator, CoTaskMemAlloc. Because all parties use the same allocator you can safely allocate in one module and deallocate in another.
So, the reason you don't need to use Sharemem is that the Delphi heap is not being used. Instead the COM heap is used. And that is shared between all modules in a process.
If you look at the Delphi implementation of WideString you will see calls to the following APIs: SysAllocStringLen, SysFreeString and SysReAllocStringLen. These are the system provided BSTR API functions.
Many of the Windows APIs you refer to pre-date the invention of COM. What's more, there are performance benefits to using a fixed length buffer, allocated by the caller. Namely that it can be allocated on the stack rather than a heap. I also can imagine that the Windows designers don't want to force every process to have to link to OleAut32.dll and pay the price of maintaining the COM heap. Remember that when most of the Windows API was designed, the performance characteristics of the typical hardware was very different from now.
Another possible reason for not using BSTR more widely is that the Windows API is targeted at C. And managing the lifetime of BSTR from C is very much more tricky than from higher level languages like C++, C#, Delphi etc.
There is an extra complication however. The Delphi ABI for WideString return values is not compatible with Microsoft tools. You should not use WideString as a return type, instead return it via an out parameter. For more details see Why can a WideString not be used as a function return value for interop?
I have to use an existing dll, but got a problem with it.
Here's how I try to use it:
unit u_main;
...
implementation
procedure getUserData(var User, Pass: string); stdcall; external 'Common5.dll';
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
u, p: string;
begin
getUserData(u,p);
end;
...
end.
When I press the button the get the userData, I get an EInvalidPointer exception.
The dll is registerd and in some other projects it's in use and work. any ideas?
EDIT:
The DLL was created in Delphi7 and is now used in a Delphi 2009 project.
Maybe there's a problem with unicode strings or something like that?
You need to rebuild the Delphi 7 DLL, make it follow the WinApi standard of getting PChar and BufferLen parameters. You've got multiple problems with the current implementation:
string is platform-specific, it's implementation may change between delphi versions (and did change). You're not supposed to use string outside the platform!
You're passing the parameters as "var", suggesting the DLL might change the value of user and/or pass. String is an special, managed type, changing it requires allocating memory for the new string. This in turns requires you to share the memory manager between the DLL and the EXE (using sharemem.pas and BorlandMM.dll - or variants). The trouble is, sharing the memory manager between different versions of Delphi is an unsupported configuration! (gotton from embarcadero forums)
The Delphi 7 is hoping to receive an simple AnsiString (1 byte chars), while the Delphi 2009 exe is sending Unicode strings (2 bytes per char).
Along with using PChar, be sure to pre-allocate the space before you call GetUserData. i.e. if you assign 'foo' into a pchar that's empty, you'll blow up. So either use static length PChar/PAnsiChar arrays, or use this technique:
var
s : AnsiString;
begin
setlength(s,256);
MyDLLProc(PAnsiChar(s));
end;
We try to pass a string from a native Delphi program to a Delphi Prism DLL.
We have no problem passing integers, but strings are mismatched in the DLL.
We saw Robert Love's code snippet in response to another question, but there is no code for the native Delphi program.
How can we pass strings from Delphi to a Delphi Prism DLL?
The best way would be to use WideString.
For several reasons.
It is Unicode and works before D2009
It's memory is managed in ole32.dll, so no dependency on either Delphi's memory manager or the CLR GC.
You do not have to directly deal with pointers
In Oxygene, you could write it like so:
type
Sample = static class
private
[UnmanagedExport]
method StringTest([MarshalAs(UnmanagedType.BStr)]input : String;
[MarshalAs(UnmanagedType.BStr)]out output : String);
end;
implementation
method Sample.StringTest(input : String; out output : String);
begin
output := input + "ä ~ î 暗";
end;
"MarshalAs" tells the CLR how to marshal strings back and forth. Without it, strings are passed as Ansi (PAnsiChar), which is probably NOT what you would want to do.
This is how to use it from Delphi:
procedure StringTest(const input : WideString; out output : WideString);
stdcall; external 'OxygeneLib';
var
input, output : WideString;
begin
input := 'A b c';
StringTest(input, output);
Writeln(output);
end.
Also, never ever use types, that are not clearly defined, for external interfaces.
You must not use PChar for DLL imports or exports. Because if you do, you will run into exceptions when you compile it with D7 or D2009 (depending on what the original dev system was)
Strings in Delphi Win32 are managed differently from strings in .Net, so you can not pass a .Net string to Delphi Win32 or vice versa.
To exchange strings values you'd better use PChar type which is supported by both compilers. That is the same way you send string values to Windows API functions.
Regards
P.S. I am NOT Robert ;-)