Hi

I finally have some good news.

So the starting point I've used for investigating the Windows AMD64 bridge
crashes is the main/testtools module, which runs extensive tests on the
bridge, and also crashes.

In that module, "build" eventually runs this:

---snip---
cd ../../wntmscx12/bin && : &&
PATH=/cygdrive/c/source/openoffice-git/main/solver/450/wntmscx12/bin${PATH:+:${PATH}}
C:/source/openoffice-git/main/solver/450/wntmscx12/bin/uno \
                -ro uno_services.rdb -ro uno_types.rdb \
                -s com.sun.star.test.bridge.BridgeTest -- \
                com.sun.star.test.bridge.CppTestObject
---snip---

which crashes.

Note what's happening here. The "uno" executable (
https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/The_UNO_Executable),
which can load and run components independently of the main OpenOffice
binary, is used. The service (-s) is "com.sun.star.test.bridge.BridgeTest",
with read-only registries (-ro) of "uno_services.rdb" and "uno_types.rdb",
and "com.sun.star.test.bridge.CppTestObject" is an argument to the service.

That com.sun.star.test.bridge.BridgeTest is implemented in
main/testtools/source/bridgetest/bridgetest.cxx. Here the crash happens
during TestBridgeImpl::run(). That method makes 4 UNO calls:
makeSurrogate(), performTest(), raiseException() and
raiseOneWayException(). The only one that works  is makeSurrogate() - the
other 3 each individually crash, even if they are the only uncommented call
made.

Leaving the exception tests for later, let's first understand why
performTest() is crashing.

The first 2 UNO calls performTest() makes are to XBridgeTest.setValues(),
and then XBridgeTest.getValues(). The setValues() call works, it is the
getValues() call that crashes.

Test_Impl::setValues() in main/testtools/source/bridgetest/cppobj.cxx
receives a huge number of variables, one of each UNO type, and copies them
to its fields. Conversely, Test_Impl::getValues() receives references to
the same types of variables as parameters, and copies its fields,
previously populated by setValues(), back out through those parameters.

Selectively commenting out lines in getValues() showed all of these crash:
xTest = _aData.Interface;
rSequence = _aData.Sequence;
rStruct = _aStructData;
return _aStructData;

That last "return _aStructData" took me long to figure out. Why is it
crashing after flow of control successfully reaches the end
of Test_Impl::getValues()? Well, if "rStruct = _aStructData;" is crashing,
then we have a bug in structure assignment, and returning a struct must
involve an implicit structure assignment somewhere, so it also crashes.
Only commenting all those lines out, and changing Test_Impl::getValues() to
return "void", stopped the crash.

So there are bugs in assignment of interfaces, sequences, and structs.

---

What is _aStructData?
TestData _aData, _aStructData;

What is TestData?
In main/testtools/source/bridgetest/idl/bridgetest.idl we have these:
---snip---
/**
 * simple types
 */
struct TestSimple
{
    boolean                    Bool;
    char                       Char;
    byte                       Byte;
    short                      Short;
    unsigned short             UShort;
    long                       Long;
    unsigned long              ULong;
    hyper                      Hyper;
    unsigned hyper             UHyper;
    float                      Float;
    double                     Double;
    TestEnum                   Enum;
};
/**
 * complex types adding string, inteface, any
 */
struct TestElement : TestSimple
{
    string                     String;
    com::sun::star::uno::XInterface   Interface;
    any                        Any;
};
/**
 * adding even more complexity, sequence< TestElement >
 */
struct TestDataElements : TestElement
{
    sequence< TestElement > Sequence;
};
/**
 * typedef used in interface
 */
typedef TestDataElements TestData;
---snip---

So the TestData struct contains sequences and interfaces, and since we've
seen sequence and interface assignments crash, that could be why TestData
assignments crash, rather than a general problem in struct assignment.

Also the sequence assignment is
to ::com::sun::star::uno::Sequence<TestElement >& rSequence, and
TestElement has an interface, so it could be that only interface assignment
is broken, and sequence and struct assignment appear broken because the
sequence and struct we are testing contain an interface.

But then again, sequences, structs and interfaces are all complex types,
and complex types are marshalled differently to simple types in the UNO
bridge. Maybe there is a bug there instead?

---

To distinguish between those possibilities, we can try changing getValues()
to return a sequence of integers. If that succeeds, it favours the broken
interface marshalling hypothesis, and if that crashes or corrupts data, it
favours the broken complex types marshalling hypothesis.

Tests show even returning sequence<long> from getValues() crashes,
suggesting the latter: all complex type marshalling could be broken.

---

Logging what getValues() returns when all the bad types are commented shows
these types are returned with values identical to what setValues() passed:
bool
char
byte
short, ushort
long, ulong
hyper, uhyper
float, double
enum
string

So those types must be working, both as parameters passed by value in
setValues(), and successfully returned through [out] parameter references
in getValues().

The string type is unusual. Strings are normally complex types - at minimum
an array of characters - and we know arrays (sequences) are broken. So how
is string working?

---

How does the bridge work?

The flow through the code in main/bridges/source/cpp_uno/msvc_win64_x86-64
is:
* cpp2uno.cxx bridges::cpp_uno::shared::VtableFactory::addLocalFunctions()
pre-populates machine code stubs for a method on an UNO interface by calling
* cpp2uno.cxx codeSnippet() which generates machine code that calls
* call.asm privateSnippetExecutor() which calls
* cpp2uno.cxx cpp_vtable_call() which for general methods (as opposed to
acquire()/release()/queryInterface()) calls
* cpp2uno.cxx cpp2uno_call() which makes an UNO call by doing:
  (*pThis->getUnoI()->pDispatcher)( pThis->getUnoI(), pMemberTypeDescr,
pUnoReturn, pUnoArgs, &pUnoExc );
  However, at least when the destination is in C++,
main/bridges/source/cpp_uno/shared/unointerfaceproxy.cxx did this at some
stage:
  pDispatcher = unoInterfaceProxyDispatch;
  which means that the above calls
* uno2cpp.cxx unoInterfaceProxyDispatch() which for a general method call
will call
* uno2cpp.cxx cpp_call() which calls:
* call.asm callVirtualMethod() which calls the C++ method on the
destination.

---

Carefully examining that callVirtualMethod() assembly language function and
comparing it to the Linux and Win32 ones led me to find how the return
value was being written into pRegisterReturn, instead of *pRegisterReturn.
And after patching that, to test the results of that change, I uncommented
the interface assigning test to see if it still crashed, and it worked. But
then I realized I was testing it without having recompiled the bridge on
Win64 with my change... After my change it carried on working, and so did
sequence assignment, and even struct assignment, so I thought returning
struct instead of void should work too, right?

No. The minute I changed Test_Impl::getValues() back to return struct, it
started crashing, but crashing long before the "return" - it was crashing
when assigning the interface.

I realized my earlier bridge patch was irrelevant.

Changing the return type of a method from "void" to "struct" causes it to
crash when assigning to an interface reference parameter.

How is this possible?

Extensively reading through the last 3 functions on my list
- unoInterfaceProxyDispatch(), cpp_call(), and callVirtualMethod() - as
well as Microsoft's x64 ABI documentation - led me to discover that return
values larger than 64 bits in size are allocated by the caller and a
pointer to them is passed as the first argument (
https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#example-of-return-value-3---user-type-result-by-pointer
).

I immediately knew the bug must be there somewhere. The "void" return type
doesn't create that first hidden argument, but the "struct" return type
does, causing later arguments to mismatch, eventually resulting in the
crash.

I found one bug where the wrong pointer was being written as this hidden
return type, and fixed it, but it made no difference, and couldn't be the
cause because that would only affect returning from the method, and the
crashes happen during assignment, long before returning.

Eventually I added logging to cpp_call(), to see how much space was being
allocated and what the pointer was, but then saw something strange: it's
treating the huge TestData struct as being <= 64 bits, and not allocating a
hidden parameter.

That led me to x86_64::return_in_hidden_param()
in main/bridges/source/cpp_uno/msvc_win64_x86-64/abi.cxx, where I found the
logic for typelib_TypeClass_STRUCT and typelib_TypeClass_EXCEPTION was
inverted: it was returning false instead of true and vice versa, for the
cases where the size is > 8 bytes vs less.

That breakthrough finally got getValues() to fully work, successfully
assigning every type and returning the TestData struct.

---

The test still crashes later on from something else, but it gets much
further before that crash.

In the next few days, I'll revisit the 127 commits I've got in my local
amd64-bridge branch, clean them up, and push some of them to
the windows-amd64 branch (from which it has been forked off). That should
make further debugging easier, and allow others to join the fun.

There is still that next crash and any others to fix, then the exception
handling to get working, but the fact it's now successfully calling several
complex methods, with many parameters of various types and complex return
types, and getting the right results, is highly encouraging. A Win64
OpenOffice no longer seems that far away :-).

Regards
Damjan



On Mon, Feb 2, 2026 at 8:44 PM <[email protected]> wrote:

> Hi Damian,
>
> you are welcome. Of course use the thread. since i wrote off list, i
> loop in the list.
>
> All the best
>
> Peter
>
> Am 01.02.2026 um 19:34 schrieb Damjan Jovanovic:
> > Hi Peter
> >
> > I did rebuild the amd64 Windows build recently, and managed to
> > reproduce the bugs in the bridge.
> >
> > I haven't had time to investigate them yet, but I'll use this email
> > thread to keep notes as I go along, if that's ok with you.
> >
> > Thank you
> > Damjan
> >
> >
> > On Mon, Jan 19, 2026 at 6:29 PM <[email protected]> wrote:
> >
> >     Hi Damjan,
> >
> >
> >     Did you manage to work on windows64?
> >     Do we need to change sdk for windows64? if not i would plan this
> >     for 4.3
> >     (see roadmap discussion.)
> >
> >     Do you see any blockers in win64? Anything that we need to ponder
> >     with?
> >
> >     All the best
> >
> >     peter
> >

Reply via email to