I have a problem accessing a COM object from a 64-bit COM server written in C++ from a .NET project (the 32-bit version works well). It is a problem similar to the one described here Troubleshooting an x64 com interop marshaling issue. I have a COM method that takes as parameter an array of structures with longs and BSTRs. When the call returns it works OK if the call was made from a native module, but when it was made from a managed (C#) assembly, I get an access violation. If the strings are not populated in the struct then there is no exception.
The proxy/stub file start with the following:
32-bit
/* File created by MIDL compiler version 7.00.0500 */
/* at Thu Sep 22 17:52:25 2011
*/
/* Compiler settings for .\RAC.idl:
Oicf, W1, Zp8, env=Win32 (32b run)
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
//##MIDL_FILE_HEADING( )
#if !defined(_M_IA64) && !defined(_M_AMD64)
64-bit
/* File created by MIDL compiler version 7.00.0500 */
/* at Thu Sep 22 17:58:46 2011
*/
/* Compiler settings for .\RAC.idl:
Oicf, W1, Zp8, env=Win64 (32b run)
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
//##MIDL_FILE_HEADING( )
#if defined(_M_AMD64)
I tried both with the 32-bit and 64-bit version of midl.exe from Windows SDK v7.0A, but it generates the very same output. So the suggestion from the other thread didn't help. Any other ideas?
UPDATE:
The struct looks like this (I changed the names, the rest is identical):
[uuid(6F13C84D-0E01-48cd-BFD4-F7071A32B49F)] struct S
{
long a;
BSTR b;
long c;
BSTR d;
long e;
BSTR f;
BSTR g;
BSTR h;
BSTR i;
long j;
BSTR k;
long l;
BSTR m;
long n;
};
The method signature looks like this:
[id(54)] HRESULT GetListOfStructs(SAFEARRAY(struct S)* arrRes);
I actually have several such structs and methods like this. Obviously, all of them have the same problem.
Related
The application ZKAccess-3.5 has the ability to enumerate doors, and open or close the selected door.
The sdk has this incomplete API:
VARIANT_BOOL ACUnlock(LONG dwMachineNumber, LONG Delay);
VARIANT_BOOL GetDoorState(LONG MachineNumber, [in,out] LONG* State, [out,retval] VARIANT_BOOL* pVal);
How do I select the door I want to open/close? I have 2 doors. I expected a doorID parameter or something like that.
Device: inBio 260 / 2 doors
EDIT: PullSDK Wrapper in C#, Shows how to use PullSDK
Those functions are not a part of the PullSDK.
ZKTeco claims the standalone SDK includes PullSDK when It does not cover everything (example: ControlDevice function).
The native dll plcommpro.dll is the main dll for the PullSDK.
It exports the function ControlDevice, that can be used to lock/unlock doors, but the documentation is confusing, I'm not sure how to use it, I will try to decompile ZKAccess3.5 to find that out.
int ControlDevice(HANDLE handle, LONG OperationID, LONG Param1, LONG Param2, LONG Param3, LONG Param4, const char *Options)
ZKTeco is not good at what it does (hardware, software, protocols, sdk, docs).
I am using webclient to get the image data from a url, and trying to generate a video with it like so:
//http://www.codeproject.com/Articles/7388/A-Simple-C-Wrapper-for-the-AviFile-Library
WebClient client = new WebClient();
System.Net.WebRequest request = System.Net.WebRequest.Create(images);
System.Net.WebResponse response = request.GetResponse();
System.IO.Stream responseStream = response.GetResponseStream();
Bitmap bitmap = new Bitmap(responseStream);
//create a new AVI file
AviManager aviManager = new AviManager(#"C:\Users\Laptop\Documents\tada.avi", false);
//add a new video stream and one frame to the new file
//set IsCompressed = false
VideoStream aviStream = aviManager.AddVideoStream(false, 2, bitmap);
aviManager.Close();
But it cuts out on the following. In the library on this line
int result = Avi.AVIFileCreateStream(aviFile, out aviStream, ref strhdr);
I get the following error:
System.AccessViolationException: 'Attempted to read or write protected
memory. This is often an indication that other memory is corrupt.'
You probably have "Any CPU" at the moment...so your app then gets compiled/run as a 64bit process (on a 64bit Windows version).
The problem appears to be that that AVI Wrapper library was probably never tested with a 64bit .NET app....it hasn't defined the "pinvoke" definitions properly so that the parameters are correctly pushed on/popped off the stack when making the 64 bit API calls.
Change your project settings "platform target" to x86...so that you can avoid the issue....and can call the "avifil32.dll" albeit in 32bit mode.
Windows does ship with a 32bit and 64bit of that AVI library so in theory it is possible to call an AVI library when you are a 64bit process....but you need to define the interop/marshalling pinvoke properly.
c:\windows\system32\avifil32.dll (64bit)
c:\windows\syswow64\avifil32.dll (32bit)
In 32bit (Microsoft uses the ILP32 data model)...
an int is 4 bytes
a pointer is 4 bytes
In 64bit (Microsoft uses the LLP64 or P64 data model)....
an int is (still) 4 bytes
a pointer is (now) 8 bytes
(see https://msdn.microsoft.com/en-us/library/windows/desktop/aa384083(v=vs.85).aspx)
The mistake that often happens is that "pinvoke" definitions have used "int" when defining pointer types, instead of the more correct IntPtr type.
Thus, the "call" works ok on 32bit (because an "int" is the same size as a "pointer")....while on 64bit they are different sizes.
Other things change when you are 64bit too...such as the default boundary alignment...this can change offsets of types within structures - so you have to be careful when you are defining your pinvoke c# structures...so they match.
In case you are interested for the function call AVIFileCreateStream its WIN32 signature is as follows:
STDAPI AVIFileCreateStream(
PAVIFILE pfile,
PAVISTREAM *ppavi,
AVISTREAMINFO *psi
);
And the "types" of its parameters are:
typedef IAVIFile *PAVIFILE; // i.e. just a pointer
typedef IAVIStream *PAVISTREAM; // i.e. just a pointer
typedef struct {
DWORD fccType;
DWORD fccHandler;
DWORD dwFlags;
DWORD dwCaps;
WORD wPriority;
WORD wLanguage;
DWORD dwScale;
DWORD dwRate;
DWORD dwStart;
DWORD dwLength;
DWORD dwInitialFrames;
DWORD dwSuggestedBufferSize;
DWORD dwQuality;
DWORD dwSampleSize;
RECT rcFrame;
DWORD dwEditCount;
DWORD dwFormatChangeCount;
TCHAR szName[64];
} AVISTREAMINFO;
That wrapper library defined the NET "pinvoke" to AVIFileCreateStream using this:
//Create a new stream in an open AVI file
[DllImport("avifil32.dll")]
public static extern int AVIFileCreateStream(
int pfile,
out IntPtr ppavi,
ref AVISTREAMINFO ptr_streaminfo);
Immediately, you can see that the first parameter is defined incorrectly.
When the "call is made"....only 4 bytes will be placed onto the stack for the first parameter...instead of 8, then for the second parameter (which is a pointer to a pointer) 8 bytes are pushed (because IntPtr was used) (the address where to "write" an address), third parameter is an address to an AVISTREAMINFO structure.
Thus when AVIFileCreateStream is called it accesses those parameters on the stack, but they are basically junk.....it will be trying to use a pointer with the wrong value (i.e. only 4 bytes of the (first parameters) pointers address has come through on the stack...and the remaining 4 bytes (of the 8 byte pointer) are filled from the "next" thing on the stack...thus the pointer address is highly likely to be garbage....which is why you get the access violation.
The way it should have been defined is something like this (note there are other ways to achieve the same):
[DllImport("avifil32.dll", SetLastError=true)]
public static extern int AVIFileCreateStream(IntPtr pfile, out IntPtr ppavi, ref AVISTREAMINFO psi);
I'm finding that when pinvoking GetBinaryType from managed code, I'm getting the opposite result of calling GetBinaryType from native code on the same machine.
I've borrowed the marshalling declaration from elsewhere:
public enum BinaryType : uint
{
SCS_32BIT_BINARY = 0, // A 32-bit Windows-based application
SCS_64BIT_BINARY = 6, // A 64-bit Windows-based application.
SCS_DOS_BINARY = 1, // An MS-DOS – based application
SCS_OS216_BINARY = 5, // A 16-bit OS/2-based application
SCS_PIF_BINARY = 3, // A PIF file that executes an MS-DOS – based application
SCS_POSIX_BINARY = 4, // A POSIX – based application
SCS_WOW_BINARY = 2 // A 16-bit Windows-based application
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetBinaryType(
string lpApplicationName,
out BinaryType dwBinType
);
and then call the function as
bool is64bit = false;
BinaryType binType = BinaryType.SCS_32BIT_BINARY;
// Figure out if it's 32-bit or 64-bit binary
if (GetBinaryType(phpPath, out binType) &&
binType == BinaryType.SCS_64BIT_BINARY)
{
is64bit = true;
}
For 32-bit native binaries, GetBinaryType returns BinaryType.SCS_64BIT_BINARY (6), and for 64-bit native binaries, returns BinaryType.SCS_32BIT_BINARY (0).
To verify, I wrote a native command line tool, and ran it against the same binaries.
PCWSTR rgBinTypes[] = {
L"SCS_32BIT_BINARY", // 0
L"SCS_DOS_BINARY", // 1
L"SCS_WOW_BINARY", // 2
L"SCS_PIF_BINARY", // 3
L"SCS_POSIX_BINARY", // 4
L"SCS_OS216_BINARY", // 5
L"SCS_64BIT_BINARY", // 6
};
int _tmain(int argc, _TCHAR* argv[])
{
DWORD binType;
if (argc < 2)
{
wprintf(L"Usage: %S <binary-path>\n", argv[0]);
goto Cleanup;
}
if (!GetBinaryType(argv[1], &binType))
{
wprintf(L"Error: GetBinaryType failed: %d\n", GetLastError());
goto Cleanup;
}
wprintf(L"Binary type: %d (%s)\n", binType, binType < 7 ? rgBinTypes[binType] : L"<unknown>");
Cleanup:
return 0;
}
The command line tool correctly returns 0 (SCS_32BIT_BINARY) for 32-bit native binaries, and 6 (SCS_64BIT_BINARY) for 64-bit native binaries.
I found one reference to someone else having this same issue, but no answer was provided: https://social.msdn.microsoft.com/Forums/en-US/fc4c1cb4-399a-4636-b3c3-a3b48f0415f8/strange-behavior-of-getbinarytype-in-64bit-windows-server-2008?forum=netfx64bit
Has anyone else run into this issue?
I realize I could just flip the definitions in my Managed enum, but that seems awfully kludgy.
This is a WinAPI bug/developer's oversight. You may find this related question useful to read, and it's top answer may help you find the appropriate workaround,
Use a separate 64 bit process, and some IPC, to retrieve the information.
Use WMI to get the module file name.
Use QueryFullProcessImageName.
I ended up going for a completely different workaround. This answer about PE headers mentions the PE headers among 32 and 64 bit Windows executables. You can completely circumvent the WinAPI checking, and have your target executable checked via reading it in the Binary mode and checking if it matches the PE signature.
Sadly, there isn't much info on the problem online. I remember seeing this problem on some forum, where is was clearly listed as bug, but this was about ~10 years ago. I hope as we discuss this problem, more people become aware of it.
I am trying to build a COM Library in C++, using a C# project for testing. Some methods need to return strings to the caller. On calling these methods from C# I get this: "Access violation reading at location ..."
This is the C++ code from my testproject (apart from all the stuff generated by VS 2010 ATL)
//COMTest.idl
[id(1)] HRESULT Test([out,retval] BSTR* ret);
//Program2.h
STDMETHOD(Test)(BSTR* ret);
//Program2.cpp
STDMETHODIMP CProgram2::Test(BSTR* ret)
{
BSTR tmp = (BSTR)CoTaskMemAlloc(sizeof(wchar_t) * 2);
tmp[0] = L'H';
tmp[1] = L'\0';
*ret = (BSTR)tmp;
return S_OK;
}
In C# I just referenced the DLL from the COM-Tab, turned "Embed Interop Types" off, because it caused errors, and ran this:
static void Main(string[] args)
{
COMTestLib.Program2Class instance = new COMTestLib.Program2Class();
string tmp = instance.Test(); //Where the error occurs
Console.WriteLine(tmp); //This is not reached
Console.Read();
}
The error occurs after leaving the Test-Method. I debugged the C++ code from within my C# project and the values are placed in the correct locations. I do not get the error if I try to return 0 (gives null in C#), even if I still allocate memory like in the example.
I can not make sense of the address, which the access violation complains about. It is neither the address I am allocating nor any other address used in the method. What also seems weird to me is that the CoTaskMemAlloc-Function always returns addresses with the first byte set to zero (0x00XXXXXX), but that might just be a COM thing.
I ran out of ideas and I cant find much information on this (except for basic COM tutorials) anywhere. Can anyone help?
BSTRs require extra memory (to keep track of the string len) so must use SysAllocString() function to allocate BSTRs (or use one of the "smart" BSTR classes).
So your original code should read like:
//Program2.cpp
STDMETHODIMP CProgram2::Test(BSTR* ret)
{
*ret = SysAllocString(L"H");
return S_OK;
}
A good reading about BSTRs: http://blogs.msdn.com/b/ericlippert/archive/2003/09/12/52976.aspx
Check that your COM project and test project are both STA. Check the bitness too. What if you replace BSTR by LPSTR ?
To sum it up, I need to do this:
12345(hWnd) -> "C:\setup.exe"
Right now, I am using GetProcessImageFileName to retrieve the Kernel Device Path of a process handle. I'm retrieving the handle using OpenProcess, passing it the PID. The PID (which I also need) is being retrieved using GetWindowThreadProcessId.
However, this gets me to a string like:
\Device\Harddisk1\setup.exe
At this point, I enumerate all drives on the system using DriveInfo.GetDrives(), and then call QueryDosDevice. Finally, I can do some string-manipulation magic, and "boom," I have my path.
Ok, so my issues:
This process breaks down on Network drives.
All I really want is QueryFullProcessImageName on XP
There HAS to be a better way to do this. Please enlighten me, oh gods of WIN32API!
The obvious question would be why you don't just use QueryFullProcessImageName, if that's what you want? Do you need compatibility with older versions of Windows?
The closest equivalent to QueryFullProcessImageName that's available on XP is probably GetModuleFileNameEx. I'd probably detect whether QueryFullProcessImageName is available and use it if possible, otherwise fall back to GetModuleFileNameEx.
Edit: While GetModuleFileNameEx isn't 100% dependable at retrieving the name of the executable for every possible process, it does work at least a fairly substantial part of the time. Here's a quick bit of test code I put together:
#include <windows.h>
#include <psapi.h>
#include <iterator>
#include <iostream>
#include <string>
#include <map>
std::string getfilename(DWORD pid) {
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
static int winver;
char path[256]= {0};
DWORD size = sizeof(path);
if (winver==0)
winver = GetVersion() & 0xf;
#if WINVER >= 0x600
if (winver >= 6)
QueryFullProcessImageName(process, 0, path, &size);
else
#endif
if (!GetModuleFileNameEx(process, NULL, path, sizeof(path)))
strcpy(path, "Unknown");
return std::string(path);
}
typedef std::map<DWORD, std::string> win_map;
namespace std {
ostream &operator<<(ostream &os, win_map::value_type const &v) {
return os << v.first << ": " << v.second;
}
}
BOOL CALLBACK show_info(HWND window, LPARAM lParam) {
win_map &exes = *(win_map *)lParam;
DWORD pid;
GetWindowThreadProcessId(window, &pid);
exes[pid] = getfilename(pid);
return true;
}
int main() {
win_map exes;
EnumWindows(show_info, (LPARAM)&exes);
std::copy(exes.begin(), exes.end(), std::ostream_iterator<win_map::value_type>(std::cout, "\n"));
return 0;
}
The results of a quick test are somewhat interesting. Compiled as 32-bit code, the version using QueryFullProcessImageName found 33 processes with top-level windows, and found names for 31 of those executables. The version using GetModuleFileNameEx, also found 33 processes, but only found names for 21 of the executable. If, however, I compile it as 64-bit code, either version finds filenames for 31 out of 33 executables (and the same two fail). Given the frequency with which you see XP/x64, that's probably of little consequence, but I found it interesting nonetheless.
In any case, even the least capable version (32-bit/GMFNE) found names for ~2/3rds of the files. While that's certainly not what you'd hope for, it's certainly better than nothing.
It must be possible to retrieve the file path to running processes since Sysinternals' Process Explorer does it. Of course, Process Explorer uses NtQueryInformationProcess, which isn't a supported function. (On the other hand, since you're specifically targeting XP and would use QueryFullProcessImageName on Vista and later, the fear of the API becoming unavailable in a future version of Windows is probably not a concern.)
CodeProject has an article about how to use NtQueryInformationProcess.