I Have a third party DLL write in Delphi "a.dll" (without source).
And this DLL has one method with this signature.
function GetAny(pFileName: String): String;
I can't do a interop call from c# because 'String type' has private access in delphi.
So a build another DLL in delphi to wrapper that call.
Delphi.
function GetAny(pFileName: String): String; external 'a.dll'
function GetWrapper(url : PChar) : PChar; stdcall;
begin
Result := PChar(GetAny(url)); // I need avoid this String allocation, is throwing a exception.
end;
C#.
[DllImport("wrapper.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern IntPtr GetWrapper(String url);
Inside "GetWrapper" i make a call to external "GetAny", the result is OK (in delphi i can debug), but before i get this result back in c# side, it's throwing a Exception.
IntPtr test = GetWrapper("a");
String result = Marshal.PtrToStringAnsi(test);
Your wrapper DLL also cannot call GetAny because string is a managed Delphi type that cannot be passed across module boundaries.
The problem is that the return value of GetAny is allocated in one module and deallocated in a different module. It is allocated in the DLL that implements GetAny, and deallocated in the DLL that calls GetAny. Since the two DLLs use different memory managers, you end up trying to deallocate memory that was allocated on a different heap.
If the DLL that implements GetAny can be persuaded to share a memory manager then you could solve that problem readily.
I do question the facts that you present though. As it stands, unless the DLL is designed to be used with ShareMem, that function can never be called safely.
If you were prepared to leak the memory you could try this:
Delphi
function GetAny(pFileName: string): PChar; external 'a.dll'
procedure GetWrapper(url: PChar; out value: WideString); stdcall;
var
P: PChar;
begin
P := GetAny(url);
if Assigned(P) then
Value := P
else
Value := '';
end;
C#
[DllImport("wrapper.dll"]
public static extern void GetWrapper(
string url,
[MarshalAs(UnmanagedType.BStr)]
out string value
);
I've downloaded your code...
The solution could be like this:
Create a wrapper procedure in Delphi with "cdecl" declaration with 2 parameters of PChar type
the first one is IN parameter
the second one is OUT parameter
Original Delphi function:
function GetAny(pFileName: String): String; external 'a.dll';
Delphi – DLL with wrapped function:
procedure GetWrapper (url: PChar; var urlNew: PChar) cdecl;
var str: string;
begin
urlStr = string(url);
urNewStr := GetAny(urlStr);
urlNew := PChar(urNewStr);
end;
exports
GetWrapper;
begin
end.
In Visual Studio:
Make the project x32 bit (not a x64 as it was in your sample)
Import DLL as Cdecl
[DllImport("wrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
Marshaling
public static extern void GetWrapper ([MarshalAs(UnmanagedType.LPStr)]string url, [MarshalAs(UnmanagedType.LPStr)] out string urlNew);
Calling in C#:
string fileName;// = #"wertwertwertwertwer";
GetWrapper("2.jpg", out fileName);
Console.WriteLine(fileName);
In my environment it worked. (Delphi 5 and VS2012).
Hopefully it will work for you also.
Related
In C# with DLLImport need to call C++ custom type variable and pointers.
Custom Types:
typedef unsigned char UN_Char;
typedef enum { Valid = 0, Invalid = 1, Unknown = -999 } AppResponseCode;
Called in SomeAPI.C
static AppResponseCode SomeFunction(UN_Char ** vUC1PtrPtr,size_t * vSize1Ptr, UN_Char * vUC2Ptr,size_t vSize2, FILE * inputFile, char * vcharPtr)
Now I need to Call C++ Function from C#.
Once I tried to create Reference its failed witht the message
A Reference to '.....\SomeApi.DLL' could not be added. Please make sure that the file is accessible, and that it is valid assembly or COM Component
So I go with other route to Import DLL using DLLImport but here I don't have signature for the calling C++ Function
[DllImport("SomeAPI.dll", CharSet = CharSet.Unicode)]
public static extern ?AppResponseCode? SomeFunction( ?????emphasized text ) ;
Can someone help me to identify Function Signature ?
I am unable to get my C# Dll to work with my Excel Macro enable spreadsheet to call the function in the Dll. I am able to call the function if it has no parameters, able to return the expected value. But I cannot get the Dll call from VBA to be successful when I add input parameters to the function.
The sum total of my C# module is as follows:
using System;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace RSExternalInterface
{
public class RSExternalInterface
{
[DllExport("add", CallingConvention = CallingConvention.Cdecl)]
public static int TestExport(int left, int right)
{
return left + right;
}
}
}
My VBA Code is as follows.
Module Declaration:
Declare Function add Lib "C:\Program Files\RS\RSExternalInterface.dll" (ByVal Left As Integer, ByVal Right as Integer) As Integer
Call the function above, inside the click event sub of a button, is as follows:
result = add (5, 7)
The moment the above line is executed, Error 49 comes up.
I have tried the following:
Removed the "ByVal" in the VBA function declaration
Removed and added the decoration in the C# function, after [DllExports ...]
Ensured that I target the correct CPU platform
Removed the parameters in the declaration and calling to ensure that the DLL function is accessible.
What am I doing wrong?
I am surprised that "Calling Convention" from question title and CallingConvention from C# code didn't ring any bell (although I don't rule out a misleading error message on MS side).
Calling convention is like a protocol when executing subroutines, between:
callee - the routine (function, procedure (or function that doesn't return anything, or returns void))
caller - code (a subroutine as well - could be main) that calls/executes the callee
and determines:
Who handles (push / pop) the function arguments on the stack (callee / caller) when executing it
Arguments order on the stack (right -> left or left -> right)
Because of #1., it's important that the 2 parties (callee / caller) to be in synch regarding calling convention, otherwise they will setp on each other's toes, and the stack will eventually get corrupted. "Bad DLL Calling Convention" means exactly this thing: the 2 are not in synch.
You get correct behavior for function without arguments, because there's nothing to push / pop to/from the stack - this doesn't mean that there isn't any error; the error is there but it's not encountered
More details: [MSDN]: Argument Passing and Naming Conventions. Note differences between __stdcall and __cdecl, and also note that they apply to 32bit (x86) architecture. AFAIK (also backed up by the error), Excel executable (as whole MSOffice) is 32bit
According to [MSDN]: DllImportAttribute.CallingConvention Field (which I assume also applies to DllExport):
You set this field to one of the CallingConvention enumeration members. The default value for the CallingConvention field is Winapi, which in turn defaults to StdCall convention.
I took your code, and played a little bit with it.
code.cs:
using System;
using System.Runtime.InteropServices;
using RGiesecke.DllExport;
namespace RSExternalInterface
{
public class RSExternalInterface
{
[DllExport("add")]
public static int TestExport(int left, int right)
{
int ret = left + right;
String s = String.Format("C# Func - add: {0} + {1} = {2}", left, right, ret);
Console.WriteLine(s);
return ret;
}
[DllExport("dbl", CallingConvention = CallingConvention.Winapi)]
public static int TestExport1(int value)
{
int ret = value * 2;
String s = String.Format("C# Func - dbl: {0} * 2 = {1}", value, ret);
Console.WriteLine(s);
return ret;
}
[DllExport("none", CallingConvention = CallingConvention.StdCall)]
public static void TestExport2(int value)
{
String s = String.Format("C# Func - none: {0}", value);
Console.WriteLine(s);
}
}
}
main.vb:
Module main
Declare Function add Lib "CsDll.dll" (ByVal Left As Integer, ByVal Right As Integer) As Integer
Declare Function dbl Lib "CsDll.dll" (ByVal Value As Integer) As Integer
Declare Sub none Lib "CsDll.dll" (ByVal Value As Integer)
Sub Main()
Console.WriteLine("64 bit OS: {0}{1}64 bit process: {2}{1}", Environment.Is64BitOperatingSystem, Environment.NewLine, Environment.Is64BitProcess)
Dim i As Integer
none(-7659)
i = dbl(78)
i = add(22, -13)
End Sub
End Module
Notes:
I wasn't able to build it and run it in Excel, so I tried the 2nd best thing: do it from VB. Note that I'm experienced in neither C# nor VB
At the beginning (as I stated in a comment), I wasn't able to export anything from the C# .dll. Looking at UnmanagedExports.nuspec (part of the nupkg):
You have to set your platform target to either x86, ia64 or x64. AnyCPU assemblies cannot export functions.
Changed the Platform from Any CPU (VStudio default) to x64 (by mistake), and everything ran fine (in spite of CallingConvention.Cdecl), only after setting it to x86 I was able to reproduce the problem
Anyway, the (C#) code contains 3 ways of (not) specifying a calling convention, that will work from VB
Output (VStudio 2015 (Community)):
64 bit OS: True
64 bit process: False
C# Func - none: -7659
C# Func - dbl: 78 * 2 = 156
C# Func - add: 22 + -13 = 9
Here's an (ancient) URL that also mentions your error: [MSDN]: Bad DLL calling convention (Error 49)
I want to use a dll that made by Delphi. It has this function :
function CryptStr(str, Key : AnsiString; DecryptStr : boolean) : AnsiString; stdcall;
I copied the Dll in /bin/debug and in application root. my code is :
[DllImport("Crypt2.dll", EntryPoint = "CryptStr", CallingConvention = CallingConvention.StdCall)]
static extern string CryptStr( string str, string Key, bool DecryptStr);
public string g = "";
private void Form1_Load(object sender, EventArgs e)
{
g=CryptStr("999", "999999", true);
MessageBox.Show(g);
}
I have some problem :
1. even I delete Dll from those path application doesn't throw not found exception
2. when application run in g=CryptStr("999", "999999", true); it finishes execution and show the form without running Messagebox line.
I tried to use Marshal but above errors remain.
You cannot expect to call that function from a programming environment other than Delphi. That's because it uses Delphi native strings which are not valid for interop. Even if you call from Delphi you need to use the same version of Delphi as was used to compile the DLL, and the ShareMem unit so that the memory manager can be shared. That function is not even well designed for interop between two Delphi modules.
You need to change the DLL function's signature. For example you could use:
procedure CryptStr(
str: PAnsiChar;
Key: PAnsiChar;
DecryptStr: boolean;
output: PAnsiChar;
); stdcall;
In C# you would declare this like so:
[DllImport("Crypt2.dll")]
static extern void CryptStr(
string str,
string Key,
bool DecryptStr,
StringBuilder output
);
This change requires the caller to allocate the buffer that is passed to the function. If you want to find examples of doing that, search for examples calling the Win32 API GetWindowText.
If you were using UTF-16 text instead of 8 bit ANSI, you could use COM BSTR which is allocated on the shared COM heap, but I suspect that option is not available to you.
As for your program not showing any errors, I refer you to these posts:
http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/
http://blog.adamjcooper.com/2011/05/why-is-my-exception-being-swallowed-in.html
Silent failures in C#, seemingly unhandled exceptions that does not crash the program
I am wondering if someone could check my c# dllimport declaration for a dll compiled with Delphi XE2. Calling this dll from a ISAPI wrapper dll works fine but I am having no luck calling it from a c# asp.net app.
The Delphi procedure is defined as:
procedure ExecuteService(const RequestJSON :PWideChar; out ResponseJSON :Pointer; out ResponseJSONSize :Integer; out ResponseContent :Pointer; out ResponseContentSize :Integer); stdcall;
and the c# declaration is:
[DllImport("services.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public static extern void BIExecuteService(
String requestJSON,
out IntPtr reposnseJSON,
out int reposnseJSONSize,
out IntPtr reposnseContent,
out int reposnseContentSize
);
Sometimes it works but mostly it gives a System.AccessViolationException.
I have been trying to solve this for days, do the declarations look correct?
Edit: Attaching to the IISExpress process in Delphi XE2 the error seems to occur in clr.dll. Maybe my library is corrupting something but I have no idea how to find out where!
Thanks,
AJ
Your p/invoke declaration is correct. Your problems lie elsewhere.
I have a C++ DLL and some functions return Unicode null-terminated strings:
void SomeFunc(wchar_t* StrBuf)
The caller must allocate StrBuf - string can be up to 256 characters.
This DLL also exposes a COM object to use this DLL from C# via COM. Definition in IDL:
[id(1), helpstring("bla")]
HRESULT SomeFunc([in,out] BSTR* TextStr, [out,retval] LONG* RetValue);
Currently the C# code looks like this:
string val = new string('\0', 256); // Allocate memory
MyComObj.SomeFunc(ref val); // Get string
val = val.Substring(0, val.IndexOf('\0')); // Convert from null-terminated string
Is there a way to define such a COM function so it can be used from C# easier? Right now it looks ugly and takes three lines to call the function or five lines if a function has two string parameters.
If you have this exposed as a COM object, just use visual studio to add a reference to your object. It will automatically generate an assembly for you (I believe they call this a COM callable wrapper).
This will expose your COM objects methods to .NET and works unless you start trying to pass across some custom structures.
Here are some resources for you:
COM Callable Wrapper
COM Interop Part 1: C# Client Tutorial
If the memory for the string in the client allocates and fills it to the COM server, you must use a StringBuilder:
// IDL code
HRESULT GetStr(/*[out]*/ LPSTR pStr);
// Interop
void GetStr(/*[out]*/ [MarshalAs(UnmanagedType.LPStr)] StringBuilder pStr);
// C# code
var str = new StringBuilder(256);
MyComObj.GetStr(a);
Then the StringBuilder will be empty or filled with characters.
Solved. In C++ I use this code in the COM wrapper:
STDMETHODIMP MyClassClass::SomeFunc(BSTR* OptionValue, LONG* RetValue)
{
*RetValue = 0;
UNICODECHAR txt[MAXSTATSTRLEN];
//... Copy text to txt variable
*OptionValue = W2BSTR(txt);
return S_OK;
}
IDL:
[id(1), helpstring("")] HRESULT SomeFunc([out] BSTR* OptionValue, [out,retval] LONG* RetValue);
In C# it can now be called easily:
string val;
MyComObj.SomeFunc(out val);