2
votes

I'm trying to send a string to a Delphi COM object and expecting an answer from the object but for some reason it throws an AccessViolationException. This is the exception it throws, the description of the exception translated to english is: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. Program output (with top of stack trace) :

QManservice started.
Press any key to stop.
Request to get orders received.
String received: S$GET ORDERS,

Onnverwerkte uitzondering: System.AccessViolationException: Poging tot het lezen of schrijven van beveiligd geheugen. Dit duidt er vaak op dat ander geheugen is beschadigd.

bij Microsoft.Win32.Win32Native.SysStringByteLen(IntPtr bstr)
bij System.StubHelpers.BSTRMarshaler.ConvertToManaged(IntPtr bstr)
bij QMan_SafanDarley.IWLM_.Send(String Msg, String& Answer)
bij WorkLoadManagerServiceDefinitions.QManService.SendStringtoCON(String codToSend) in D:\Michael\C# Projects\QManServiceConsoleApp\OrderEditor_WCF\QManService.cs:regel 210
bij WorkLoadManagerServiceDefinitions.QManService.RequestGetOrders() in D:\Michael\CR Projects\QManServiceConsoleApp\OrderEditor_WCF\QManService.cs:regel 67 ...

This is the code that calls the COM

private string SendStringToCOM(string cmdToSend)
    {
        try
        {
            Console.WriteLine($"String received: {cmdToSend}");
            if (WLM == null)
            {
                WLM = new WLM_();
            }
            string answer = string.Empty;
            WLM.Send(cmdToSend, out answer);
            Console.WriteLine("Answer received");
            return answer;
        } catch(Exception e)
        {
            Console.WriteLine(e.Message);
            Console.ReadKey();
            return string.Empty;
        }
    }

This is the code in Delphi that receives the call, it sends it to another unit that does database stuff according to what command it receives.

function TWLM_.Send(const Msg: WideString; out Answer: WideString) : Integer;
begin
    Result := fmProduction.AnalyzeData(Msg, 0);
end;

I should add that this works on my pc and a coworker's pc, but not on a third pc. Any suggestions on how i can resolve this?

2
Delphi's WideString is a wrapper for a COM BSTR. Is your C# code marshaling strings as BSTRs when PInvoking to Send()? You didn't show that declaration on the C# side. Also, is your Delphi function using a calling convention that is compatible with C#? It doesn't appear to be. It must use either cdecl or stdcall only, and be declared accordingly on the C# side.Remy Lebeau
@RemyLebeau the code doesn't marshal strings, but since it works on some systems, i assume this isn't necessary. Same thing for the calling convention, this isn't defined currently, but it seems to work without it.MichaelD
@MichaelD "Appearing to work sometimes" is not of itself evidence of correct code (usually, quite the contrary). Making assumptions is likewise perilous. How did you import this?J...
@J... I added the reference via Visual Studio's Add reference -> COM. The dll is made by my company. The answer parameter will be assigned with the next version of the dll.MichaelD

2 Answers

1
votes
function TWLM_.Send(const Msg: WideString; out Answer: WideString) : Integer;
begin
    Result := fmProduction.AnalyzeData(Msg, 0);
end;

Here you are not assigning anything to the Answer parameter. This is passed as an out parameter, meaning the method is required to assign something to it. This behaves in exactly the same way as a function return value.

If you don't assign anything to this variable it will have whatever (unassigned) value happened to exist on the stack at the time the method allocated stack space for it. This will not be a valid pointer to a WideString but the consuming code will try to marshal it as if it were. Sometimes this will crash immediately, sometimes it will not, sometimes it may simply corrupt other data. In either case, it is an error.

In native Delphi code you may be used to out and var parameters behaving quite similarly - in both cases, for reference types, the calling code's pointer is available for reading and writing by the method accepting the parameter. If the method choses not to modify the value it is not required to. With managed interop, however, the expectation is that an out parameter will always be assigned by the method. C# enforces this but Delphi does not.

In this case, the empty string you passed in on the C# side is not passed in to the method at all - you might expect that it should remain an empty string, unmodified by the Delphi code, but by making the parameter an out parameter the calling code expects to receive a return value in that parameter and immediately overwrites the passed variable with whatever is returned (in this case, a pointer to nonsense). Any value the variable on the C# side may have had prior to being passed to this method will, by extension, not be accessible on the Delphi/COM side.

-1
votes

@J...'s comment about the answer parameter not being assigned was, in my case, the issue. I got a colleague to change the dll to assign a value to the parameter and that seems to fix the error and now it works as it is supposed to.