Creating a DLL that would bridge an unmanaged Delphi call to managedC#

D

Daniel W.

Hello, I'm trying to create this DLL that will get called from a
Delphi program. In order to interface the unmanaged Delphi program
with the managed C#, I'm trying to make an intermediary bridge DLL. So
far I've been able to make it accessible to the Delphi program, but
I'm having problems returning a string which is passed as a pchar
parameter.

Delphi signature for the function I'm implementing:
function ElectronicPay(const input :pchar, var output :pchar):
boolean; export; stdcall;

C# bridge implementation so far:
[DllExport] // http://www.codeproject.com/KB/dotnet/DllExporter.aspx?msg=3267441
public unsafe static bool ElectronicPay(string input, IntPtr
output)
{
string managedOutput = Marshal.PtrToStringAnsi(output);

Terminal.ElectronicPay(input, ref managedOutput);

output = Marshal.StringToHGlobalAnsi(managedOutput);
return true;
}

Now the input string works perfectly. However I'm having problems with
producing the output. I'm pretty sure I'm not supposed to allocate the
memory for output, but rather use what they should have given me
already preallocated. However I haven't been able to get the string
they are sending in in the output parameter. Can anybody shine some
light on how to deal with this situation. Please note that I don't
have any control nor source code of the Delphi program.

Thanks.
Dan
 
P

Peter Duniho

Daniel said:
[...]
C# bridge implementation so far:
[DllExport] // http://www.codeproject.com/KB/dotnet/DllExporter.aspx?msg=3267441

Wow. That seems overly complex.

If I were doing this (and thankfully, I'm not :) ), I would expose my
managed code as a COM object. Then I would either access the COM object
directly from the unmanaged code, or I would write an unmanaged wrapper
that does that.

In your case, it sounds like the latter would be necessary, since the
original Delphi isn't expecting a COM object, nor are you able to change
it so that it does.

Post-processing a managed assembly seems unreasonably complicated to me.
Not that COM is trivial to get right, but .NET and Windows both
already have significant built-in support to handle conversion to and
from COM-friendly types and marshaling function calls between unmanaged,
COM, and managed code. Seems like it would make more sense to take
advantage of that.

Pete
 
A

Arne Vajhøj

Hello, I'm trying to create this DLL that will get called from a
Delphi program. In order to interface the unmanaged Delphi program
with the managed C#, I'm trying to make an intermediary bridge DLL. So
far I've been able to make it accessible to the Delphi program, but
I'm having problems returning a string which is passed as a pchar
parameter.

Delphi signature for the function I'm implementing:
function ElectronicPay(const input :pchar, var output :pchar):
boolean; export; stdcall;

C# bridge implementation so far:
[DllExport] // http://www.codeproject.com/KB/dotnet/DllExporter.aspx?msg=3267441
public unsafe static bool ElectronicPay(string input, IntPtr
output)
{
string managedOutput = Marshal.PtrToStringAnsi(output);

Terminal.ElectronicPay(input, ref managedOutput);

output = Marshal.StringToHGlobalAnsi(managedOutput);
return true;
}

Now the input string works perfectly. However I'm having problems with
producing the output. I'm pretty sure I'm not supposed to allocate the
memory for output, but rather use what they should have given me
already preallocated. However I haven't been able to get the string
they are sending in in the output parameter. Can anybody shine some
light on how to deal with this situation. Please note that I don't
have any control nor source code of the Delphi program.

I would drop that DllExport thingy and manually write a Win32 DLL in
mixed mode C++ that exposes the Win32 API that Delphi expects and
calls your .NET stuff.

That should be relative trivial.

Arne
 
D

Daniel W.

I would drop that DllExport thingy and manually write a Win32 DLL in
mixed mode C++ that exposes the Win32 API that Delphi expects and
calls your .NET stuff.

That should be relative trivial.

Arne

I'm actually fairly close to getting it this way, using "mixed-mode
C#" with the DllExport. I think I may hit the same wall with C++,
since it is appearing to me that they are not preallocating the memory
for that string. Also I have no idea on how to write a mixed-mode C++
dll. I know that the previous code would have a memory leak, if it
works, I would be freeing up that memory after a period of time.
 
P

Peter Duniho

Daniel said:
I'm actually fairly close to getting it this way, using "mixed-mode
C#" with the DllExport. I think I may hit the same wall with C++,
since it is appearing to me that they are not preallocating the memory
for that string. Also I have no idea on how to write a mixed-mode C++
dll. I know that the previous code would have a memory leak, if it
works, I would be freeing up that memory after a period of time.

Writing a mixed-mode C++ DLL is, as Arne suggests, trivial: just compile
C++ code with managed support (you can find this in the project
properties, or use /clr on a command line build).

User-defined types in the DLL can include both managed types and
unmanaged types (i.e. managed types will be "value class", "value
struct", "ref class", or "ref struct", while unmanaged types will just
be "struct" or "class"). Any unmanaged program can use any of the
unmanaged types just as it could those found in any other unmanaged DLL.
And those unmanaged types can in turn use any managed types they need
to, providing the necessary connection between the unmanaged code using
your DLL and any managed code you need to use.

I suggested COM because for certain kinds of data marshaling, it should
be simpler. But of course it adds the complexity of dealing with COM,
which you don't have if you use a mixed-mode DLL. Instead, you will
probably find yourself using the System.Runtime.InteropServices.Marshal
class to convert between managed and unmanaged types within whatever
"bridge" type you create (i.e. the unmanaged type in your mixed-mode DLL
that in turn uses managed types).

IMHO, no matter how close you are to getting the DllExportAttribute
attribute to work, you should abandon that approach. Let Visual Studio
do all of the necessary processing of your code to accomplish the task;
adding an external layer to that can only make things more complicated,
harder to get working correctly, and easier to break.

Which approach you should take — COM or mixed-mode DLL — will IMHO
depend on what you're already most comfortable with. If you don't know
anything about either, I would recommend going with Arne's suggestion,
to use a mixed-mode DLL. IMHO it's probably easier to learn the Marshal
class than to learn all the ins and outs of COM programming.

Pete
 
A

Arne Vajhøj

I'm actually fairly close to getting it this way, using "mixed-mode
C#" with the DllExport. I think I may hit the same wall with C++,
since it is appearing to me that they are not preallocating the memory
for that string. Also I have no idea on how to write a mixed-mode C++
dll. I know that the previous code would have a memory leak, if it
works, I would be freeing up that memory after a period of time.

I still think you should drop that funky middleware and just
code it.

If you need to return a char*/PChar then you only have two
choices to avoid memory leak:
1) return static buffer and the Delphi code better copy
the data before next call
2) dynamic allocate in C++ and have Delphi call something
to deallocate

No magic.

Arne
 
A

Arne Vajhøj

I still think you should drop that funky middleware and just
code it.

If you need to return a char*/PChar then you only have two
choices to avoid memory leak:
1) return static buffer and the Delphi code better copy
the data before next call
2) dynamic allocate in C++ and have Delphi call something
to deallocate

No magic.

Super simple code example:

program high;

{$APPTYPE CONSOLE}

function dup(s : PChar):pCHar; stdcall; external 'middle2' index 1;

begin
writeln(dup('ABC'));
end.

#include <windows.h>

#include <cstring>

using namespace std;

#using <mscorlib.dll>
#using <low2.dll>

using namespace System;
using namespace System::Runtime::InteropServices;

extern "C"
{
__declspec(dllexport) char* __stdcall dup(char* s);
}

static char buf[100];

__declspec(dllexport) char* __stdcall dup(char* s)
{
String^ s2 = gcnew String(s);
s2 = Low::Dup(s2);
strcpy(buf, (char *)Marshal::StringToHGlobalAnsi(s2).ToPointer());
return buf;
}

using System;

public class Low
{
public static string Dup(string s)
{
return s + s;
}
}

Obviously there are a zillion variants, but it is doable.

Arne
 
D

Daniel W.

I still think you should drop that funky middleware and just
code it.
If you need to return a char*/PChar then you only have two
choices to avoid memory leak:
1) return static buffer and the Delphi code better copy
the data before next call
2) dynamic allocate in C++ and have Delphi call something
to deallocate
No magic.

Super simple code example:

program high;

{$APPTYPE CONSOLE}

function dup(s : PChar):pCHar; stdcall; external 'middle2' index 1;

begin
   writeln(dup('ABC'));
end.

#include <windows.h>

#include <cstring>

using namespace std;

#using <mscorlib.dll>
#using <low2.dll>

using namespace System;
using namespace System::Runtime::InteropServices;

extern "C"
{
__declspec(dllexport) char* __stdcall dup(char* s);

}

static char buf[100];

__declspec(dllexport) char* __stdcall dup(char* s)
{
     String^ s2 = gcnew String(s);
     s2 = Low::Dup(s2);
     strcpy(buf, (char *)Marshal::StringToHGlobalAnsi(s2).ToPointer());
     return buf;

}

using System;

public class Low
{
     public static string Dup(string s)
     {
         return s + s;
     }

}

Obviously there are a zillion variants, but it is doable.

Arne

Thanks Arne,

That has been very helpful, and now I'm not fiddling with weird stuff.
However after converting it to mixed-mode C++, I still get the same
garbage on the char* output parameter. Do you have any clue of what
else can be the problem?


__declspec(dllexport) bool __stdcall ElectronicPay(const char* input,
char* output)
{
String^ input2 = gcnew String(input);
String^ output2 = gcnew String(output);
//String^ output2 = Marshal::ptrToStringAnsi(output);
// either of the above two result in a output2 being garbage, input2
is correct though

ICGCC::Terminal::ElectronicPay(input2, output2);

strcpy(output, (char
*)Marshal::StringToHGlobalAnsi(output2).ToPointer());

return true;
}
 
D

Daniel W.

I still think you should drop that funky middleware and just
code it.
If you need to return a char*/PChar then you only have two
choices to avoid memory leak:
1) return static buffer and the Delphi code better copy
the data before next call
2) dynamic allocate in C++ and have Delphi call something
to deallocate
No magic.

Super simple code example:

program high;

{$APPTYPE CONSOLE}

function dup(s : PChar):pCHar; stdcall; external 'middle2' index 1;

begin
   writeln(dup('ABC'));
end.

#include <windows.h>

#include <cstring>

using namespace std;

#using <mscorlib.dll>
#using <low2.dll>

using namespace System;
using namespace System::Runtime::InteropServices;

extern "C"
{
__declspec(dllexport) char* __stdcall dup(char* s);

}

static char buf[100];

__declspec(dllexport) char* __stdcall dup(char* s)
{
     String^ s2 = gcnew String(s);
     s2 = Low::Dup(s2);
     strcpy(buf, (char *)Marshal::StringToHGlobalAnsi(s2).ToPointer());
     return buf;

}

using System;

public class Low
{
     public static string Dup(string s)
     {
         return s + s;
     }

}

Obviously there are a zillion variants, but it is doable.

Arne

Woohoo!!

Here's the working code:

__declspec(dllexport) bool __stdcall ElectronicPay(const char* input,
char** output)
{
String^ input2 = gcnew String(input);
String^ output2 = gcnew String(*output);

ICGCC::Terminal::ElectronicPay(input2, output2);

strcpy(*output, (char
*)Marshal::StringToHGlobalAnsi(output2).ToPointer());

return true;
}

It turns out that the Delphi declaration includes a var keyword.
function ElectronicPay(const input :pchar, var output :pchar):boolean;
export; stdcall;

Apparently that "var output :pchar" would be the equivalent of "char**
output"

I'm not completely sure if this is totally right due to my lack of
experience with C++, but it appears to be working, I will do extensive
tests on this.

Thanks a lot to Arne and Peter for pointing me into the right
direction, and making me forget that DllExporter thingy.
 
A

Arne Vajhøj

Here's the working code:

__declspec(dllexport) bool __stdcall ElectronicPay(const char* input,
char** output)
{
String^ input2 = gcnew String(input);
String^ output2 = gcnew String(*output);

ICGCC::Terminal::ElectronicPay(input2, output2);

strcpy(*output, (char
*)Marshal::StringToHGlobalAnsi(output2).ToPointer());

return true;
}

It turns out that the Delphi declaration includes a var keyword.
function ElectronicPay(const input :pchar, var output :pchar):boolean;
export; stdcall;

Apparently that "var output :pchar" would be the equivalent of "char**
output"

I'm not completely sure if this is totally right due to my lack of
experience with C++, but it appears to be working, I will do extensive
tests on this.

That sounds correct.

char* = pchar
char** = var pchar

Arne
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top