מיום ליום עוד ועוד חברות מצטרפות למהפכה של המדיה סנטר. שידור אינטראקטיבי אמיתי. עם העליה של אפליקצית קשת נוסף חומר רב ומגוון:
יש אמנם דבר אחד שמפריע לי בכל האתרים הישראלים. אין לי יכולת לבקש לראות שידור של מספר קטעים ברצף. לדוגמא: את כל הקטעים של תוכנית מסוימת של ארץ נהדרת. ברגע שמסתיים קטע אני צריך לבחור את הקטע הבא. זו בכל זאת טלויזיה. אפשר גם להשתמש בתור, ז"א לאפשר לי להוסיף קטעים לתור של צפיה ושאני מבקש לנגן אותם ברצף.
This is not new, but many people have hard time when combining native and managed code. One of the more challenging interop task is to call back from native code into managed code.
There are multiple ways of doing it:
- Use P/Invoke and pass a delegate as a pointer to the native code.
- Use COM interop and connection points (The COM wrapper produces .Net events)
- You may find other ways such as using Windows Messages or other inter-process comunication mechanism. For example to call to native code (not a callback) You can even use Code Generation and emit a "Calli" opcode that gets an IntPtr to a native function address (Adam Nathan demonstrates it in his .NET and COM book, pages 818-821. He P/Invoke to LoadLibrary and GetProcAddress and then he creates a method call using Reflection.Emit)
- Use C++/CLI and gcroot as a reverse reference to the managed code.
The first method is very easy, just define the delegate with the managed prototype that best matches the native function pointer, and pass it to the native register function. Be aware of two things:
-
The delegate object should be kept a live as long as the native code has a pointer to it.
-
The calling convention is __stdcall (great for Win32 API, but not so good for the default C/C++ __cdecl convention)
Use the COM way only if the native side is already based on COM. COM interop involved thread marshaling that can be nightmare since .NET is actually FTM while COM is usually STA or MTA.
The third ways are overkill to my opinion.
So, we have the C++/CLI way. C++/CLI based interop is best when you deal with classes and objects and not just plain C style function pointer.
In the following example, we have a native DLL that implements the basic command pattern:
The native header file:
#ifdef NATIVE_EXPORTS
#define NATIVE_API __declspec(dllexport)
#else
#define NATIVE_API __declspec(dllimport)
#endif
#include <stack>
class NATIVE_API CCommand
{
public:
virtual void Do() = 0;
virtual void Undo() = 0;
};
// This class is exported from the native.dll
class NATIVE_API CActivator
{
public:
void Activate(CCommand *pCommand);
CCommand *Undo();
private:
std::stack<CCommand *> m_commands;
};
The native CPP file:
// native.cpp : Defines the exported functions for the DLL application.
//
#include "stdafx.h"
#include "native.h"
// This is the constructor of a class that has been exported.
// see native.h for the class definition
void CActivator::Activate(CCommand *pCommand)
{
pCommand->Do();
m_commands.push(pCommand);
}
CCommand *CActivator::Undo()
{
if (m_commands.empty())
return NULL;
CCommand *pCommand = m_commands.top();
m_commands.pop();
pCommand->Undo();
return pCommand;
}
As you can see the CActivator::Activate method takes a CCommand object, calls its Do() method and pushes it into a stack. The CActivator::Undo() method, takes a CCommand object from the stack, executes it and returns the command (this is later used to reclaim the command object native memory).
We would like to create a command object in C#, something like this:
namespace Commander
{
class MyCommand : Activation.ICommand
{
#region ICommand Members
public void Do()
{
Console.WriteLine("Do");
}
public void Undo()
{
Console.WriteLine("Undo");
}
#endregion
}
class MyCommand1 : Activation.ICommand
{
#region ICommand Members
public void Do()
{
Console.WriteLine("Do1");
}
public void Undo()
{
Console.WriteLine("Undo1");
}
#endregion
}
class Program
{
static void Main(string[] args)
{
MyCommand cmd = new MyCommand();
MyCommand1 cmd1 = new MyCommand1();
using (Activation.Activator activator = new Activation.Activator())
{
activator.Activate(cmd);
activator.Activate(cmd1);
activator.Undo();
activator.Undo();
}
}
}
}
You may notice that I use a namespace Activation, and an Activator class that has Activate and Undo methods. This class is the glue, the C++/CLI wrapper of the native DLL that make the bridge between the managed and the native worlds. The wrapper is a C++/CLI class library CLR project that includes the native CActivator header file and link with the native lib file that represents the native DLL. Don't forget to copy the native DLL to the C# debug or release directory so it will be loaded with the wrapper assembly. (If you don't copy the native DLL you get a "file not found exception" from the .NET loader)
Here are the wrapper files:
// Activation.h
#pragma once
#include "..\native\native.h"
#include <vcclr.h>
namespace Activation {
public interface class ICommand
{
void Do();
void Undo();
};
class CommandWrapper : public CCommand
{
public:
CommandWrapper(gcroot<ICommand ^> managedCommand)
: m_managedCommand(managedCommand)
{
}
virtual void Do()
{
m_managedCommand->Do();
}
virtual void Undo()
{
m_managedCommand->Undo();
}
private:
gcroot<ICommand ^> m_managedCommand;
};
public ref class Activator
{
public:
Activator();
~Activator();
void Activate(ICommand ^command);
void Undo();
private:
CActivator *m_pActivator;
};
}
// Activation.cpp
#include "stdafx.h"
#include "Activation.h"
namespace Activation
{
Activator::Activator() : m_pActivator(new CActivator())
{
}
Activator::~Activator()
{
delete m_pActivator;
}
void Activator::Activate(ICommand ^command)
{
CommandWrapper *pCommandWrapper = new CommandWrapper(command);
m_pActivator->Activate(pCommandWrapper);
}
void Activator::Undo()
{
delete m_pActivator->Undo();
}
}
When the C# code creates the Activation.Activator, the C++/CLI Activator class creates the native CActivator object and keeps it in a regular native pointer.
The C# code implements a command that implements the Activation.ICommand. When the C# calls the Activator.Activate(cmd), the C++/CLI Activator class allocates a CommandWrapper class that implements the CCommand base class from the native DLL. The magic is in the CommandWrapper class. To hold the managed ICommand object the CommandWrapper uses the gcroot template (gcroot is a C++ template that wraps the GCHandle class). gcroot keeps the managed object reference and allows us to call back to the C# command.
The C++/CLI Activator class has a destructor. Destructors in C++/CLI ref classes implement the IDispose pattern. the C# using statement activates the Dispose method that delete the native CActivator class.
יש הרבה דברים חדשים בגרסא הבאה של VS. אחד מהם הוא היכולת לקבל מטריקות בסיסיות כל managed code.
זה אמנם לא ndepend אבל זה לא רע בכלל לדעת מה רמת הקוד:
שימו לב שניתן בקלות ליצא את המידע ל Excel.
אתם מכירים את זה שמישהו מתקשר אליכם ומספר לכם על בעיה בטלפון, אתם לא מול מחשב או מייל, אין סיכוי שתפתרו את הבעיה מבלי לראות דוגמא בעיניים, אבל אי אפשר לשלוח לכם דוגמא כי יש המון קוד ואסור להוציא אותו וכו', אז זה בדיוק מה שהיה לי ביום שישי האחרון, ואין כמו הסיפוק של להצליח לפתור את הבעיה.
שיחת טלפון:
"הי אלון, אני השתתפתי בקורס שלך על C++/CLI ואני עושה מה שאמרתי לנו, אני עוטף איזה קוד של Logger שאנחנו משתמשים ב ++C ואני חוטף Exception"
אני: "למה שלא תשתמשו ב Log4Net, או במשהו דומה?"
הוא: "לא! ה Logger שלנו ממש טוב..."
אני: "אין הרבה סיכוי שאני אוכל לעזור לך בטלפון, שלח לי מייל עם דוגמא שמשחזרת את הבעיה"
הוא: "תראה אני ישבתי על זה כל אתמול, זה בעיה לשלוח את כל הקוד הזה, ב Native זה עובד אבל דרך #C אני מקבל File Not Found Exception, אבל נראה לי שקבצי הלוג תקינים."
אני: "חושב, חושב, חושב... אתה מעתיק את הספריות הדינאמיות של ה Native Code אל המקום שבו נמצא ה Exe שפותח ב #C"
הוא: "אני רק מוסיף רפרנס..."
אני: "כן, אבל #C טוען את המעטפת ה C++/CLI.שצריכה לטעון את הNative Dll והוא לא מוצא אותו"
הוא: "מעתיק לספרית Debug את ה Dll, תודה רבה!!! למה לא הצלחתי לתפוס אותך אתמול."
אני: "מצטער, הייתי ביעוץ יום שלם, בכל אופן שמחתי לעזור ודרך אגב אתה יכול להוסיף ל VS פקודה שמבצעת העתקה של ה Native DLL בסוף ה Build"
הוא: "תודה, אני אוכל להתיעץ איתך גם בעתיד?"
אני: "בוודאי, ובהצלחה"