2

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?

J...
  • 30,968
  • 6
  • 66
  • 143
MichaelD
  • 107
  • 1
  • 1
  • 9
  • Delphi's `WideString` is a wrapper for a COM `BSTR`. Is your C# code marshaling `string`s as `BSTR`s 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 Dec 10 '18 at 09:14
  • @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 Dec 10 '18 at 10:17
  • @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... Dec 10 '18 at 11:08
  • @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 Dec 10 '18 at 11:57

2 Answers2

1
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.

J...
  • 30,968
  • 6
  • 66
  • 143
  • An `out` parameter auto-inializates a managed type, like `WideString`, to a valid default value upon entry into the function. So, the `WideString` will be initialized to an nil/empty string in this case. See [What's the difference between "var" and "out" parameters?](https://stackoverflow.com/questions/14507310/). – Remy Lebeau Dec 10 '18 at 16:57
  • @RemyLebeau Yes, but this is a Delphi thing, is it not? Initializing compiler managed `out` parameters and return values is performed by the caller, I thought. In the case of calling this method from Delphi code, yes, the compiler will insert initialization on the calling side, but the method itself does not do this... or at least that was my understanding. Because this is called by the C# wrapper it would be up to that wrapper to decide what to do with the variable on the way in. Or am I completely misunderstanding something here? – J... Dec 10 '18 at 17:46
  • @RemyLebeau Actually, I just compiled a test and this seems to be the case, ie : `Project1.dpr.18: Send(ws); 00417B9B 8D45FC lea eax,[ebp-$04] 00417B9E E85DECFEFF call @WStrClr 00417BA3 E8A4FFFFFF call Send`, so `@WStrClr` is inserted by the compiler *before* calling `Send`. If `Send` is exported to a COM DLL, however, it's up to the caller to decide what to do with that parameter, I think. – J... Dec 10 '18 at 18:09
-1

@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.

MichaelD
  • 107
  • 1
  • 1
  • 9