Click to See Complete Forum and Search --> : [RESOLVED] Passing variables from managed c++


AKRichard
September 11th, 2011, 09:49 AM
Hello all,

Ive posted more over the last few days on this site then I ever have.

I am trying to implement some routines with inline assembly from managed c++ (vs 2008 express), I am trying to pass pointers to the first element in the array to the assembly code, but it is not recieving the correct addresses. What I have so far:


MiniBI^ operator+(MiniBI^ _val1, MiniBI^ _val2){

MiniBI^ _retval=gcnew MiniBI();

pin_ptr<int> _v1 = &_val1->_numa[0];
pin_ptr<int> _v2 = &_val2->_numa[0];
pin_ptr<int> _tempadd= &_retval->_numa[0];

MyBase::Add(_v1,_v2, _tempadd, _val1->_numa->Length, _val2->_numa->Length, _retval->_numa->Length);

return _retval;

}


So the code above is supposed to pin all the variables and return a pointer to the first element in the array, then it calls the assembly code passing the pointers, as well as the lengths of the arrays.


#pragma managed(push,off)

void MyBase::Add(int* _val1offset, int* _val2offset, int* _retval, int _val1size, int _val2size, int _retvalsize){

short count=0;
short car=0;

__asm{

push ecx
push edi
push esi
xor edi,edi
xor ecx,ecx

Add1:

xor eax,eax
mov ax,car
mov car,0h
add eax, dword ptr [_val1offset+edi*4]
add eax, dword ptr [_val2offset+edi*4]
jnc NoCarry
mov car, 01h
NoCarry:
mov dword ptr [_retval+edi*4],eax
inc edi
cmp edi,_val1size
jle Add1
xor eax,eax
mov ax,car
mov dword ptr [_retval+edi*4],eax
pop esi
pop edi
pop ecx
ret


}

//return _retval;

}

#pragma managed(pop)


and then is supposed to show up here. The algorithm appears to work correctly, except the values that show up for:

add eax, dword ptr [_val1offset+edi*4]
add eax, dword ptr [_val2offset+edi*4]

are not the values that were in the arrays in the calling routine, in fact, most of the time the assembly code errors out with an accessviolation error (Im assumiing because it is not hitting the arrays), also, Ive noticed that the _val1size variable almost allways (maybe allways) changes on the first pass through the algorithm, but it does start out with the correct value.

I am not a solid assembly (or native c++) programmer so you wouldnt hurt my feelings at all if you pointed out other problems with the alg.

Thanks

AKRichard
September 11th, 2011, 10:24 AM
Actually I just figured out that the native c++ section is recieving the pointer correctly, its just the assmbly code isnt reading it correctly. Did I use the wrong addressing mode?

Eri523
September 11th, 2011, 09:00 PM
Actually I just figured out that the native c++ section is recieving the pointer correctly, its just the assmbly code isnt reading it correctly. Did I use the wrong addressing mode?

Given that, I ignored the part of extracting the correct pointer from the .NET object and concentrated on how the inline assembly code works with the (presumably correct) pointer. I modeled your scenario with this really simplistic native program (the commented-out C++ function near the top of the code shows what the inline assembly part is supposed to mimic):


// Test8.cpp

#include <cstdio>

char achFormat[] = "%d\n";

/*

void DoIt(int *pn, int nSize)
{
printf(achFormat, nSize);
for (int i = 0; i < nSize; ++i)
printf(achFormat, pn[i]);
}

*/

void DoIt(int *pn, int nSize)
{
__asm {
mov esi, offset achFormat
push dword ptr nSize
push esi
call dword ptr [printf]
add esp, 8
xor edi, edi
loop000:
push dword ptr [pn + edi * 4]
push esi
call dword ptr [printf]
add esp, 8
inc edi
cmp edi, dword ptr nSize
jb loop000
}
}

int main()
{
int an[10];
for (int i = 0; i < 10; ++i)
an[i] = 101 + i;
DoIt(an, 10);
}


Not much to my surprise, that doesn't work either. Thorough debugging revealed that the line highlighted in red does not push the ints in the array one by one, what it should, but instead pushes the pointer to the int array, then the dword following the pointer in memory, then yet the next dword etc. Effectively, it gives me sort of a stack dump.

I did lots of syntactic twisting, and while doing so, remembered that I already encountered exactly this problem earlier. The only solution I found back then (as well as now) was to first "park" the pointer to the array in a register, then do the indexed addressing from there. So, replacing the higlighted line above by the following makes the code work:


mov ebx, dword ptr pn
push dword ptr [ebx + edi * 4]


Since the pointer pn itself is constant, the first one of the two lines can be moved in front of the loop to optimize the code a bit. So, the final DoIt() function looks like this:


void DoIt(int *pn, int nSize)
{
__asm {
mov esi, offset achFormat
mov ebx, dword ptr pn
push dword ptr nSize
push esi
call dword ptr [printf]
add esp, 8
xor edi, edi
loop000:
push dword ptr [ebx + edi * 4]
push esi
call dword ptr [printf]
add esp, 8
inc edi
cmp edi, dword ptr nSize
jb loop000
}
}


Note that actually you did not use the wrong addressing mode. In the real MASM that would have worked. It's just that silly inline assembler that's not able to translate it properly into object code. (Or maybe I've overlooked yet another daring syntactical twist that makes it work. If someone knows it, please post.)

Also note that you should not simply bail out of inline assembly code with a RET instruction. This bypasses the C++ compiler's stack clean-up code and is likely to corrupt the stack, especially if you have local variables like your count and car. Doesn't that crash (probably upon return from the function that called your assembly routine at the latest)? EDIT: Ok, you're not using count at all so it may be eliminated by optimizations, and perhaps car is assigned to an unused register by the compiler, so you got lucky. The caveat of being careful with explicit RETs in inline assembly code holds true, though.

Finally, on the side note: You actually may use much more CPU registers in your inline assembly code without preserving them on the stack like one might think. See http://msdn.microsoft.com/en-us/library/k1a8ss06(v=VS.100).aspx

EDIT2: Before someone complains: Yes, my code (just like the OP code, BTW) makes the assumption that the size parameter is > 0, in contrast to the for loop in the C++ reference function. This was intended because a post-check loop looks considerably smoother in assembly language and is easier to understand, at least on first sight.

AKRichard
September 17th, 2011, 03:21 PM
Note that actually you did not use the wrong addressing mode. In the real MASM that would have worked. It's just that silly inline assembler that's not able to translate it properly into object code. (Or maybe I've overlooked yet another daring syntactical twist that makes it work. If someone knows it, please post.)


I was wondering why it worked in the rad ide and not in visual studio. So not only do I ave to fight to understand the addressing modes, but Im going to have to fight the compiler too? If I put all this in an ASM file will it expect the code to be more like in visual studio? The only problem I had with putting the code in an ASMfile was that I couldnt get the c++ code to see it, oh well back to reading I guess.

Also note that you should not simply bail out of inline assembly code with a RET instruction. This bypasses the C++ compiler's stack clean-up code and is likely to corrupt the stack, especially if you have local variables like your count and car. Doesn't that crash (probably upon return from the function that called your assembly routine at the latest)? EDIT: Ok, you're not using count at all so it may be eliminated by optimizations, and perhaps car is assigned to an unused register by the compiler, so you got lucky. The caveat of being careful with explicit RETs in inline assembly code holds true, though.

I pretty much just copied over the code straight out o the rad ide without even thinking about it, but you were right, it was causing it to crash (once I got it to run that far).

Finally, on the side note: You actually may use much more CPU registers in your inline assembly code without preserving them on the stack like one might think. See http://msdn.microsoft.com/en-us/library/k1a8ss06(v=VS.100).aspx

I wasnt sure how the operating system, clr, or anything else handled the registers while switching between threads, so I started saving each register I used and restoring it at the end.

I am going to start trying to get it working in an ASM file before going further. I have read that I should be able to get the express edition to compile 64 bit code, however, the configuration manager still only shows win32 and now Itanium build options, unfortunately I need the amd64 (X64) compiler and have not figured out where I went wrong. If I get that part working then I should be able to do these calculations 256 bits at a time using (or 128 bits if not floating point) with the sse/mmx instructions, that should really kick it into high gear then.

Anyways, your solution worked beautifully for the inline assembly, Thanks for your help again

Eri523
September 17th, 2011, 06:28 PM
[...] If I put all this in an ASM file will it expect the code to be more like in visual studio? The only problem I had with putting the code in an ASMfile was that I couldnt get the c++ code to see it, oh well back to reading I guess.

I don't quite get what you mean by "more like in visual studio". You probably know that assembly language to be processed by MASM looks a bit different than that for the inline assembler. We have also learned, at the latest in this thread, that it may behave differently to some extent.

As to the visibility of your assembly routines: While free functions in C/C++ have external linkage unless you explicitly declare them static, procedures (and other symbols) in an .asm module are invisible to the outside world unless you explicitly declare them public using the PUBLIC directive. Of course you also need to prototype the functions to be called on the C++ side, either in an .h file or the .cpp file itself, but I think there's no need to tell you this.

While VC++ Express does list .asm files as "Visual Studio Files" when you add an existing element to your project, I'm not sure whether adding one to the project would actually work or fail due to the "no mixed-language projects" limitation. I haven't tried that yet.

I wasnt sure how the operating system, clr, or anything else handled the registers while switching between threads, so I started saving each register I used and restoring it at the end.

Thread switching is handled entirely transparent by the OS, and the CLR isn't involved unless you explicitly call managed code. And, as you can see in the MSDN article I linked to, preserving CPU registers for the code outside of your inlne assembly routine is of less concern than what registers that hold your data to preserve across calls to C++ routines. Actually, in my code in post #3, I carelessly assumed printf() would preserve EBX though actually I'm not completely sure about that.

Unless that hasn't changed since the last time I read MS documentation about that and IIRC, the only thing you can really be sure of is that C++ functions you call preserve ESI and EDI for you (out of the more-or-less-general-purpose registers EAX, EBX, ECX, EDX, ESI and EDI) because they are the preferred registers used by the compiler to assign local variables to, and that you better don't rely on EAX and EDX being preserved because these are used to return values from functions and thus are treated in a rather volatile manner by the compiler.

I think details about that can be found in the docuntation on the calling conventions, and they may vary between the individual calling conventions. Also, when writing external assembly routines, you don't have the convenience of the compiler taking care of the registers outside your code, like in inline assembler. Instead you need to obey the rules of the calling convention.

I have read that I should be able to get the express edition to compile 64 bit code, however, the configuration manager still only shows win32 and now Itanium build options, unfortunately I need the amd64 (X64) compiler and have not figured out where I went wrong. If I get that part working then I should be able to do these calculations 256 bits at a time using (or 128 bits if not floating point) with the sse/mmx instructions, that should really kick it into high gear then.

As I said, I was thinking the Express Edition wouldn't support 64-bit at all, whether Itanium or x64. I don't have a 64-bit OS running, though.

But did you now you not only can use SSE and MMX instructions in 32-bit code as well, you don't even need to touch any sort of assembly language in order to use them: You can use compiler intrinsics (http://msdn.microsoft.com/en-us/library/y0dh78ez.aspx). There have been some threads about that here on CG, in the Non-VC++ and/or VC++ sections, with posts by really competent guys who have used that already (as opposed to myself). You'll probably be able to find them using a forum search.

And if you don't have decent MASM documentaion already, you may want to have a look at the attachment to http://www.codeguru.com/forum/showthread.php?p=2019421. Unlike what we're discussing here it's a stand-alone Windows app written in assembly, but it's Win32 and perhaps can give you a first impression of the directives needed to properly set up the memory model and assign code and data to the respective segments.

I didn't check the MASM link in that post, but perhaps there's documentation available there. Actually I, myself, only have documentation on the ancient 20-years-old MASM 6.1 here (plus newer documents on Intel CPUs) but that actually has always been enough for the small amount of assembly language I'm doing nowadays.

AKRichard
September 18th, 2011, 02:43 AM
I don't quite get what you mean by "more like in visual studio". You probably know that assembly language to be processed by MASM looks a bit different than that for the inline assembler. We have also learned, at the latest in this thread, that it may behave differently to some extent.



I meant to ask if the asm code will need to be structured the way that visual studio is requiring it or if it would need to be structured the way masm expects it. The differences are subtle and with my limited understanding of assembly, it could take quite some time to work out those differences.

While VC++ Express does list .asm files as "Visual Studio Files" when you add an existing element to your project, I'm not sure whether adding one to the project would actually work or fail due to the "no mixed-language projects" limitation. I haven't tried that yet.


I have tried it and it created an obj file out of the ASM file, but I am not sure if there were any problems with it as I must have done something wrong with my prototypes or something. Thats what I meant when I said I couldnt get the c++ code to see the assembly stuff.

But did you now you not only can use SSE and MMX instructions in 32-bit code as well, you don't even need to touch any sort of assembly language in order to use them: You can use compiler intrinsics. There have been some threads about that here on CG, in the Non-VC++ and/or VC++ sections, with posts by really competent guys who have used that already (as opposed to myself). You'll probably be able to find them using a forum search.


To be honest, I only had a very basic understanding of what the SSE and MMX extensions where before I started down this road, and I had never even heard of compiler intrinsics till a week ago while trying to solve another problem (and I didnt even think to look there after reading about SSE/MMX). I do believe if they are in there, I will write the routines in C++ first to work out the bugs before converting them to assembly.

I didn't check the MASM link in that post, but perhaps there's documentation available there. Actually I, myself, only have documentation on the ancient 20-years-old MASM 6.1 here (plus newer documents on Intel CPUs) but that actually has always been enough for the small amount of assembly language I'm doing nowadays.

Well your small amount that you do is considerably more than I have done. I have created a few small routines in assembly, but nothing like what I am attempting now. My little assembly exposure was on an old 286 machine, which was quite a bit simpler then the amd quad core Im running now.

Thanks again

Eri523
September 18th, 2011, 06:11 AM
I meant to ask if the asm code will need to be structured the way that visual studio is requiring it or if it would need to be structured the way masm expects it. The differences are subtle and with my limited understanding of assembly, it could take quite some time to work out those differences.

Though there are differences (as we already have seen), the assembly source code looks rather similar between inline assembler and MASM. I think the main difference are the directives, which the inline assembler doesn't know at all.

I have tried it and it created an obj file out of the ASM file, but I am not sure if there were any problems with it as I must have done something wrong with my prototypes or something. Thats what I meant when I said I couldnt get the c++ code to see the assembly stuff.

Once you managed to expose your routine from the assembly language module, it's more or less business as usual on the C++ side. If your assembly routine has __cdecl calling convention, the C++ prototype is much like that of any C++ free function.

EDIT: Oh, and of course the prototype on the C++ side needs to be declared extern "C", otherwise the C++ compiler will apply name mangling which is incompatible with assembly language modules (unless you manually mimic it at the assembly side, but that's quite tideous).

What are the symptoms of your C++ code not seeing the assembly code? If you get C++ compiler errors, it's a problem with the prototype. The assembly module's .obj file isn't involved at all at that stage. A cause of linker errors would likely be failure to (properly) expose the assembly routine, or VS doesn't even attempt to link in the assembly module, which would mean VS doesn't properly handle it if you already managed to add the module to your project. You can check the linker command line in the project properties whether the .obj files shows up there. If not, you may add it as an additional dependency to the project. Mismatched or improperly implemented calling conventions usually result in runtime problems, probably crashes.

AKRichard
September 18th, 2011, 10:36 AM
Unfortunately I dont have the exact file I was using when tried compiling with the asm file before, but I wasnt getting any errors, I just couldnt get the ide to "See" the assembly language class. I had used the stdcall calling convention in the assembly code

.model flat, stdcall ; default STDCALL calling convention

and had the prototypes such as:

Addition PROTO STDCALL :DWORD, :WORD, :DWORD, :WORD, :DWORD

I do not believe I had the extern "C" on the c++ side. So that will be something for me to watch for.

but the visual studio ide would not bring up any of the members when I tried instantiating the class written in assembly, nor did anything show up in the object browser. When I tried to compile it, the compiler spat out a few errors to the affect that the class did not exist. However, using the exact same code within the RADasm ide, I was able to call the members within the class just fine.
.
I didnt spend very much time with it before deciding to go for the inline assembly, I just cannot compile to 64 bit with it. Thats too bad to, I seem to be having a hard time getting the express edition to recognize the 64bit amd compiler despite several articles that describe the procedure (including one msdn article specifically for enabling 64 bit builds on vc express 2008) with no success yet. Once I do have the compilers working correctly, I will attempt using an asm file again and be able to post a more detailed description of whats going on, that is if I have the same difficulty (lol Im teaching myself assembly! I am sure I will).

Anyhow, my environment isnt setup correctly yet, and I have a ton of reading to do before going back and trying anything else yet. I thought I had a clear plan when I started out on ths, but have learned the way I wanted to do it will not work (with the inline assembly and compiling to 64 bit) and I have to look into the intrinsic functions to see if they would provide what I need

Eri523
September 18th, 2011, 05:15 PM
[...]
and had the prototypes such as:

Addition PROTO STDCALL :DWORD, :WORD, :DWORD, :WORD, :DWORD

Yes, that's a prototype. But in order to define an assembly language function, of course you'd need to write a PROC containing the implementation.

I do not believe I had the extern "C" on the c++ side. So that will be something for me to watch for.

__stdcall has a different and much simpler name mangling than __cdecl, yet it has one. You'd either need to account for that on the assembly side, or disable it by declarig the function extern "C". See http://msdn.microsoft.com/en-us/library/zxk0tw93(v=VS.100).aspx

but the visual studio ide would not bring up any of the members when I tried instantiating the class written in assembly, nor did anything show up in the object browser. When I tried to compile it, the compiler spat out a few errors to the affect that the class did not exist. However, using the exact same code within the RADasm ide, I was able to call the members within the class just fine.

Your assembly language functions should show up in class view (under global functions and variables) as soon as you've properly prototyped them. The assembly language module itself isn't even needed for that.

Looks like you were using HLA (of which I hardly know more than that it exists) under RadASM. That does support classes, MASM does not. From MASM you'll only get free functions and perhaps some global variables. That's usually enough, because only supporting functions are written in assembly language, or you may write a thin C++ wrapper around your assembly routines.

I didnt spend very much time with it before deciding to go for the inline assembly, I just cannot compile to 64 bit with it.

That's by design. The MS 64-bit compiler simply doesn't support inline assembly (regardless of the edition), and AFAIK it never will.

[...] I have to look into the intrinsic functions to see if they would provide what I need

Chances are they do. Also, they work under both 32-bit and 64-bit. And if your code uses them intensively, you may not even experience a significant performance gain by porting the code to assembly language.