I have been trying to do a basic ActiveX component marshalling for across thread access. I do it through a Global Interface Table (GIT) . I have a simple MFC dialog to which I add an arbitrary AciveX control such as IDC_PDF.
Inserting activeX control to MFC dialog in C++
After that I add the variable representing this control to the code. This effectively adds a pdf1.h and pdf1.cpp to the project. At initialization of the dialog inside OnInitDialog() I try to marshal the interface to this ActiveX component so that it could be used from another thread without STA apartment violation.
bool CLMC_mfcDlg::CreateMarshalledController()
{
::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CComPtr<IGlobalInterfaceTable> pGIT;
// get pointer to GIT
CoCreateInstance(CLSID_StdGlobalInterfaceTable,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void **)&pGIT);
// get IID and IUnknown interface pointer
REFIID iid = Pd_OCX_fControl.GetClsid();
IUnknown* active_x_pointer = Pd_OCX_fControl.GetControlUnknown();
// register interface inside the GIT
if (active_x_pointer != NULL) {
HRESULT hr = pGIT->RegisterInterfaceInGlobal(active_x_pointer, iid, &dwCookie);
if (SUCCEEDED(hr))
{
// OK, wir haben das interface im GIT registriert
assert(dwCookie != 0);
}
else
dwCookie = 0;
//pGIT->Release();
}
else
dwCookie = 0;
active_x_pointer->Release();
return dwCookie!=0;
}
As a result the value of the dwCookie is set to 256, which though not 0, still feels like an error value.
When I try to get the marshalled interface from another thread. The marshalled interface received is 0x0000.
bool CLMC_mfcDlg::FetchMarshalledController()
{
HRESULT res = ::OleInitialize(NULL);
switch (res)
{
case S_OK:
break;
case OLE_E_WRONGCOMPOBJ:
case RPC_E_CHANGED_MODE:
return false;
}
CComPtr<IGlobalInterfaceTable> pThreadGIT;
CoCreateInstance(CLSID_StdGlobalInterfaceTable,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void **)&pThreadGIT);
REFIID iid = Pd_OCX_fControl.GetClsid();
pThreadGIT->GetInterfaceFromGlobal(
dwCookie, iid, (void**)&pMarshalledOCX);
pThreadGIT->RevokeInterfaceFromGlobal(dwCookie);
return pMarshalledOCX != nullptr;
}
What am I doing wrong? I am working with a standard ActiveX, using standard marshalling patern. Anybody got this to work?
Aurora was correct. To get a proper IID you need to find the interface in registry using oleview.exe:
you use the finding to define interface IID in your code:
static REFIID const intf_id
= { 0x5CD5C9C3, 0x0CD7, 0x453A,{ 0x8D, 0x27, 0xE3, 0xBB, 0x32, 0xB7, 0xEA, 0xFC } };
you get the interface pointer for it like this:
IUnknown * pUnknown = CBaldorOCXCard.GetControlUnknown();
// get _DMintControllerCtrl interface pointer
void* IMint = NULL;
pUnknown->QueryInterface(intf_id, (void **)&IMint);
The interface pointer and IID can now be used in marshaling.
(The problem how you work with this interface pointer without a wrapper class:) Still looking for answer)
Related
C# code:
class Hello{
public void helloWorld(char[] chars){
//do something
}
}
C++ code to call C#:
MyCSDLL::Hello* hello;
//init hello, some calls are ok.
char* myCharPtr;
//init with message
HRESULT result = hello->helloWorld(safeArray, (MyCSDLL::_MyRetVal) _retValPtr);
Adapting from How to create and initialize SAFEARRAY of doubles in C++ to pass to C#
void createSafeArray(SAFEARRAY** saData, char* charPtr)
{
char* iterator = charPtr;
SAFEARRAYBOUND Bound;
Bound.lLbound = 0;
Bound.cElements = 10;
*saData = SafeArrayCreate(VT_R8, 1, &Bound);
char HUGEP *pdFreq;
HRESULT hr = SafeArrayAccessData(*saData, (void HUGEP* FAR*)&pdFreq);
if (SUCCEEDED(hr))
{
do {
*pdFreq++ = *iterator;
} while (*iterator++);
}
}
How to call hello->helloWorld()? it is expecting SAFEARRAY*. The current code gives 80131538 error. How to fix it?
C++ Project is not CLR.
Let's suppose the C# code is this:
namespace ClassLibrary1
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Hello
{
public void helloWorld(char[] chars)
{
...
}
}
}
Then, you can call it with this C/C++ code, for example:
#import "C:\mycode\ClassLibrary1\bin\Debug\classlibrary1.tlb" raw_interfaces_only
using namespace ClassLibrary1;
HRESULT CallHello(wchar_t* charPtr, int count)
{
CComPtr<_Hello> p;
HRESULT hr = p.CoCreateInstance(__uuidof(Hello));
if (FAILED(hr))
return hr;
SAFEARRAY* psa = SafeArrayCreateVector(VT_UI2, 0, count);
if (!psa)
return E_OUTOFMEMORY;
LPVOID pdata;
hr = SafeArrayAccessData(psa, &pdata);
if (SUCCEEDED(hr))
{
CopyMemory(pdata, charPtr, count * 2); // count is the number of chars
SafeArrayUnaccessData(psa);
hr = p->helloWorld(psa);
}
SafeArrayDestroy(psa);
return hr;
}
.NET's char type is unicode, so the binary size is two bytes, the C equivalent is wchar_t (or unsigned short, etc...). So the safearray element type must match that, that's why I used VT_UI2 (VT_R8 that you used is Real of size 8 bytes, so it's equivalent to .NET's double type).
If you really want to use C's char, then you must do some kind of conversion to a 2-byte character.
Also, you can use the SafeArrayCreateVector function which directly allocates a 1-dimension safe array. Don't forget to call cleanup methods.
OK so here is my source code this is from a tutorial i found online in creating your first DX window. so i copied it by typing it out to try and remember better. Anyways the problem i am having is that I cannot get the program to build in VS, I have tried changing the Linker subsystem to windows, didnt work.
ERROR Recieved
1>MSVCRTD.lib(crtexew.obj) : error LNK2019: unresolved external symbol _WinMain#16 referenced in function ___tmainCRTStartup
1>e:\documents\visual studio 2013\Projects\Project1\Debug\Project1.exe : fatal error LNK1120: 1 unresolved externals
Heres external link to code https://gist.github.com/bANNji/24ebedaf5a72f2003d29
//include the basic windows header files and the Direct3dD Header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
// Include the DIrect3D library file
#pragma comment (lib, "d3d9.lib")
// global declarations
LPDIRECT3D9 d3d; // The pointer to our Direct3D Interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
// functiuon prototypes
void initD3D(HWND hWnd); // Sets up and initializes Direct3D
void render_frame(void); // Renders a single frame
void cleanD3D(void); // Closes Direct3D and releases memory
// The window proc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// this function initializes and prepares direct3d for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // Create the Direct3D Interface
D3DPRESENT_PARAMETERS d3dpp; // create a stuct to hold verious device information
ZeroMemory(&d3dpp, sizeof(d3dpp)); //Clear out the stuct for usee
d3dpp.Windowed = TRUE; // Program windowed not full screen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // Discard old frames
d3dpp.hDeviceWindow = hWnd; // Set the window to be used by Direct3D
// Create a deviced calss using this information and information from the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
}
// THE FUN STUFF
// this is the function used to render a single frame
void render_frame(void)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
d3ddev->BeginScene(); // this begins the 3D Scene
// do 3D Rendering on the back buffer here...
d3ddev->EndScene(); // ends the 3D Scene
d3ddev->Present(NULL, NULL, NULL, NULL); // This displays the created frame
}
// THis is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
d3ddev->Release(); // close and release the 3D Device
d3d->Release(); //Close and release Direct3D
}
The error means exactly what it says: You don't have a WinMain. I would strongly suggest you first learn how to wield the Win32 API to create and manipulate windows by themselves before you try to use them with Direct3D. It is heavily documented on MSDN and pretty easy to grasp. Here's a quick jumble of sample code (incomplete, obviously) to help give you a reference point to get started:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HRESULT hr = CoInitializeEx(NULL,COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
WNDCLASSEX wndClass;
ZeroMemory(&wndClass,sizeof(wndClass));
wndClass.cbSize = sizeof(wndClass);
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hbrBackground = NULL;
wndClass.hCursor = LoadCursor(hInstance,LoadCursor(NULL,IDC_ARROW);
wndClass.hIcon = LoadIcon(hInstance,LoadIcon(NULL,IDI_APPLICATION);
wndClass.hIconSm = LoadIcon(hInstance,LoadIcon(NULL,IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpszMenuName = NULL;
wndClass.lpfnWndProc = winProc;
wndClass.lpszClassName = "derp";
wndClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&wndClass);
RECT win = {0,0,width,height};
AdjustWindowRectEx(&win,flags,FALSE,WS_EX_APPWINDOW);
int winWidth = win.right - win.left;
int winHeight = win.bottom - win.top;
hWnd = CreateWindowEx( WS_EX_APPWINDOW,
"derp",
"derpderp",
flags | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
GetSystemMetrics(SM_CXSCREEN)/2 - width/2,
GetSystemMetrics(SM_CYSCREEN)/2 - height/2,
winWidth,
winHeight,
HWND_DESKTOP,
NULL,
hInstance,
NULL
);
SetWindowPos( hWnd,
HWND_NOTOPMOST,
(GetSystemMetrics(SM_CXSCREEN)/2) - (winWidth/2),
(GetSystemMetrics(SM_CYSCREEN)/2) - (winHeight/2),
winWidth,
winHeight,
SWP_SHOWWINDOW
);
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
/* Do your other stuff here */
return 0;
}
And the winproc definition:
LRESULT CALLBACK App::WinProc( HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam )
{
switch(uMsg)
{
// NOTE: Obviously you'd need only those that matter to you, and you'd need to fill them in
case WM_KEYUP:
case WM_KEYDOWN:
case WM_CHAR:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_RBUTTONUP:
case WM_MOUSEMOVE:
case WM_SYSKEYDOWN:
case WM_MOUSEWHEEL:
case WM_ACTIVATE:
case WM_SYSKEYUP:
case WM_SYSCOMMAND:
case WM_CREATE:
case WM_CLOSE:
case WM_DESTROY:
App::GetInstance()->Shutdown();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
return 0;
}
I have come across a bug in CLR 2.0 that is resolved in CLR 4.0. It occurs when passing arrays across .NET COM interop and a COM exception is generated (E_FAIL). The details of how to reproduce this bug are below.
My problem is that it will be very difficult to force our clients to upgrade to .NET 4.0, so I'd like to implement a workaround. I can do so by calling obj->Release if I know that the bug has occured, but clearly this is dangerous if there's any chance of a false positive.
And so the question: What is the specification of this bug, and is it possible for me to identify it precisely?
I found .NET release notes for 4.0.1, 4.0.2, and 4.0.3, but the bug is not mentioned. There must be a significant changelist in the CLR transition from 2.0 to 4.0, and I guess this is not publicly available?
Obviously the code below makes little sense on its own, but it's the simplest reproduction of the issue that I could distill based on quite a large, complicated solution.
Thanks in advance for taking a look,
R
Important Edit
Unfortunately, I came back to try and investigate a little further, and it's possible that the code below does not actually reproduce the bug, which would be disappointing. However, in the actual application, the memory leak is clear. If someone is interested and I have time, I'll try to produce a valid example.
Code Overview
I have a .NET application, ConsoleApp.exe, here reproduced in C# although the original is F#. ConsoleApp.exe calls a managed assembly, managed.AComObject.dll, that exposes a COM object, AComObject. AComObject.get_TheObject() returns a VARIANT* pointing to a smart pointer, ASmartPtr, which allows me to override the AddRef and Release methods to observe the references held against the object.
When running ConsoleApp.exe with unmanaged code debugging enabled, I can see the reference counts on the SmartPtr. I change the CLR by adjusting the supportedRuntime property in ConsoleApp.exe.config with the following results:
v4.0 shows "DEBUGMSG::ASmartPtr::Release:0", at which point the SmartPtr is deleted.
v2.0.50727 shows "DEBUGMSG::ASmartPtr::Release:1" before it exits, a leak.
I include the bits of code I believe are relevant, but please shout if more is required; COM needs a lot of boilerplate code...!
ConsoleApp.exe
using managed.AComObject;
using System;
public static class Program
{
public static void Main()
{
AComObject an_obj = new AComObject();
object[] pData = new object[] { 1 };
object a_val = an_obj.get_TheObject(0, pData);
object[] pData2 = new object[] { a_val };
try
{
object obj3 = an_obj.get_TheObject(1, pData2);
}
catch (System.Exception)
{
// Makes no diff whether it's caught - still does not clean
}
}
}
AComObject.dll
AComObject.idl
interface IAComObject : IDispatch
{
[propget, id(1), helpstring("")] HRESULT DllName([out, retval] BSTR* pName);
[propget, id(2), helpstring("")] HRESULT TheObject([in] LONG count, [in, size_is(count)] VARIANT* pData, [out, retval] VARIANT* pObject);
};
[...]
library AComObjectLib
{
importlib("stdole2.tlb");
// Class information
[...]
coclass AComObject
{
[default] interface IAComObject;
};
};
AComObject.h
[...]
class ATL_NO_VTABLE CAComObject :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAComObject, &CLSID_AComObject>,
public IDispatchImpl<IAComObject, &IID_IAComObject, &LIBID_AComObjectLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_ACOMOBJECT)
BEGIN_COM_MAP(CAComObject)
COM_INTERFACE_ENTRY2(IDispatch, IAComObject)
COM_INTERFACE_ENTRY(IAComObject)
END_COM_MAP()
public:
CAComObject();
virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_DllName(
/* [retval][out] */ BSTR* pName);
virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_TheObject(
/* [in] */ LONG count,
/* [in, size_is(count)] */ VARIANT* pData,
/* [retval][out] */ VARIANT* pObject);
};
OBJECT_ENTRY_AUTO(CLSID_AComObject, CAComObject)
AComObject.cpp
class ASmartPtr : public IUnknown
{
int m_RC;
void DebugMsg(std::string msg)
{
std::stringstream _msg;
_msg << ".\nDEBUGMSG::ASmartPtr::" << msg << "\n";
OutputDebugStringA(_msg.str().c_str());
}
public:
ASmartPtr()
: m_RC(1)
{
DebugMsg(std::string("Created"));
}
virtual ULONG STDMETHODCALLTYPE AddRef()
{
ULONG refcnt = ++m_RC;
std::stringstream msg;
msg << "AddRef:" << refcnt;
DebugMsg(msg.str());
return refcnt;
}
virtual ULONG STDMETHODCALLTYPE Release()
{
ULONG refcnt = --m_RC;
std::stringstream msg;
msg << "Release:" << refcnt;
DebugMsg(msg.str());
if (m_RC == 0)
delete this;
return refcnt;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObj)
{
if (!ppvObj) return E_POINTER;
if (iid == IID_IUnknown)
{
*ppvObj = this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
};
[...]
STDMETHODIMP CAComObject::get_TheObject(LONG count, VARIANT* pData, VARIANT* pObject)
{
if (count == 1)
return E_FAIL;
CComVariant res;
res.punkVal = new ASmartPtr();
res.vt = VT_UNKNOWN;
res.Detach(pObject);
return S_OK;
}
managed.AComObject.dll
This is assembled from the COM object with the following post-build events to enable passing of arrays to get_TheObject() rather than references.
Batch File
call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat"
echo "f" | xcopy /L/D/Y ..\Debug\AComObject.dll managed.AComObject.dll | find "AComObject" > nul
if not errorlevel 1 (
tlbimp ..\Debug\AComObject.dll /primary /keyfile:..\piakey.snk /out:managed.AComObject.dll
ildasm managed.AComObject.dll /out:managed.AComObject.raw.il
perl -p oneliner.pl < managed.AComObject.raw.il > managed.AComObject.il
ilasm managed.AComObject.il /dll /key=..\piakey.snk
)
set errorlevel=0
exit 0
oneliner.pl
$a = 1 if (/TheObject\(/);if ($a){s/object&/object\[\]/; s/marshal\( struct\) pData/marshal\( \[\]\) pData/; $a++; $a&=3;}
This simply changes the IL:
[in] object& marshal( struct) pData) runtime managed internalcall
to
[in] object[] marshal( []) pData) runtime managed internalcall
Some additional information
In considering my response to Hans's comment, I realised some relevant information is missing.
If no exception is thrown (i.e. E_FAIL is changed to S_OK), there is no leak. In the S_OK case, we can see the object reference count returning to 1 as we cross the .NET COM interop back into ConsoleApp.exe. In the E_FAIL case, the refcount remains at 2. In both cases, we can observe the finalizer reducing the refcount again as the application terminates (and observe the object destructor in the S_OK case), but in the E_FAIL case, this still leaves the refcount at 1 so the object is leaked. In CLR 4.0, all behaves as expected (i.e. refcount returns to 1 on passing back to ConsoleApp.exe even in the E_FAIL case).
We are considering upgrading to CLR 4.0 to resolve this leak, but it is not entirely trivial since it treats COM wrapped managed DLLs in a different way and this is a breaking change for some of our clients. If there was a way for me to precisely identify this bug, we could avoid the upgrade pain for a little longer.
In the end, the solution was rather simple, and we were able to proceed without upgrading. It was the old trick of adding a supportedRuntime and the additional attribute to the application.exe.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" />
</startup>
</configuration>
Without the attribute, the .NET2 code loads side-by-side into CLR2, and so we suffer the leak. The attribute allows the .NET2 code to be loaded directly into the CLR4, hence avoiding the leak. There is a detailed review of that attribute here: http://www.marklio.com/marklio/PermaLink,guid,ecc34c3c-be44-4422-86b7-900900e451f9.aspx.
This unfortunately leaves the memory leak extant for anyone using an application with such a config, but this is adequate for the time being.
I have previously used MiniZip (zlib wrapper) to unzip archives. MiniZip cannot be used for Metro applications as it uses deprecated APIs in "iowin32.c" -- CreateFile() and SetFilePointer().
I thought that would be an easy fix and created "iowinrt.c" with CreateFile() and SetFilePointer() replaced with CreateFile2() and SetFilePointerEx(). While this way I obtained a version of MiniZip that uses only approved Win8 APIs, it still turned out to be useless -- I forgot about sandboxing. If I pick a file using FileOpenPicker() and pass its path to my modified MiniZip I still cannot open it -- CreateFile2() will fail with "Access is denied." message.
So it appears that old C API for file access if now mostly useless; it is my understanding that in order to fix this I would need to reimplement my "iowinrt" in C++/CX using the new async file access. Are there any other options? I think I saw somewhere that WinRT does have compress/uncompress functionality but that it only works on individual files, not archives.
Additional requirements it that I need this to work in memory.
For a moment I thought I had a solution via .NET Framework 4.5:
I found this piece of info about how to create .NET classes that can be used from C++/CX:
http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/3ff383d0-0c9f-4a30-8987-ff2b23957f01
.NET Framework 4.5 contains ZipArchive and ZipArchiveEntry classes in System.IO.Compression:
http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive%28v=vs.110%29.aspx#Y0
http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchiveentry%28v=vs.110%29.aspx#Y0
I thought I could create C# Metro Class Library with WinMD Output type exposing ZipArchive and ZipArchiveEntry then use that in my C++/CX project. However, even if it worked it would not work in-memory; it appears that ZipArchive and ZipArchiveEntry work only with files.
Got reading from archive working. Explanation and code below but really just a hack at this point, to see if it's possible at all. I just kept modifying things until I got something working; this is just an example of what works and by no means a production quality code (it's not re-entrant for start). There are undoubtedly many things that are bad/unnecessary/wtf so feel free to use comments to help with clean up.
As mentioned previously, it is no longer enough to pass path to the library -- unless file is in one of KnownFolders (documents, home, media, music, pictures, removable or videos) you end up with "access is denied" message. Instead, library must be able to accept StorageFile^, as returned from FileOpenPicker. At least I haven't found any other way to do it, maybe someone knows better?
MiniZip provides Windows filesystem access layer for zlib via iowin32.h/.c. This still works in desktop mode for old-style apps, but does not work for Metro apps as it uses deprecated APIs and relies on paths. To get MiniZip going on Windows 8, a complete rewrite of iowin32 is required.
To get things working again, first thing was to find a way to pass StorageFile^ all the way down to iowinrt (Windows 8 replacement for iowin32). Fortunately, that was not a problem as MiniZip provides two styles of open file functions -- ones that accept pointer to char, and the others accepting pointer to void. Since ^ is still just a pointer, casting StorageFile^ to void* and than back to StorageFile^ works fine.
Now that I was able to pass StorageFile^ to my new iowinrt, the next problem was how to make new async C++ file access API work with Zlib. In order to support very old C compilers, Zlib is written with old K&R style C. VisualStudio compiler will refuse to compile this as C++, it has to be compiled as C, and new iowinrt must be compiled as C++ of course -- keep that in mind when creating your project. Other things to note about VS project is that I did it as Visual C++ Windows Metro style Static Library although DLL should also work but then you must also define macro to export MiniZip API (I haven't tried this, don't know which macro you have to use). I think I also had to set "Consume Windows Runtime Extension" (/ZW), set "Not Using Precompiled Headers" and add _CRT_SECURE_NO_WARNINGS and _CRT_NONSTDC_NO_WARNINGS to Preprocessor Definitions.
As for iowinrt itself, I've split it in two files. One holds two sealed ref classes -- reader and writer objects; they accept StorageFile^. Reader implements Read, Tell, SeekFromBeginning, SeekFromCurrent and SeekFromEnd (the reason for 3 Seek methods is because ref sealed classes have to stick with RT types and that apparently excludes enums so I just took the easy route). Writer implements just Write at the moment, haven't used it yet.
This is FileReader code:
#include "pch.h"
#include "FileAccess.h" // FileReader and FileWriter
using namespace Concurrency;
using namespace Windows::Security::Cryptography;
using namespace CFileAccess;
FileReader::FileReader(StorageFile^ archive)
{
if (nullptr != archive)
{
create_task(archive->OpenReadAsync()).then([this](IRandomAccessStreamWithContentType^ archiveStream)
{
if (nullptr != archiveStream)
{
_readStream = archiveStream;
}
}).wait();
}
} // end of constructor
int32 FileReader::Read(WriteOnlyArray<byte>^ fileData)
{
int32 bytesRead = 0;
if ((nullptr != _readStream) && (fileData->Length > 0))
{
try
{
auto inputStreamReader = ref new DataReader(_readStream);
create_task(inputStreamReader->LoadAsync(fileData->Length)).then([&](task<unsigned int> dataRead)
{
try
{
bytesRead = dataRead.get();
if (bytesRead)
{
inputStreamReader->ReadBytes(fileData);
}
}
catch (Exception^ e)
{
bytesRead = -1;
}
inputStreamReader->DetachStream();
}).wait();
}
catch (Exception^ e)
{
bytesRead = -1;
}
}
return (bytesRead);
} // end of method Read()
int64 FileReader::Tell(void)
{
int64 ret = -1;
if (nullptr != _readStream)
{
ret = _readStream->Position;
}
return (ret);
} // end of method Tell()
int64 FileReader::SeekFromBeginning(uint64 offset)
{
int64 ret = -1;
if ((nullptr != _readStream) && (offset < _readStream->Size))
{
_readStream->Seek(offset);
ret = 0;
}
return (ret);
} // end of method SeekFromBeginning()
int64 FileReader::SeekFromCurrent(uint64 offset)
{
int64 ret = -1;
if ((nullptr != _readStream) && ((_readStream->Position + offset) < _readStream->Size))
{
_readStream->Seek(_readStream->Position + offset);
ret = 0;
}
return (ret);
} // end of method SeekFromCurrent()
int64 FileReader::SeekFromEnd(uint64 offset)
{
int64 ret = -1;
if ((nullptr != _readStream) && ((_readStream->Size - offset) >= 0))
{
_readStream->Seek(_readStream->Size - offset);
ret = 0;
}
return (ret);
} // end of method SeekFromEnd()
iowinrt sits between MiniZip and FileReader (and FileWriter). It's too long to give everything here but this should be sufficient to reconstruct the rest since it's mostly just more of the same with different function names, plus a bunch of fill_winRT_filefuncxxx() which are obvious:
#include "zlib.h"
#include "ioapi.h"
#include "iowinrt.h"
#include "FileAccess.h"
using namespace Windows::Security::Cryptography;
using namespace Platform;
using namespace CFileAccess;
static FileReader^ g_fileReader = nullptr;
static FileWriter^ g_fileWriter = nullptr;
static StorageFile^ g_storageFile = nullptr;
[...]
static voidpf winRT_translate_open_mode(int mode)
{
if (nullptr != g_storageFile)
{
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
{
g_fileWriter = nullptr;
g_fileReader = ref new FileReader(g_storageFile);
}
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
{
g_fileReader = nullptr;
g_fileWriter = ref new FileWriter(g_storageFile);
}
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
{
g_fileReader = nullptr;
g_fileWriter = ref new FileWriter(g_storageFile);
}
}
return (nullptr != g_fileReader ? reinterpret_cast<voidpf>(g_fileReader) : reinterpret_cast<voidpf>(g_fileWriter));
}
voidpf ZCALLBACK winRT_open64_file_func (voidpf opaque,const void* storageFile,int mode)
{
g_storageFile = reinterpret_cast<StorageFile^>(const_cast<void*>(storageFile));
return (winRT_translate_open_mode(mode));
}
[...]
Long ZCALLBACK winRT_read_file_func (voidpf opaque, voidpf stream, void* buf,uLong size)
{
uLong bytesRead = 0;
if (nullptr != g_fileReader)
{
auto fileData = ref new Platform::Array<byte>(size);
bytesRead = g_fileReader->Read(fileData);
memcpy(buf, fileData->Data, fileData->Length);
}
return (bytesRead);
}
uLong ZCALLBACK winRT_write_file_func (voidpf opaque,voidpf stream,const void* buf,uLong size)
{
uLong bytesWritten = 0;
if (nullptr != g_fileWriter)
{
auto bytes = ref new Array<uint8>(reinterpret_cast<uint8*>(const_cast<void*>(buf)), size);
IBuffer ^writeBuffer = CryptographicBuffer::CreateFromByteArray(bytes);
bytesWritten = g_fileWriter->Write(writeBuffer);
}
return (bytesWritten);
}
long ZCALLBACK winRT_tell_file_func (voidpf opaque,voidpf stream)
{
long long ret = 0;
if (nullptr != g_fileReader)
{
ret = g_fileReader->Tell();
}
return (static_cast<long>(ret));
}
ZPOS64_T ZCALLBACK winRT_tell64_file_func (voidpf opaque, voidpf stream)
{
ZPOS64_T ret = 0;
if (nullptr != g_fileReader)
{
ret = g_fileReader->Tell();
}
return (ret);
}
[...]
long ZCALLBACK winRT_seek64_file_func (voidpf opaque, voidpf stream,ZPOS64_T offset,int origin)
{
long long ret = -1;
if (nullptr != g_fileReader)
{
switch (origin)
{
case ZLIB_FILEFUNC_SEEK_CUR :
ret = g_fileReader->SeekFromCurrent(offset);
break;
case ZLIB_FILEFUNC_SEEK_END :
ret = g_fileReader->SeekFromEnd(offset);
break;
case ZLIB_FILEFUNC_SEEK_SET :
ret = g_fileReader->SeekFromBeginning(offset);
break;
default:
// should never happen!
ret = -1;
break;
}
}
return (static_cast<long>(ret));
}
int ZCALLBACK winRT_close_file_func (voidpf opaque, voidpf stream)
{
g_fileWriter = nullptr;
g_fileReader = nullptr;
return (0);
}
int ZCALLBACK winRT_error_file_func (voidpf opaque,voidpf stream)
{
/// #todo Get errors from FileAccess
return (0);
}
This is enough to get MiniZip going (at least for reading) but you have to take care how you call MiniZip functions -- since Metro is all about async and blocking UI thread will end up with exception, you must wrap access in tasks:
FileOpenPicker^ openPicker = ref new FileOpenPicker();
openPicker->ViewMode = PickerViewMode::List;
openPicker->SuggestedStartLocation = PickerLocationId::ComputerFolder;
openPicker->FileTypeFilter->Append(".zip");
task<IVectorView<StorageFile^>^>(openPicker->PickMultipleFilesAsync()).then([this](IVectorView<StorageFile^>^ files)
{
if (files->Size > 0)
{
std::for_each(begin(files), end(files), [this](StorageFile ^file)
{ // open selected zip archives
create_task([this, file]()
{
OpenArchive(file);
[...]
});
});
}
else
{
rootPage->NotifyUserBackgroundThread("No files were returned.", NotifyType::ErrorMessage);
}
});
[...]
bool OpenArchive(StorageFile^ archive)
{
bool isArchiveOpened = false;
if (nullptr != archive)
{ // open ZIP archive
zlib_filefunc64_def ffunc;
fill_winRT_filefunc64(&ffunc);
unzFile archiveObject = NULL;
create_task([this, &ffunc, archive]()
{
archiveObject = unzOpen2_64(reinterpret_cast<const void*>(archive), &ffunc);
}).wait();
if (NULL != archiveObject)
{
[...]
I'm confused about when I need to use KeepAlive in my C++/CLI wrapper code and how lifetimes are handled in it. Consider the following code and note the places where I ask whether KeepAlive is needed.
// convert from managed to native string
inline std::string ToStdString(String^ source)
{
if (String::IsNullOrEmpty(source))
return std::string();
int len = ((source->Length+1) * 2);
/*** Do I need GC::KeepAlive(source) here? ***/
char *ch = new char[ len ];
bool result ;
{
pin_ptr<const wchar_t> wch = PtrToStringChars( source );
result = wcstombs( ch, wch, len ) != -1;
}
std::string target = ch;
delete ch;
if(!result)
throw gcnew Exception("error converting System::String to std::string");
return target;
}
// convert from native to managed string
inline String^ ToSystemString(const std::string& source)
{
return gcnew String(source.c_str());
}
// unmanaged C++ class
struct NativeDog
{
std::string name;
std::string bark() const {return "woof";}
void eat(std::string& food) const {food.clear();}
};
typedef shared_ptr<NativeDog> NativeDogPtr;
// C++/CLI wrapper class
ref class ManagedDog
{
NativeDogPtr* base_;
NativeDog& base() {return **base_;}
ManagedDog() {base_ = new NativeDogPtr(new NativeDog);}
~ManagedDog() {if (base_) delete base_;}
!ManagedDog() {delete this;}
property String^ name
{
String^ get() {return ToSystemString(base().name);}
void set(String^ name)
{
base().name = ToStdString(name);
/*** Do I need GC::KeepAlive(name) here? ***/
}
}
String^ bark() {return ToSystemString(base().bark());}
void eat(String^ food)
{
std::string nativeFood = ToStdString(food);
base().eat(nativeFood);
food = ToSystemString(nativeFood);
/*** Do I need GC::KeepAlive(food) here? ***/
}
};
// unmanaged C++ class
struct NativeKennel
{
vector<NativeDogPtr> dogs;
};
// C++/CLI wrapper class
ref class ManagedKennel
{
NativeKennel* base_;
NativeKennel& base() {return *base_;}
IList<ManagedDog^>^ dogs;
void addDog(ManagedDog^ dog)
{
base().dogs.push_back(*dog->base_);
dogs->Add(dog);
/*** Do I need GC::KeepAlive(dog) here? Will the IList manage the ManagedDog lifetimes? ***/
}
};
Right before calling a managed delegate's function pointer.
This is a common failure mode, the garbage collector cannot see any reference held by native code. The managed code must store a reference to the delegate itself to prevent it from getting garbage collected. There's a debugger assistant for this, not sure why you didn't see it. More details in this MSDN Library article.
None of the above!
If you access managed classes in C++/CLI, KeepAlive won't help. You need to pin the data in memory to stop it from relocating afer a garbage collect. In all of these examples, this is done implicitly by the functions you call.
KeepAlive has a different goal. References stored on the stack are subject to garbage collection immediately after the last time the object is dereferenced. KeepAlive prevents this from happening, by extending the lifetime of your object until after the KeepAlive call.